Is it possible in XSLT to sort in alphabetical order, with 5 items as \"preferred\".
i.e. given
Have you seen xsl:sort element, you can create two or more sorting criteria by combining xsl:apply-templates and xsl:sort
Indeeed, here is a sample where some countries comes first, in a predefined order, then all the others. Note that these are elements, not attributes as above.
<xsl:sort select="not(CountryValue = 'Scotland')"/>
<xsl:sort select="not(CountryValue = 'India')"/>
<xsl:sort select="CountryValue"/>
Sample result:
Country
Scotland
Scotland
Scotland
India
Afghanistan
Afghanistan
Afghanistan
Afghanistan
Afghanistan
Afghanistan
Albania
Albania
Albania
Algeria
In your XSLT, are you able to make use of extension functions?
If so, one method is to modify the existing list of nodes in-line, to create a new node set but with an extra 'sortname' attribute on each node. You can then iterate through this new node set, sorting using the new 'sortname' attribute:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" />
<xsl:template match="/teams">
<xsl:variable name="extendedteams">
<xsl:for-each select="team">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:attribute name="sortname">
<xsl:choose>
<xsl:when test="@name='England' or @name='Northern Ireland' or @name='Republic of Ireland' or @name='Scotland' or @name='Wales'">1</xsl:when>
<xsl:otherwise>2</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="@name" />
</xsl:attribute>
</xsl:copy>
</xsl:for-each>
</xsl:variable>
<xsl:copy>
<xsl:for-each select="exsl:node-set($extendedteams)/team">
<xsl:sort select="@sortname" />
<xsl:copy>
<xsl:copy-of select="@*[name() != 'sortname']" />
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In the example above, I prefix a '1' onto any national team, and a '2' on any domestic team, and then sort on this new attribute.
See information on Node Sets to see what XSLT processers support which extensions.
You could do this:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://tempuri.org"
exclude-result-prefixes="my"
>
<xsl:output method="xml" indent="yes" />
<my:data>
<my:nationalteams>
<my:team id="121" /><!-- Republic of Ireland -->
<my:team id="123" /><!-- England -->
<my:team id="126" /><!-- Northern Ireland -->
<my:team id="142" /><!-- Scotland -->
<my:team id="295" /><!-- Wales -->
</my:nationalteams>
</my:data>
<xsl:template match="teams">
<xsl:copy>
<xsl:variable name="national" select="
document('')/*/my:data/my:nationalteams/my:team
" />
<!-- national teams preferred -->
<xsl:apply-templates select="team[@id = $national/@id]">
<xsl:sort select="@name" />
</xsl:apply-templates>
<!-- other teams after them -->
<xsl:apply-templates select="team[not(@id = $national/@id)]">
<xsl:sort select="@name" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="team">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
The whole <my:data>
could be moved to a secondary XML/config file, where you can also leave off the "my" namespace.
After that, one line would need a small change:
<xsl:variable name="national" select="
document('config.xml')/data/nationalteams/team
" />
The output of the above is somewhat unsurprising :-)
<teams>
<team id="123" name="England" />
<team id="126" name="Northern Ireland" />
<team id="121" name="Republic of Ireland" />
<team id="142" name="Scotland" />
<team id="295" name="Wales" />
<team id="49" name="Arsenal" />
<team id="299" name="Bolton" />
<team id="84" name="Chelsea" />
<team id="110" name="Liverpool" />
<team id="42" name="Manchester City" />
<team id="13" name="Manchester United" />
<team id="298" name="Tottenham Hotspur" />
</teams>
Tim C has already given a nice answer, but maybe a simpler way will suffice in your case. You could simply specify two xsl:sort
conditions: The first will sort by preferred/not-preferred items, the second one alphabetically by name:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/teams">
<teams>
<xsl:for-each select="team">
<xsl:sort select="not(@name = 'England' or @name='Northern Ireland'
or @name='Republic of Ireland'
or @name='Scotland' or @name='Wales')"
data-type="number"/>
<xsl:sort select="@name"/>
<team>
<xsl:value-of select="@name"/>
</team>
</xsl:for-each>
</teams>
</xsl:template>
</xsl:stylesheet>
Please note that you have to invert the first condition using not()
. The reason is that the boolean result of your expression is converted to a number (0 is false, 1 is true) and hence the items evaluating to 'false' will be listed first.