tags:

views:

222

answers:

1

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?

+3  A: 
<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>&#10;</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)
  )
)
Tomalak
That's clever, I like it. I shall have to try and use it more often... ;-)
Chris R
What this does is convert a boolean value into a 1 or 0, so that you can sort on that value. But you *already can* sort on boolean values; they sort from false to true. If you just sort on `not(boolean(text()))` you'll get the non-empty elements first followed by the empty ones.
Robert Rossney
Interesting, I didn't think of that. Could be simplified to `boolean(text())`, sort order could then be controlled via ascending/descending.
Tomalak
That's similar to the way I'm already doing it with node() = false() to give me the null ones last.
Chris R
And so we have come full circle. :) Still I would go with `boolean(node())` rather than `node() = false()`, the former feels more idiomatic to me.
Tomalak