Search code examples
xsltxslt-1.0xslt-grouping

Not able to group similar records in XSLT


I am trying to group all similar records based on language. But I am not able to group in XSLT. I am using XSL KEY function group the record in XSLT. I am trying loop and add each group records to one group.

I have the following input xml.

<root>
    <element name="David" language="German"></element>
    <element name="Sarah" language="German"></element>
    <element name="Isaac" language="English"></element>
    <element name="Abraham" language="German"></element>
    <element name="Jackson" language="English"></element>
    <element name="Deweher" language="English"></element>
    <element name="Jonathan" language="Hindi"></element>
    <element name="Mike" language="Hindi"></element>
</root>

XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="1.0">
    <xsl:key name="lang" match="element" use="@language"></xsl:key>    
    <xsl:template match="/">    
        <root>
            <xsl:for-each select="key('lang',//element/@language)">
                <Group>
                    <xsl:attribute name="name" select=".//@language"></xsl:attribute>
                    <member><xsl:value-of select=".//@name"/></member>                
                </Group>    
            </xsl:for-each>
        </root>
    </xsl:template>
</xsl:stylesheet>

Expected Output :

<root>
   <Group name="German">
      <member>David</member>
      <member>Sarah</member>
      <member>Abraham</member>
   </Group>
   <Group name="English">
      <member>Isaac</member>
      <member>Jackson</member>
      <member>Deweher</member>
   </Group>
   <Group name="Hindi">
      <member>Jonathan</member>
      <member>Mike</member>
   </Group>
</root>

Actual Output :

<root>
    <Group name="German">
        <member>David</member>
    </Group>
    <Group name="German">
        <member>Sarah</member>
    </Group>
    <Group name="English">
        <member>Isaac</member>
    </Group>
    <Group name="German">
        <member>Abraham</member>
    </Group>
    <Group name="English">
        <member>Jackson</member>
    </Group>
    <Group name="English">
        <member>Deweher</member>
    </Group>
    <Group name="Hindi">
        <member>Jonathan</member>
    </Group>
    <Group name="Hindi">
        <member>Mike</member>
    </Group>
</root>

I am getting each records separately. Can someone please let me know what went wrong in the XSL. Thanks :)


Solution

  • I made some changes in your stylesheet. This should achieve the result you expect:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    
        <xsl:output indent="yes"/>
    
        <xsl:key name="lang" match="element" use="@language"></xsl:key>    
    
        <xsl:template match="root">    
            <xsl:copy>
                <xsl:for-each select="element[count(. | key('lang', @language)[1]) = 1]">
                    <Group name="{@language}">
                        <xsl:for-each select="key('lang', @language)">
                            <member><xsl:value-of select="@name"/></member>                
                        </xsl:for-each>
                    </Group>
                </xsl:for-each>
            </xsl:copy>
        </xsl:template>
    
    </xsl:stylesheet>
    

    The first loop selects each unique language (a node-set of size 3), and creates a context for the inner loop. The inner loop iterates through each element and selects only the ones that have the same language.

    Muenchian grouping may seem hard to grasp, but you can always apply the template shown in this tutorial and not have to think much. I simply applied that template to your example.

    UPDATE: Here is a solution without using for-each loops:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    
        <xsl:output indent="yes"/>
    
        <xsl:key name="lang" match="element" use="@language"></xsl:key>    
    
        <xsl:template match="root">    
            <xsl:copy>
                <xsl:apply-templates select="element[generate-id(.) = generate-id(key('lang', @language)[1])]"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="element">
            <Group name="{@language}">
                <xsl:apply-templates select="key('lang', @language)" mode="member"/>
            </Group>
        </xsl:template>
    
        <xsl:template match="element" mode="member">
            <member><xsl:value-of select="@name"/></member>  
        </xsl:template>
    
    </xsl:stylesheet>