Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I change the order of an xsl for-each?

Tags:

xslt

I need to change the order in which my for-each is executed only if some conditions are met.

Here is what my XML looks like :

<OptionList>
    <Option name="My First Option" />
    <Option name="My Second Option" />
</OptionList>

However, in some case, my XML can be like this :

<OptionList>
    <Option />
    <Option name="My Second Option" />
</OptionList>

In my XSL, I'm doing a for-each like this :

<xsl:for-each select="//OptionList/Option">
    {...}
</xsl:for-each>

I know I can change orders of Option nodes using this line in my for-each :

<xsl:sort select="position()" data-type="number" order="descending" />

The problem is that I want my order to be descending only when my first Option node is empty and doesn't have the name attribute. Otherwise, I want to keep the default ascending order.

Any input on how I can acheive that? So far, everything I tried ended up with "Variable out of scope" or invalid use of xpath functions.

like image 958
Gabriel Avatar asked Aug 04 '10 18:08

Gabriel


2 Answers

You can use a hack to change the sort order based on a condition:

<?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="/">
    <result>
    <xsl:for-each select="//OptionList/Option">
      <xsl:sort data-type="number" order="ascending"
        select="position()*(-2*number(not(//OptionList/Option[1]/@name))+1)"/>

      <option>
        <xsl:value-of select="@name"/>
      </option>
    </xsl:for-each>
    </result>

  </xsl:template>
</xsl:stylesheet>

The hack is that number((true()) returns 1 and number(false()) returns 0. As a consequence, the expression

-2 * number(not(//OptionList/Option[1]/@name)) + 1

evaluates to 1 if the first option element has a name attribute and to -1 otherwise. This is used as a factor to reverse the sort order.

like image 89
Dirk Vollmar Avatar answered Oct 07 '22 21:10

Dirk Vollmar


The order of producing the wanted output can be easily specified with <xsl:sort>:

This is natural and easy and involves almost no hacks.

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:variable name="vOrder" select=
   "2*boolean(/*/Option[1]/@name)-1"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="/*">
   <xsl:copy>
     <xsl:for-each select="Option">
       <xsl:sort data-type="number" select="$vOrder* position()"/>

       <xsl:apply-templates select="."/>
     </xsl:for-each>
   </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

When this transformation is performed on the this XML document:

<OptionList>
    <Option name="My First Option" />
    <Option name="My Second Option" />
    <Option name="My Third Option" />
</OptionList>

the wanted, correct result is produced:

<OptionList>
   <Option name="My First Option"/>
   <Option name="My Second Option"/>
   <Option name="My Third Option"/>
</OptionList>

When the same transformation is now performed on this XML document:

<OptionList>
    <Option />
    <Option name="My Second Option" />
    <Option name="My Third Option" />
</OptionList>

the wanted, correct result is produced again:

<OptionList>
   <Option name="My Third Option"/>
   <Option name="My Second Option"/>
   <Option/>
</OptionList>

Explanation: The variable $vOrder is defined in such a way, that it is -1 iff the first Option element has no name attribute and it is +1 if the first Option element has a name attribute. Here we use the fact that false() is converted automatically to 0 and true() to 1.

We also use the fact that when the sign of each number in sequence of increasing positive numbers (the positions) is reversed, the order of the new sequence becomes decreasing.

like image 41
Dimitre Novatchev Avatar answered Oct 07 '22 22:10

Dimitre Novatchev