Xsl: sort XML file using multiple elements

I am trying to sort a bunch of records in an XML file. The trick is that I need to sort using different elements for different nodes. To give a simple example, I want to do this: given an xml file

<?xml version="1.0" encoding="utf-8" ?> <buddies> <person> <nick>Jim</nick> <last>Zulkin</last> </person> <person> <first>Joe</first> <last>Bumpkin</last> </person> <person> <nick>Pumpkin</nick> </person> <person> <nick>Andy</nick> </person> </buddies> 

I want to convert it to

 Andy Joe Bumpkin Pumpkin Jim Zulkin 

That is, a person can be specified by any subset of the name, surname and nickname. The sort key is the last name, if one is present, otherwise it is an alias if one is present, and the first name otherwise.

I'm having difficulty since using variables as xsl: sort keys are apparently not allowed .

My current best shot is a two-step conversion: add a custom tag to each entry using this stylesheet

 <?xml version="1.0" encoding="ISO-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="no" indent="yes"/> <!-- *** convert each person record into a person2 record w/ the sorting key *** --> <xsl:template match="/buddies"> <buddies> <xsl:for-each select="person"> <person2> <xsl:copy-of select="*"/> <!-- add the sort-by tag --> <sort-by> <xsl:choose> <xsl:when test="last"> <xsl:value-of select="last"/> </xsl:when> <xsl:otherwise> <xsl:choose> <xsl:when test="nick"> <xsl:value-of select="nick"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="first"/> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </sort-by> </person2> </xsl:for-each> </buddies> 

And then sort the resulting xml

 <?xml version="1.0" encoding="ISO-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/buddies"> <xsl:apply-templates> <xsl:sort select="sort-by"/> </xsl:apply-templates> </xsl:template> <xsl:template match="person2"> <xsl:value-of select="first"/> <xsl:value-of select="nick"/> <xsl:value-of select="last"/><xsl:text> </xsl:text> </xsl:template> 

While this two-step conversion works, I wonder if there is a more elegant way to do this in just one go?

+7
xml xpath xslt
source share
2 answers

You can use the concat XPath function:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:template match="/buddies"> <xsl:apply-templates> <xsl:sort select="concat(last,nick,first)"/> </xsl:apply-templates> </xsl:template> <xsl:template match="person"> <xsl:value-of select="concat(normalize-space(concat(first, ' ', nick, ' ', last)), '&#xA;')"/> </xsl:template> </xsl:stylesheet> 
+5
source share

Can you try if this template gives the expected result? For your simple example, it gives the correct answer, but there may be corner cases that won't work. Normalized space is used here to remove the top and trailing spaces if one of the elements is missing.

 <xsl:template match="/buddies"> <xsl:for-each select="person"> <xsl:sort select="normalize-space(concat(last, ' ', nick, ' ', first))"/> <xsl:if test="first"> <xsl:value-of select="first" /> <xsl:text> </xsl:text> </xsl:if> <xsl:if test="nick"> <xsl:value-of select="nick" /> <xsl:text> </xsl:text> </xsl:if> <xsl:value-of select="last" /> <xsl:text>&#10;</xsl:text> </xsl:for-each> </xsl:template> 
+1
source share

All Articles