43

What is the best way to loop in XSLT from 1 to 60? I research in net, there are some templates to do this, is there any other way for example like a built-in function?

Jagger
  • 9,590
  • 7
  • 42
  • 78
Ianthe
  • 4,879
  • 18
  • 53
  • 70

5 Answers5

53

In XSLT 2.0,

<xsl:for-each select="1 to 60">...</xsl:for-each>

But I guess that you must be using XSLT 1.0, otherwise you wouldn't be asking.

In XSLT 1.0 you should use recursion: a template that calls itself with a counter that's incremented on each call, and the recursion terminates when the required value is reached.

Alternatively there's a workaround in XSLT 1.0: provided your source document contains at least 60 nodes, you can do

<xsl:for-each select="(//node())[60 >= position()]">...</xsl:for-each>
Michael Kay
  • 138,236
  • 10
  • 76
  • 143
28

The problem with simple recursion when processing long sequences is that often the space for the call stack becomes insufficient and the processing ends due to stack overflow. This typically happens with sequence length >= 1000.

A general technique to avoid this (implementable with any XSLT processor, even if it doesn't recognize tail-recursion) is DVC (Divide and Conquer) style recursion.

Here is an example of a transformation that successfully prints the numbers from 1 to 1000000 (1M):

<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output method="text"/>

     <xsl:template match="/">
      <xsl:call-template name="displayNumbers">
        <xsl:with-param name="pStart" select="1"/>
        <xsl:with-param name="pEnd" select="1000000"/>
      </xsl:call-template>
     </xsl:template>

     <xsl:template name="displayNumbers">
      <xsl:param name="pStart"/>
      <xsl:param name="pEnd"/>

      <xsl:if test="not($pStart > $pEnd)">
       <xsl:choose>
        <xsl:when test="$pStart = $pEnd">
          <xsl:value-of select="$pStart"/>
          <xsl:text>&#xA;</xsl:text>
        </xsl:when>
        <xsl:otherwise>
          <xsl:variable name="vMid" select=
           "floor(($pStart + $pEnd) div 2)"/>
          <xsl:call-template name="displayNumbers">
           <xsl:with-param name="pStart" select="$pStart"/>
           <xsl:with-param name="pEnd" select="$vMid"/>
          </xsl:call-template>
          <xsl:call-template name="displayNumbers">
           <xsl:with-param name="pStart" select="$vMid+1"/>
           <xsl:with-param name="pEnd" select="$pEnd"/>
          </xsl:call-template>
        </xsl:otherwise>
       </xsl:choose>
      </xsl:if>
     </xsl:template>
</xsl:stylesheet>

When applied on any XML document (not used) this transformation produces the wanted result -- all the numbers from 1 to 1000000.

You can use/adapt this transformation for any task that needs to "do something N times".

Dimitre Novatchev
  • 230,371
  • 26
  • 281
  • 409
  • 1
    You are the XSLT-Guru! I had to downgrade my XSLT 2.0 template and your answer was a life saviour! – Jagger Jan 08 '14 at 15:18
2

Very simple check inside the foreach-loop

<xsl:if test="$maxItems > position()">
    do something
</xsl:if>

Based on Dimitre Novatchev's answer.

Example:

<xsl:variable name="maxItems" select="10" />
<xsl:variable name="sequence" select="any-sequence"/>

<xsl:for-each select="$sequence">

    <!-- Maybe sort first -->
    <xsl:sort select="@sort-by" order="descending" />

    <!-- where the magic happens -->
    <xsl:if test="$maxItems > position()">
        do something
    </xsl:if>
</xsl:for-each>
Community
  • 1
  • 1
Aart den Braber
  • 804
  • 1
  • 11
  • 21
1

The basic example for V1.0 using recursion would it be like this:

<xsl:template match="/">
<Root>
      <!-- Main Call to MyTemplate -->
     <xsl:call-template name="MyTemplate" />
</Root>
</xsl:template>

<xsl:template name="MyTemplate">
  <xsl:param name="index" select="1" />
  <xsl:param name="maxValue" select="60" />

  <MyCodeHere>
     <xsl:value-of select="$index"/>
  </MyCodeHere>

  <!-- &lt; represents "<" for html entities -->
  <xsl:if test="$index &lt; $maxValue">
    <xsl:call-template name="MyTemplate">
        <xsl:with-param name="index" select="$index + 1" />
        <xsl:with-param name="total" select="$maxValue" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>
David Castro
  • 1,197
  • 13
  • 14
0

XSLT works based on templates and you'll need a template do run that loop.

You'll need to build a template receiving start and end values and, inside it, make a recursive call computing with start + 1. When $start equals $end, you do return your template, without another call.

In practice: http://www.ibm.com/developerworks/xml/library/x-tiploop/index.html

Rubens Farias
  • 54,126
  • 8
  • 125
  • 158