34

I'm reasonably new to xlst and am confused as to whether there is any way to store a value and change it later, for example incrementing a variable in a loop.

I'm a bit baffled by not being able to change the value of a after it's set doesn't make sense to me, making it more of a constant.

For example I want to do something like this:

<xsl:variable name="i" select="0" />
<xsl:for-each select="data/posts/entry">
    <xsl:variable name="i" select="$i + 1" />
    <!-- DO SOMETHING -->
</xsl:for-each>

If anyone can enlighten me on whether there is an alternative way to do this
Thanks

DonutReply
  • 3,184
  • 6
  • 31
  • 34
  • 1
    Thanks Dimitre, I didn't realise variables could be reused in a for-each loop. My problem was actually a lot more complicated than the example I posted and I found a solution using recursion, however I'll look into a more elegant solution using your suggestion – DonutReply Jul 29 '10 at 12:14
  • 1
    @Oliver While recursion is something universal, there are ways to replace recursion with iteration. This results in optimized -- both for time and space -- xslt applications. – Dimitre Novatchev Jul 29 '10 at 12:40
  • They should have call it constant and not variable, the name is misleading. – Guillaume.P Mar 16 '21 at 14:59

5 Answers5

38

XSLT is a functional language and among other things this means that variables in XSLT are immutable and once they have been defined their value cannot be changed.

Here is how the same effect can be achieved in XSLT:

<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:template match="/">
   <posts>
    <xsl:for-each select="data/posts/entry">
        <xsl:variable name="i" select="position()" />
        <xsl:copy>
         <xsl:value-of select="concat('$i = ', $i)"/>
        </xsl:copy>
    </xsl:for-each>
   </posts>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the following XML document:

<data>
 <posts>
  <entry/>
  <entry/>
  <entry/>
  <entry/>
  <entry/>
 </posts>
</data>

the result is:

<posts>
    <entry>$i = 1</entry>
    <entry>$i = 2</entry>
    <entry>$i = 3</entry>
    <entry>$i = 4</entry>
    <entry>$i = 5</entry>
</posts>
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
7

You can use the position() function:

<xsl:for-each select="data/posts/entry">
  <xsl:text>
    Postion: '
  </xsl:text>
  <xsl:value-of select = "position()" />
  <xsl:text>
    '
  </xsl:text>
  <!-- DO SOMETHING -->
</xsl:for-each>
JohnB
  • 18,046
  • 16
  • 98
  • 110
1

I ran into that myself two years ago. You need to do use recursion for this. I forget the exact syntax, but this site might help:

Tip: Loop with recursion in XSLT

The strategy works basically as follows: Replace for loop with a template "method". Have it recieve a parameter i. Do the body of the for loop in the template method. If i > 0 call the template method again (recursion) with i - 1 as parameter.

Pseudocode:

for i = 0 to 10:
   print i

becomes:

def printer(i):
   print i
   if i < 10:
      printer(i + 1)
printer(0)

Please note that using position() in a xsl:for-each (see other answers) can be simpler if all you want to do is have a variable increment. Use the kind of recursion explained here if you want a more complicated loop / condition.

Buhake Sindi
  • 87,898
  • 29
  • 167
  • 228
Daren Thomas
  • 67,947
  • 40
  • 154
  • 200
1

Another approach (IE specific) we can follow is to take help of Javascript using "msxsl:script"

<msxsl:script implements-prefix='yourprefix' language='JavaScript'>  
   <![CDATA[var counter=1; function getCounter(){return counter++;}]]>
</msxsl:script>  

Then we can call this method in our xsl

<xsl:value-of select="yourprefix:getCounter()" />
Suvi
  • 66
  • 5
0

One way to achieve this is if your Transformer supports Java extensions, you can call a static method to increment a variable that it holds internally. Note that since this is a static method, you should maintain a Map of Sessions and make sure your static method keeps a seperate tally for each Transformer instance:

  <xsl:variable name="sessionId" select="java:initXsltSession()"/>
  <xsl:template match="/">

...

 <xsl:variable name="tally" select="java:getTally($sessionId, field[6])" />
Black
  • 5,023
  • 6
  • 63
  • 92