Tuesday, January 15, 2013

XSLT sorting by child attributes

I'm new to xslt but I had to tackle a problem involving sorting an xml structure based on attributes of generic children. Something like the xml below.
<data>
 <node name="Node1">
  <attribute name="sortValue" value="value1"/>
  <attribute name="color" value="red"/>
  <attribute name="size" value="XL"/>
 </node> 
 <node name="Node2">
  <attribute name="sortValue" value="value3"/>
  <attribute name="color" value="pink"/>
  <attribute name="size" value="L"/>
 </node> 
 <node name="Node3">
  <attribute name="sortValue" value="value2"/>
  <attribute name="color" value="blue"/>
  <attribute name="size" value="M"/>
 </node> 
 <node name="Node4">
  <attribute name="sortValue" value="value3"/>
  <attribute name="color" value="black"/>
  <attribute name="size" value="S"/>
 </node> 
</data>
After searching a little I found some solutions and documented them here for future reference.

Simple Sorting 

The simple case would involve using the sortValue attributes. To perform a simple sort for the xml on the sort field you can use the following xslt:
<xsl:template match="/">
  <html>
  <body>
     <table border="1">
      <tr bgcolor="pink">
        <th>Node Name</th>
        <th>Sort Value</th>
        <th>Color</th>
        <th>Size</th>
      </tr>
      <xsl:for-each select="data/node">
      <xsl:sort select="attribute/@value[../@name = 'sortValue']"/>
      <tr>
        <td><xsl:value-of select="@name"/></td>
  <xsl:for-each select="attribute">
   <td><xsl:value-of select="@value"/></td>
  </xsl:for-each>
      </tr>
      </xsl:for-each>
    </table>
  </body>
  </html>
</xsl:template>
 
This should give you the following result:

Node NameSort ValueColorSize
Node1value1redXL
Node3value2blueM
Node2value3pinkL
Node4value3blackS

Sorting by multiple attributes

XSLT also supports multiple sorting so if you want to sort by multiple attributes (for example the last two lines have the same sort value but the color are not sorted) you can add a second sort. For this exmpla just change

<xsl:sort select="attribute/@value[../@name = 'sortValue']"/>
with
<xsl:sort select="attribute/@value[../@name = 'sortValue']"/>
<xsl:sort select="attribute/@value[../@name = 'color']"/>
and you should get:
Node NameSort ValueColorSize
Node1value1redXL
Node3value2blueM
Node4value3blackS
Node2value3pinkL

 

Sorting finite options by priority

If you have a list of possibilities with a finite number of priorities (like the clothes sizes in the example) you can use a trick to prioritize the list. This involves converting the options to numbers and giving each a weight for the purpose of sorting and performing the sort on numeric values.
To sort by size in this example you could use the following:
<xsl:sort data-type='number' order='ascending' 
 select="(number(attribute/@value[../@name = 'size'] = 'S') * 1)
  + (number(attribute/@value[../@name = 'size'] = 'M') * 2)
  + (number(attribute/@value[../@name = 'size'] = 'L') * 3)
  + (number(attribute/@value[../@name = 'size'] = 'XL') * 4)"/>
This should give the following result:
Node NameSort ValueColorSize
Node4value3blackS
Node3value2blueM
Node2value3pinkL
Node1value1redXL

Happy sorting