Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSLT to sum product of two attributes

Tags:

sum

xslt

xpath

I have the following XML source structure:

<turnovers>
    <turnover repid="1" amount="500" rate="0.1"/>
    <turnover repid="5" amount="600" rate="0.5"/>
    <turnover repid="4" amount="400" rate="0.2"/>
    <turnover repid="1" amount="700" rate="0.05"/>
    <turnover repid="2" amount="100" rate="0.15"/>
    <turnover repid="1" amount="900" rate="0.25"/>
    <turnover repid="2" amount="1000" rate="0.18"/>
    <turnover repid="5" amount="200" rate="0.55"/>
    <turnover repid="9" amount="700" rate="0.40"/>
</turnovers>

I need an XSL:value-of select statement that will return the sum of the product of the rate attribute and the amount attribute for a given rep ID. So for rep 5 I need ((600 x 0.5) + (200 x 0.55)).

like image 659
staterium Avatar asked Aug 26 '09 09:08

staterium


2 Answers

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:template match="/turnovers">
    <val>
      <!-- call the sum function (with the relevant nodes) -->
      <xsl:call-template name="sum">
        <xsl:with-param name="nodes" select="turnover[@repid='5']" />
      </xsl:call-template>
    </val>
  </xsl:template>

  <xsl:template name="sum">  
    <xsl:param name="nodes" />
    <xsl:param name="sum" select="0" />

    <xsl:variable name="curr" select="$nodes[1]" />

    <!-- if we have a node, calculate & recurse -->
    <xsl:if test="$curr">
      <xsl:variable name="runningsum" select="
        $sum + $curr/@amount * $curr/@rate
      " />
      <xsl:call-template name="sum">
        <xsl:with-param name="nodes" select="$nodes[position() &gt; 1]" />
        <xsl:with-param name="sum"   select="$runningsum" />
      </xsl:call-template>
    </xsl:if>

    <!-- if we don't have a node (last recursive step), return sum -->
    <xsl:if test="not($curr)">
      <xsl:value-of select="$sum" />
    </xsl:if>

  </xsl:template>
</xsl:stylesheet>

Gives:

<val>410</val>

The two <xsl:if>s can be replaced by a single <xsl:choose>. This would mean one less check during the recursion, but it also means two additional lines of code.

like image 72
Tomalak Avatar answered Sep 28 '22 04:09

Tomalak


In plain XSLT 1.0 you need a recursive template for this, for example:

  <xsl:template match="turnovers">
    <xsl:variable name="selectedId" select="5" />
    <xsl:call-template name="sum_turnover">
      <xsl:with-param name="turnovers" select="turnover[@repid=$selectedId]" />
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="sum_turnover">
    <xsl:param name="total" select="0" />
    <xsl:param name="turnovers"  />
    <xsl:variable name="head" select="$turnovers[1]" />
    <xsl:variable name="tail" select="$turnovers[position()>1]" />
    <xsl:variable name="calc" select="$head/@amount * $head/@rate" />
    <xsl:choose>
      <xsl:when test="not($tail)">
        <xsl:value-of select="$total + $calc" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name="sum_turnover">
          <xsl:with-param name="total" select="$total + $calc" />
          <xsl:with-param name="turnovers" select="$tail" />
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
like image 28
jor Avatar answered Sep 28 '22 05:09

jor