XSLT grouping on attributes

Tag: xslt , grouping Author: huangly001 Date: 2011-05-02

The input file format is

<Node1 Attribute11="colors" >
    <Node2 Attribute21="Order1" Attribute22="123" Attribute23="red" />
    <Node2 Attribute21="Order2" Attribute22="456" Attribute23="blue" />
    <Node2 Attribute21="Order1" Attribute22="789" Attribute23="green" />
    <Node2 Attribute21="Order2" Attribute22="012" Attribute23="yellow" />
    <Node2 Attribute21="Order3" Attribute22="345" Attribute23="red" />
    <Node2 Attribute21="Order1" Attribute22="678" Attribute23="blue" />
</Node1>

The desired output is

<Node1 Attribute11="colors" >
    <Node3 Attribute21="Order1" >
    <Node2 Attribute21="Order1" Attribute22="123" Attribute23="red" />
    <Node2 Attribute21="Order1" Attribute22="789" Attribute23="green" />
    <Node2 Attribute21="Order1" Attribute22="678" Attribute23="blue" />
    </Node3>
    <Node3 Attribute21="Order2">
    <Node2 Attribute21="Order2" Attribute22="456" Attribute23="blue" />
    <Node2 Attribute21="Order2" Attribute22="012" Attribute23="yellow" />
    </Node3>
    <Node3 Attribute21="Order3">
    <Node2 Attribute21="Order3" Attribute22="345" Attribute23="red" />
    </Node3>
</Node1>

With the grouping I am not able to get the output in the desired format. Any help with XSLT 1.0 is much appreciated

Other Answer1

XSLT 1.0 solutions tested with Saxon-HE 9.2.1.1


Solution with looping:

 <xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="byAttribute21" match="Node1/Node2" use="@Attribute21"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="Node1">
<Node1>
    <xsl:copy-of select="@*"/>
    <xsl:for-each select="Node2[generate-id()=generate-id(key('byAttribute21', @Attribute21)[1])]">
        <Node3 Attribute21="{@Attribute21}">
            <xsl:apply-templates select="key('byAttribute21', @Attribute21)"/>
        </Node3>
    </xsl:for-each>
 </Node1>
</xsl:template>

</xsl:stylesheet>

Solution without looping:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="byAttribute21" match="Node1/Node2" use="@Attribute21"/>

 <xsl:template match="node()|@*">
     <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
 </xsl:copy>
</xsl:template>

<xsl:template match="Node1">
    <Node1>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates select="*" mode="node3"/>
    </Node1>
</xsl:template>

 <xsl:template match="Node2[generate-id()=generate-id(key('byAttribute21', @Attribute21)[1])]" mode="node3">
 <Node3 Attribute21="{@Attribute21}">
   <xsl:apply-templates select="key('byAttribute21',@Attribute21)" mode="node2"/>
  </Node3>
     </xsl:template>

     <xsl:template match="Node2" mode="node2">
         <xsl:copy-of select="."/>
     </xsl:template>

</xsl:stylesheet>