Christopher Schultz
Christopher Schultz

Reputation: 20862

How can I sort nodes by a numerical value where not all nodes have that value?

I have an XML document like this:

<?xml version="1.0" ?>
<root>
  <element id="1"><name>Red</name><rank value="1" /></element>
  <element id="2"><name>Orange</name><rank value="4" /></element>
  <element id="3"><name>Yellow</name><rank value="3" /></element>
  <element id="4"><name>Green</name><rank value="2" /></element>
  <element id="5"><name>Blue</name><rank value="5" /></element>
  <element id="6"><name>Indigo</name></element>
  <element id="6"><name>Violet</name></element>
</root>

I'd like to sort by numeric rank and have the un-ranked elements float to the bottom, like this:

The order of Indigo/Violet is not relevant to me.

I tried this:

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/root">
    <xsl:apply-templates select="element">
      <xsl:sort select="rank/@value" data-type="number" order="ascending" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="element">
    <xsl:text>* </xsl:text><xsl:apply-templates select="name" /><xsl:text>
</xsl:text>
  </xsl:template>
</xsl:stylesheet>

But my output looks like this:

All of the ranked items are in fact in order, but they are at the bottom of the list.

Upvotes: 1

Views: 83

Answers (3)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243479

  <xsl:template match="/*">
    <xsl:apply-templates select="element">
      <xsl:sort select="not(rank/@value)"/>
      <xsl:sort select="rank/@value" data-type="number"/>
    </xsl:apply-templates>
  </xsl:template>

Upvotes: 0

michael.hor257k
michael.hor257k

Reputation: 116993

I would suggest a more general approach:

<xsl:template match="/root">
    <xsl:apply-templates select="element">
        <xsl:sort select="number(boolean(rank/@value))" data-type="number" order="descending" />
        <xsl:sort select="rank/@value" data-type="number" order="ascending" />
    </xsl:apply-templates>
</xsl:template>

See explanation: https://stackoverflow.com/a/34290573/3016153

Upvotes: 1

Christopher Schultz
Christopher Schultz

Reputation: 20862

Answering my own question after a bit of tinkering.

Since I'm using a numeric sort criteria, I can sort descending instead of ascending, and then flipping the sign of the numeric argument being sorted:

<xsl:apply-templates select="element">
  <xsl:sort select="-(rank/@value)" data-type="number" order="descending" />
</xsl:apply-templates>

This gives the desired order:

  • Red
  • Green
  • Yellow
  • Orange
  • Blue
  • Indigo
  • Violet

Upvotes: 1

Related Questions