Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting a nodeset before passing to xsl:for-each

Tags:

sorting

xslt

I have a situation where loop through a sorted nodeset and apply a template on each of the nodes:

<div id="contractscontainer">
  <xsl:for-each select="document">
    <xsl:sort select="content[@name='ClientName']/text()" />
    <xsl:apply-templates select="." mode="client-contract" />
  </xsl:for-each>
</div>

I want to do something special with the "first" 5 nodes in the node set and render them nested element. The problem is that they need to be in the same order as if they were sorted (as they are in the loop).

I had planned on doing this by using two xsl:for-each elements, each with the correct nodes selected from the set. I can't do this, however, because they need to be sorted before I can select the "first" 5.

Example:

<div id="contractscontainer">
  <div class="first-five">
    <xsl:for-each select="document[position() < 6]">
      <xsl:sort select="content[@name='ClientName']/text()" />
      <xsl:apply-templates select="." mode="client-contract" />
    </xsl:for-each>
  </div>
  <div class="rest-of-them">
    <xsl:for-each select="document[position() > 5]">
      <xsl:sort select="content[@name='ClientName']/text()" />
      <xsl:apply-templates select="." mode="client-contract" />
    </xsl:for-each>
  </div>
</div>

I don't think this will work because I'm selecting the nodes by position before sorting them, but I can't use xsl:sort outside of the xsl:for-each.

Am I approaching this incorrectly?

Edit: My current solution is to sort them and store the sorted set in another variable:

<xsl:variable name="sorted-docs">
  <xsl:for-each select="document">
    <xsl:sort select="content[@name='ClientName']/text()" />
    <xsl:copy-of select="." />
  </xsl:for-each>
</xsl:variable>

It works, but is there a better way?

like image 658
Zack The Human Avatar asked Apr 07 '10 20:04

Zack The Human


1 Answers

Here is one way to do this in XSLT 1.0 without the need to use the xxx:node-set() extension function:

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

 <xsl:template match="/*">
  <html>
   <div class="first-five">
     <xsl:apply-templates select="num">
       <xsl:sort select="." data-type="number"/>
       <xsl:with-param name="pStart" select="1"/>
       <xsl:with-param name="pEnd" select="5"/>
     </xsl:apply-templates>
   </div>
   <div class="rest-of-them">
     <xsl:apply-templates select="num">
       <xsl:sort select="." data-type="number"/>
       <xsl:with-param name="pStart" select="6"/>
     </xsl:apply-templates>
   </div>
  </html>
 </xsl:template>

 <xsl:template match="num">
  <xsl:param name="pStart"/>
  <xsl:param name="pEnd" select="999999999999"/>

  <xsl:if test="position() >= $pStart
              and
                not(position() > $pEnd)">
   <p><xsl:value-of select="."/></p>
  </xsl:if>
 </xsl:template>

</xsl:stylesheet>

When the above transformation is applied on this simple XML document:

<nums>
  <num>5</num>
  <num>3</num>
  <num>6</num>
  <num>8</num>
  <num>4</num>
  <num>1</num>
  <num>9</num>
  <num>2</num>
  <num>7</num>
  <num>10</num>
</nums>

the wanted result is produced:

<html>
    <div class="first-five">
        <p>1</p>
        <p>2</p>
        <p>3</p>
        <p>4</p>
        <p>5</p>
    </div>
    <div class="rest-of-them">
        <p>6</p>
        <p>7</p>
        <p>8</p>
        <p>9</p>
        <p>10</p>
    </div>
</html>
like image 184
Dimitre Novatchev Avatar answered Oct 16 '22 11:10

Dimitre Novatchev