I need to keep a count of items that match a condition while looping through a node list using a for-each.
I was using position() but this is incorrect as I need to keep a running total.
Any help would be really appreciated.
I need to keep a count of items that match a condition while looping through a node list using a for-each.
I was using position() but this is incorrect as I need to keep a running total.
Any help would be really appreciated.
I think you'll have to rewrite the loop into a recursion. Then you can pass all sorts of variables into the next iteration as arguments.
Two alternatives. With this input:
<root>
<number>1</number>
<number>2</number>
<number>3</number>
<number>4</number>
<number>5</number>
</root>
This stylesheets:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<result>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="number">
<sum>
<xsl:value-of select="sum(.|preceding-sibling::number)"/>
</sum>
</xsl:template>
</xsl:stylesheet>
And
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<result>
<xsl:apply-templates select="*[1]"/>
</result>
</xsl:template>
<xsl:template match="number">
<xsl:param name="pCount" select="0"/>
<sum>
<xsl:value-of select="$pCount + ."/>
</sum>
<xsl:apply-templates select="following-sibling::*[1]">
<xsl:with-param name="pCount" select="$pCount + ."/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
Output:
<result>
<sum>1</sum>
<sum>3</sum>
<sum>6</sum>
<sum>10</sum>
<sum>15</sum>
</result>
Note: First one with 'preceding' axis. Second one passing param in a node by node processing.
I need to keep a count of items that match a condition while looping through a node list using a for-each.
I was using position() but this is incorrect as I need to keep a running total.
Any help would be really appreciated.
In XSLT this can generally be done using recursion.
FXSL offers an easy way to produce a snapshot of the steps of a computation.
Use the scanl
or scanl1
template / function of FXSL
In XSLT 1.0 this transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:myAdd="f:myAdd"
xmlns:myParam="f:myParam"
>
<xsl:import href="scanl.xsl"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<!-- To be applied on: numList.xml -->
<myAdd:myAdd/>
<myParam:myParam>0</myParam:myParam>
<xsl:template match="/">
<xsl:variable name="vFun" select="document('')/*/myAdd:*[1]"/>
<xsl:variable name="vZero" select="document('')/*/myParam:*[1]"/>
<xsl:call-template name="scanl">
<xsl:with-param name="pFun" select="$vFun"/>
<xsl:with-param name="pQ0" select="$vZero" />
<xsl:with-param name="pList" select="/*/num"/>
</xsl:call-template>
- - - - - - - - - - -
<xsl:call-template name="scanl1">
<xsl:with-param name="pFun" select="$vFun"/>
<xsl:with-param name="pList" select="/*/num"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="myAdd:*" mode="f:FXSL">
<xsl:param name="pArg1" select="0"/>
<xsl:param name="pArg2" select="0"/>
<xsl:value-of select="$pArg1 + $pArg2"/>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
produces the running sums (the intermediate results of a sum()
computation):
<el>0</el>
<el>1</el>
<el>3</el>
<el>6</el>
<el>10</el>
<el>15</el>
<el>21</el>
<el>28</el>
<el>36</el>
<el>45</el>
<el>55</el>
- - - - - - - - - - -
<el>01</el>
<el>3</el>
<el>6</el>
<el>10</el>
<el>15</el>
<el>21</el>
<el>28</el>
<el>36</el>
<el>45</el>
<el>55</el>
The corresponding XSLT 2.0 transformation, producing the same result, is this:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
exclude-result-prefixes="f">
<xsl:import href="../f/func-scanl.xsl"/>
<xsl:import href="../f/func-Operators.xsl"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:value-of separator=", " select="f:scanl(f:add(), 0, /*/*)"/>
<xsl:text>
- - - - - - - - - - -
</xsl:text>
<xsl:value-of separator=", " select="f:scanl1(f:add(), /*/*)"/>
</xsl:template>
</xsl:stylesheet>
You can pass as parameter to f:scanl()
any function with two arguments, the first of which is the currently accumulated result and the second is the current item of the sequence, passed to f:scanl()
as its last argument. The second argument to f:scanl()
is the initial accumulator value. Typically, it will be 0
for summation and 1
for computing a product.
f:scanl1()
is almost the same as f:scan()
but it requires its sequence argument to be non-empty and uses its first item as the initial accumulator value.