Search code examples
xsltaggregate

How to get the grand total in an aggregate sum in XSLT?


First, I want to thank you for your time to help. I'm new to XSLT and am stuck for days trying to get the grand total of quantity. I am able to get the aggregate sum for each report_id by type and discard the negative aggregate sum (see report_id 222 and 444).

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <report>
        <report_id>111</report_id>
        <group>
            <type>A</type>
            <quantity>2</quantity>
        </group>
        <group>
            <type>B</type>
            <quantity>4</quantity>
        </group>
        <group>
            <type>A</type>
            <quantity>6</quantity>
        </group>
    </report>
    <report>
        <report_id>222</report_id>
        <group>
            <type>A</type>
            <quantity>-5</quantity>
        </group>
        <group>
            <type>A</type>
            <quantity>2</quantity>
        </group>
    </report>
    <report>
        <report_id>333</report_id>
        <group>
            <type>B</type>
            <quantity>7</quantity>
        </group>
    </report>
    
    <report>
        <report_id>444</report_id>
        <group>
            <type>B</type>
            <quantity>-12</quantity>
        </group>
    </report>
</root>

My xsl output is below, and the problem lies at the last value 0. 111 A=8; 111 B=4; 333 B=7; 0

The output should be this 111 A=8; 111 B=4; 333 B=7; 19

I tried call-template to do the $grand_qty and failed. I prefer XSLT 2.0 but version is find too. Thanks in advance.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="xsl"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
    <xsl:output method="text" />
    <xsl:template match="/root">
        <xsl:variable name="total_qty" select="0"/>
        <xsl:variable name="grand_qty" select="0"/>
        <xsl:for-each-group select="report/group" group-by="concat(../report_id, '|', type)">
            <xsl:variable name="qty" select="sum(current-group()/quantity)"/>
            <xsl:if test="$qty &gt; 0">
                <xsl:variable name="total_qty" select="$qty + $total_qty"/>
                <xsl:value-of select="concat(../report_id, ' ', type, '=', $total_qty, '; ')"/>
                <xsl:variable name="grand_qty" select="$grand_qty + $total_qty"/>
            </xsl:if>
        </xsl:for-each-group>
        <xsl:value-of select="$grand_qty"/>
    </xsl:template>
</xsl:stylesheet>

Solution

  • Variables in XSLT are immutable, and limited in scope to their parent element (or more precisely, to all following siblings and their descendants).

    Try a different approach:

    XSLT 2.0

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="UTF-8"/>
    
    <xsl:template match="/root">
        <xsl:variable name="groups">
            <xsl:for-each-group select="report/group" group-by="concat(../report_id, ' ', type)">
                <xsl:variable name="qty" select="sum(current-group()/quantity)"/>
                <xsl:if test="$qty gt 0">
                    <group name="{current-grouping-key()}">
                        <xsl:value-of select="$qty"/>
                    </group>    
                </xsl:if>
            </xsl:for-each-group>
        </xsl:variable>
        <xsl:value-of select="$groups/group/concat(@name, '=', ., '; ')" separator=""/>
        <xsl:value-of select="sum($groups/group)"/>
    </xsl:template>
    
    </xsl:stylesheet>