tags:

views:

54

answers:

3

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.

+2  A: 

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.

tdammers
+1 @tdammers - it would be helpful to provide a short example of the use of recursion and `call-template` so that @Burt can understand how to do it.
Mads Hansen
+1  A: 

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"&gt;
    <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"&gt;
    <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.

Alejandro
+2  A: 

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>&#xA;- - - - - - - - - - -&#xA;</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.

Dimitre Novatchev
@Dimitre: +1 for `scanl` reference from Functional Paradigm.
Alejandro
Also, XSLT 2.1 WD propose an interation with params!
Alejandro