I'm 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 simplest 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 may be listed by any subset of the first name, last name and a nick. The sorting key is the last name if it's present, otherwise it's the nickname if it's present and a firstname otherwise.
I'm having difficulties here since using of variables as xsl:sort keys is apparently not allowed.
My current best shot is to have a two-step transformation: Add a special tag to each record 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 transform works, I'm wondering if there is more elegant way of doing it in just one go?
The XSLT <xsl:sort> element is used to specify a sort criteria on the nodes. It displays the output in sorted form. The <xml:sort> element is added inside the <xsl:for-each> element in the XSL file, to sort the output.
We could sort some or all of the XML elements, then generate output based on the sorted elements. We could group the data, selecting all elements that have some property in common, then sorting the groups of elements.
XSLT is designed to be used as part of XSL. In addition to XSLT, XSL includes an XML vocabulary for specifying formatting. XSL specifies the styling of an XML document by using XSLT to describe how the document is transformed into another XML document that uses the formatting vocabulary.
Returns the contents of the current group selected by xsl:for-each-group. Available in XSLT 2.0 and later versions. Available in all Saxon editions. current-group() ➔ item()*
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)),
'
')"/>
</xsl:template>
</xsl:stylesheet>
Can you try if this template gives the expected result? For your simple example it gives the correct answer but there might be corner cases that won't work. Normalize-space is used here to remove leading 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> </xsl:text>
</xsl:for-each>
</xsl:template>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With