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
XSLT is a functional language, not a procedural language, so you can't declare a counter. You can use xsl:number to get the position of the current node in its parent, if that helps. You can coerce a string to a number by using the XPath number() function.
Variables in XSLT are not really variables, as their values cannot be changed. They resemble constants from conventional programming languages. The only way in which a variable can be changed is by declaring it inside a for-each loop, in which case its value will be updated for every iteration.
Definition and Usage. The <xsl:number> element is used to determine the integer position of the current node in the source. It is also used to format a number.
You can format your XSLT stylesheet to go to a specific node, and then loop through the given node set. You create an XSLT loop with the <xsl:for-each> tag. The value of the select attribute in this tag is an XPath expression that allows you to specify the data element to loop through.
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>
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>
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.
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()" />
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With