Luffy
Luffy

Reputation: 5

XSL - Sum of computed amounts

I just need some help on basic feature of XSL. I would like to display a sum of amounts previously computed. But I do not know how to do it. For information the XSL must work with XSLT 1.0, technical limitation on my side.

For instance here is my xml.

<A>
   <amount>10</amount>
   <rate>4</rate>
</A>
<A>
   <amount>-21</amount>
   <rate>2</rate>
</A>
<B>
   <amount>8</amount>
   <rate>1</rate>
</B>
<C>
   <amount>7</amount>
   <rate>32</rate>
</C>

and I would like to display the sum of each amount multiplied by each associated rate within a Total element.

<Total value="230">
    <PositiveTotal>
        272
    </PositiveTotal>
    <NegativeTotal>
        -42
    </NegativeTotal>
</Total>

I have no idea how to do it.

Thanks in advance

Regards,

Upvotes: 0

Views: 664

Answers (3)

michael.hor257k
michael.hor257k

Reputation: 116959

I would suggest you do it this way:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/root">
    <!-- first pass -->
    <xsl:variable name="summands-rtf">
        <xsl:for-each select="*">
            <value>
                <xsl:value-of select="amount * rate" />
            </value>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="summands" select="exsl:node-set($summands-rtf)/value" />
    <!-- output -->
    <Total value="{sum($summands)}">
        <PositiveTotal>
            <xsl:value-of select="sum($summands[. > 0])" />
        </PositiveTotal>
        <NegativeTotal>
            <xsl:value-of select="sum($summands[. &lt; 0])" />
        </NegativeTotal>
    </Total>
</xsl:template>

</xsl:stylesheet>

When applied to a well-formed XML input (with a single root element):

XML

<root>
  <A>
    <amount>10</amount>
    <rate>4</rate>
  </A>
  <A>
    <amount>-21</amount>
    <rate>2</rate>
  </A>
  <B>
    <amount>8</amount>
    <rate>1</rate>
  </B>
  <C>
    <amount>7</amount>
    <rate>32</rate>
  </C>
</root>

the result will be:

<?xml version="1.0" encoding="UTF-8"?>
<Total value="230">
   <PositiveTotal>272</PositiveTotal>
   <NegativeTotal>-42</NegativeTotal>
</Total>

Upvotes: 0

Michael Kay
Michael Kay

Reputation: 163262

This question has been asked many times and following the links to similar searches on SO should give you lots of ideas.

Computing the sum of computed values in XSLT 2.0 is trivial, but in XSLT 1.0 it isn't easy because there's no such data type in its data model as a set of numbers (sum() only works over a set of nodes). Possible solutions include:

(a) a recursive template call which supplies the running total as a parameter to the template, adds the next value, then calls the template again to process the rest of the list with the new running total

(b) a multiphase transformation where the computed values are placed in XML nodes during one phase, and summed using the sum() function in a second phase

(c) use of the FXSL library which uses xsl:apply-templates to simulate higher-order functions and then provides a fold mechanism which can be specialised to implement summation.

(d) calling out to extension functions in a procedural programming language

(e) upgrading to XSLT 2.0.

Upvotes: 0

uL1
uL1

Reputation: 2167

One of possible multiple solutions. It will give you an idea, how to solve this.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="root">
        <xsl:variable name="positiveTotal">
            <xsl:call-template name="sum">
                <xsl:with-param name="items" select="*[not(starts-with(amount,'-') or starts-with(rate, '-'))]"/>
            </xsl:call-template>
        </xsl:variable>

        <xsl:variable name="negativTotal">
            <xsl:call-template name="sum">
                <xsl:with-param name="items" select="*[starts-with(amount,'-') or starts-with(rate, '-')]"/>                
            </xsl:call-template>
        </xsl:variable>

        <Total value="{$positiveTotal + $negativTotal}">
            <PositivTotal>
                <xsl:value-of select="format-number($positiveTotal, '0')"/>
            </PositivTotal>
            <NegativeTotal>
                <xsl:value-of select="format-number($negativTotal, '0')"/>
            </NegativeTotal>
        </Total>

    </xsl:template>

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

        <xsl:choose>
            <xsl:when test="not($items)">
                <xsl:value-of select="$total"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="sum">
                    <xsl:with-param name="items" select="$items[position() > 1]" />
                    <xsl:with-param name="total"
                        select="$total + ($items[1]/amount * $items[1]/rate)" />
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

!! Change match="root" to your root-node! Given source-xml is not valid.

There are already many sum-questions! See the Related Box on your right side of screen.

Upvotes: 1

Related Questions