XSLT xsl:sort issue
I have an XML file that I need to sort. Worked great until the dev consuming it told me to change the XML to items that I had with attributes of type=label to label nodes. Not great at XSLT. Need to sort on the 'sort' node.
The (simplified) XML looks like this:
<rss>
<channel>
<title>This is the title</title>
<link>http://www.mydomain.com/</link>
<description>The Description</description>
<label>
<title>Another Label</title>
<sort>4</sort>
</label>
<item>
<title>An Item</title>
<sort>2</sort>
</item>
<item>
<title>One Item</title>
<sort>3</sort>
</item>
<label>
<title>A Label</title>
<sort>1</sort>
</label>
</channel>
</rss>
The old XSL (when I was just sorting 'items') looks something like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="channel">
<rss>
<channel>
<xsl:copy-of select="title"/>
<xsl:copy-of select="link"/>
<xsl:copy-of select="description"/>
<xsl:apply-templates select="item">
<xsl:sort select="sort" data-type="number"/>
</xsl:apply-templates>
</channel>
</rss>
</xsl:template>
<xsl:template match="item">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
Tried this thinking it would work and it mostly does, but I get all sorts of "stragglers".
<xsl:stylesheet xmlns:xsl="http://ww开发者_StackOverfloww.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8" />
<xsl:template match="channel">
<rss>
<channel>
<xsl:copy-of select="title"/>
<xsl:copy-of select="link"/>
<xsl:copy-of select="description"/>
<xsl:apply-templates>
<xsl:sort select="sort"/>
</xsl:apply-templates>
</channel>
</rss>
</xsl:template>
<xsl:template match="item">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="label">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
The "stragglers" look like this when all is said and done using the latest XSL:
<rss xmlns:st="http://ww2.startribune.com/rss/modules/base/">
<channel>
<title>A Title</title>
<link>http://www.mydomain.com/</link>
<description>The Description</description>
A Title
http://www.mydomain.com/
The Description
<label>...
<item>...
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="channel">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()">
<xsl:sort select="sort" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<rss>
<channel>
<title>This is the title</title>
<link>http://www.mydomain.com/</link>
<description>The Description</description>
<label>
<title>A Label</title>
<sort>1</sort>
</label>
<item>
<title>An Item</title>
<sort>2</sort>
</item>
<item>
<title>One Item</title>
<sort>3</sort>
</item>
<label>
<title>Another Label</title>
<sort>4</sort>
</label>
</channel>
</rss>
Note: Identity rule. Sorting channel
childrens: NaN
values for a number sorting key come first. Althought this is normal behavior, it wasn't until XSLT 2.0 that this was explicitly defined, from http://www.w3.org/TR/xslt20/#comparing-sort-keys :
NaN values, for sorting purposes, are considered to be equal to each other, and less than any other numeric value.
EDIT: I wasn't sure, but after searching, this is also part of XSLT 1.0 spec in errata document:
in ascending order a NaN precedes all other numeric values and in descending order it follows them
XSLT provides a default matching template for nodes, where the textual content of the node is outputed.
In the XSLT you provide, you already processing elements title, link, and description in your channel template, you need to create empty templates to suck them up due to your apply-templates call in your channel template. For example:
<xsl:template match="title|link|description"/>
This should suck up your "stragglers".
To avoid having to 'hard-code' the names of specific 'stragglers', which would be otherwise picked up by the default matching template, you could add you own default template in this case, to simply ignore such nodes.
<xsl:template match="@*|node()">
<xsl:apply-templates />
</xsl:template>
This matches any non-specific node, and simply ignores it, and carries on processing the child nodes (so that when it matches the 'rss' node, it can then go on to match the 'channel' node). You specific template matches for 'channel', 'item' and 'label' will take priority over this.
Thus, if you take the following XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="channel">
<rss>
<channel>
<xsl:copy-of select="title"/>
<xsl:copy-of select="link"/>
<xsl:copy-of select="description"/>
<xsl:apply-templates>
<xsl:sort select="sort"/>
</xsl:apply-templates>
</channel>
</rss>
</xsl:template>
<xsl:template match="item">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="label">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
And apply it to your simplified XML, you should get the following output
<rss>
<channel>
<title>This is the title</title>
<link>http://www.mydomain.com/</link>
<description>The Description</description>
<label>
<title>A Label</title>
<sort>1</sort>
</label>
<item>
<title>An Item</title>
<sort>2</sort>
</item>
<item>
<title>One Item</title>
<sort>3</sort>
</item>
<label>
<title>Another Label</title>
<sort>4</sort>
</label>
</channel>
</rss>
精彩评论