I'd like to define an ordering on strings used in a certain element. For example, <class>Senior</class> <class>Junior</class> <class>Sophomore</class> <class>Freshman<class>
would describe a reasonable ordering on class.
Is there a way using <xsl:sort select='class'> that would sort by the ordering given above?
Thanks in advance.
Have you looked into Saxon's custom collation extensions?
For example,
<xsl:variable name="rules" select="'< Freshman < Sophomore
< Junior < Senior'" />
This uses the RuleBasedCollator format from the Java class of that name.
To use it in your sort (piggybacking on Tim C's useful example input XML and stylesheet):
<xsl:apply-templates select="object">
<xsl:sort select="@class"
collation="http://saxon.sf.net/collation?rules={encode-for-uri($rules)}"/>
</xsl:apply-templates>
This gives the same results as Tim C's solution. (Tested using Saxon PE 9.3.0.5.)
It's not an xsl:function, but it gives you a little more flexibility than an array, and is arguably more succinct. AFAICT there is no way to create a custom collation using an XSLT user-defined function. Since you don't say why you want an xsl:function
, it's hard to speculate on what alternatives will meet your needs.
For complete xsl:function-like flexibility, you could define your own collator in Java; see http://www.saxonica.com/documentation/extensibility/collation.xml on implementing the java.util.Comparator interface and specifying your comparator in the class
attribute.
What you could do in your XSLT is define a variable to represent your custom ordering, like so
<xsl:variable name="inline-array">
<class sort="1">Senior</class>
<class sort="2">Junior</class>
<class sort="3">Sophomore</class>
<class sort="4">Freshman</class>
</xsl:variable>
Then to access this 'array' you can define another variable to reference the XSLT document itself:
<xsl:variable name="array"
select="document('')/*/xsl:variable[@name='inline-array']/*" />
This now allows you to look up the sort attribute for a given class name when you are sorting (where current() represents the current node being sorted)
<xsl:sort select="$array[. = current()/@class]/@sort" />
As an example, here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="inline-array">
<class sort="1">Senior</class>
<class sort="2">Junior</class>
<class sort="3">Sophomore</class>
<class sort="4">Freshman</class>
</xsl:variable>
<xsl:variable name="array"
select="document('')/*/xsl:variable[@name='inline-array']/*"/>
<xsl:template match="/objects">
<xsl:copy>
<xsl:apply-templates select="object">
<xsl:sort select="$array[. = current()/@class]/@sort" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When you apply this to the following sample XML...
<objects>
<object id="2" name="Junior Jo" class="Junior" />
<object id="1" name="Senior Sue" class="Senior" />
<object id="4" name="Freshman Frank" class="Freshman" />
<object id="3" name="Sophie Sophomore" class="Sophomore" />
</objects>
The following is returned
<objects>
<object id="1" name="Senior Sue" class="Senior"></object>
<object id="2" name="Junior Jo" class="Junior"></object>
<object id="3" name="Sophie Sophomore" class="Sophomore"></object>
<object id="4" name="Freshman Frank" class="Freshman"></object>
</objects>