Reputation: 786
I'm trying to figure out a (relatively) efficient way of sorting elements in XSLT so that I don't have to go back into my Java code and mess with the DOM.
If I have a file like this:
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<Root>
<Player name="Jane Doe">
<Location distance="90"/>
<Location distance="45"/>
</Player>
<Player name="John Doe">
<Location distance="50"/>
<Location distance="20"/>
</Player>
</Root>
My goal is to sort the players by the distance of the closest location. In other words, since John Doe has a location within 20 miles, he's closer and his node needs to be sorted above Jane Doe whose closest locations is 45 miles.
Is such a thing even possible? If not, no biggie. I just wanted to throw this out there before I begin DOM manipulation.
Upvotes: 1
Views: 233
Reputation: 243449
Here is a short XSLT 1.0 transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="*/@distance[not(. > ../../*/@distance)]" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Root>
<Player name="Jane Doe">
<Location distance="90"/>
<Location distance="45"/>
</Player>
<Player name="John Doe">
<Location distance="50"/>
<Location distance="20"/>
</Player>
</Root>
the wanted correctly-sorted result is produced:
<Root>
<Player name="John Doe">
<Location distance="50"/>
<Location distance="20"/>
</Player>
<Player name="Jane Doe">
<Location distance="90"/>
<Location distance="45"/>
</Player>
</Root>
Upvotes: 2
Reputation: 167516
Using XSLT 2.0 (and an XSLT 2.0 processor like Saxon 9) you can use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Root">
<xsl:copy>
<xsl:apply-templates select="Player">
<xsl:sort select="min(Location/@distance)"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Here is an XSLT 1.0 solution using exsl:node-set
to process a result tree fragment created in a variable where the minimum distance has been identified using the usual XSLT 1.0 approach of sorting and taking the first item in sort order:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:output indent="yes"/>
<xsl:variable name="players-rtf">
<xsl:for-each select="Root/Player">
<xsl:copy>
<xsl:attribute name="min-dist">
<xsl:for-each select="Location/@distance">
<xsl:sort select="." data-type="number"/>
<xsl:if test="position() = 1">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
<xsl:copy-of select="@* | *"/>
</xsl:copy>
</xsl:for-each>
</xsl:variable>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Root">
<xsl:copy>
<xsl:apply-templates select="exsl:node-set($players-rtf)/Player">
<xsl:sort select="@min-dist" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="Player/@min-dist"/>
</xsl:stylesheet>
Upvotes: 0