Ben, I know that you have done a fair bit of work with xml files in ColdFusion and I am hoping you can help me with a problem I am trying to solve. I am interested in sorting an xml file by a given attribute. For Example:

<xmlitems>
<xmlitem name="b">something</xmlitem>
<xmlitem name="a">somethingelse</xmlitem>
</xmlitems>

I would like to sort the xmlitem nodes by the name attribute. Do you know of a simple way to do this? Can xslt be used to solve this? I know that xslt can be used for sorting, but I am not familiar with it.

This was a really interesting question. I have to say, it's not one that I have come up against before, even in my own programming, and it took me a little while to figure out. The solution I have is for this very specific problem; however, after this, I'd love to come up with a way to abstract this out into something more generic. But, that's a whole other blog post altogether.

Ok, so let's just take a look at the code, and then we can walk through it a bit more:

  • <!--- Define the XML data. --->
  • <cfxml variable="xmlData">
  •  
  • <xmlitems>
  • <xmlitem name="b">something</xmlitem>
  • <xmlitem name="a">somethingelse</xmlitem>
  • </xmlitems>
  •  
  • </cfxml>
  •  
  •  
  • <!--- Define the XSL Transofrm data. --->
  • <cfxml variable="xmlTransform">
  •  
  • <!--- Document type declaration. --->
  • <?xml version="1.0" encoding="ISO-8859-1"?>
  •  
  • <xsl:transform
  • version="1.0"
  • xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  •  
  • <!--- Match the root node (xmlitems). --->
  • <xsl:template match="xmlitems">
  •  
  • <!---
  • Copy the current node's top-level values
  • (the tag and it's attributes, but not it's
  • descendents).
  • --->
  • <xsl:copy>
  •  
  • <!--- Loop over the xmlitem nodes. --->
  • <xsl:for-each select="xmlitem">
  •  
  • <!---
  • As we are looping over these xmlitem
  • nodes, sort them ascendingly according
  • to their NAME attribute.
  • --->
  • <xsl:sort
  • select="@name"
  • data-type="text"
  • order="ascending"
  • />
  •  
  • <!---
  • Copy the entire node (include its
  • descendantas).
  • --->
  • <xsl:copy-of select="." />
  •  
  • </xsl:for-each>
  •  
  • </xsl:copy>
  •  
  • </xsl:template>
  •  
  • </xsl:transform>
  •  
  • </cfxml>
  •  
  •  
  • <cfoutput>
  •  
  • <!--- Output the transformation. --->
  • #HTMLEditFormat(
  • XmlTransform( xmlData, xmlTransform )
  • )#
  •  
  • </cfoutput>

Running the above code, we get the following output (based on our resultant XML document):

  • <?xml version="1.0" encoding="UTF-8"?>
  • <xmlitems>
  • <xmlitem name="a">somethingelse</xmlitem>
  • <xmlitem name="b">something</xmlitem>
  • </xmlitems>

As you can see, the XML document is copied over with the XmlItem nodes sorting ascendingly using the NAME attribute.

Ok, so let's walk through the code a bit. The first thing we are doing is matching a template on the root element, XmlItems. Once we have that matched node, we are copying the that node using the xsl:copy command. The xsl:copy command copies the current node without its child nodes. Within this copy, we then loop over the child nodes, XmlItem, and copy those using the xsl:copy-of command. The difference between xsl:copy and the xsl:copy-of command is that the xsl:copy-of copies over the current tag attribute AND the child nodes. The key to this solution is that inside the node loop, xsl:for-each, we are using an xsl:sort command to define the order in which the child-nodes are being iterated. In our case, the sort direction depends on the value of the NAME attribute as defined by the select="@name" sort.

I know that this solution is very specific to your sample data; but, maybe it will point you in the right direction. I'd like to now take some time to figure out how to do this in a more generic way.