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:
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.
Want to use code from this post? Check out the license.