how to xsl sort ascending with nulls last
xml, xslt
Solution
<xsl:template match="/">
<xsl:for-each select="b/c">
<xsl:sort select="concat(
substring('1', 1, boolean(text())),
substring('0', 1, not(boolean(text())))
)" />
<xsl:sort select="." data-type="number"/>
<xsl:text>Row</xsl:text>
<xsl:value-of select="position()"/>
<xsl:text>:</xsl:text>
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
This:
concat(
substring('1', 1, boolean(text()) ),
substring('0', 1, not(boolean(text())))
)
Produces either '0' or '1', depending on whether there is a child text node, or not. It is the concatenation of two mutually exclusive strings - the poor man's if/then/else in XPath 1.0.
The `boolean(text())` produces `true` or `false`, which is then converted to a number for `substring()`. Boolean values are converted to 1 or 0, respectively.
The more complete version of the above goes like this:
concat(
substring(
$if_str,
1,
boolean($condition) * string-length($if_str)
),
substring(
$else_str,
1,
not(boolean($condition)) * string-length($else_str)
)
)
Problem
I want to sort these elements in ascending order but with the nulls last rather than first which seems to be what xslt does by default. I've managed to do this but wonder if there is a better way. Here's my example. ``` <b> <c>2</c> <c>1</c> <c>3</c> <c></c> <c>15</c> <c>11</c> <c></c> <c>43</c> <c>4</c> </b> <xsl:template match="/"> <xsl:for-each select="b/c"> <xsl:sort select="node() = false()"/> <xsl:sort select="." data-type="number"/> Row<xsl:value-of select="position()"/>:<xsl:value-of select="."/> </xsl:for-each> </xsl:template> ``` Which gives the required output of: ``` Row1:1 Row2:2 Row3:3 Row4:4 Row5:11 Row6:15 Row7:43 Row8: Row9: ``` I'm currently using `<xsl:sort select="node() = false()"/>` to test for it being null then using a sort to order the null elements last (null will be 1 and not null will be 0 so it orders them correctly). Can anyone suggest anything better than this?