Reputation: 3
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 > 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>
Upvotes: 0
Views: 128
Reputation: 167706
As your code uses version="3.0"
I would guess XSLT 3 also works; there you have some ways to store values with maps or arrays, xsl:iterate
or fold-left; together with grouping it is possible but a bit convoluted to keep a total
parameter as follows:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:mf="http://example.com/mf"
expand-text="yes">
<xsl:function name="mf:group" as="map(*)*">
<xsl:param name="group-elements" as="element(group)*"/>
<xsl:for-each-group select="$group-elements" composite="yes" group-by="../report_id, type">
<xsl:variable name="qty" select="sum(current-group()/quantity)"/>
<xsl:sequence select="map { 'qty' : $qty, 'value' : current-grouping-key()[1] || ' ' || current-grouping-key()[2] || ' = ' || $qty }[$qty gt 0]"/>
</xsl:for-each-group>
</xsl:function>
<xsl:output method="text" item-separator="; "/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:iterate select="root/mf:group(report/group)">
<xsl:param name="total" select="0"/>
<xsl:on-completion>
<xsl:sequence select="$total"/>
</xsl:on-completion>
<xsl:sequence select="?value"/>
<xsl:next-iteration>
<xsl:with-param name="total" select="?qty + $total"/>
</xsl:next-iteration>
</xsl:iterate>
</xsl:template>
</xsl:stylesheet>
Alternative with fold-left
instead of xsl:iterate
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:mf="http://example.com/mf"
expand-text="yes">
<xsl:function name="mf:group" as="map(*)*">
<xsl:param name="group-elements" as="element(group)*"/>
<xsl:for-each-group select="$group-elements" composite="yes" group-by="../report_id, type">
<xsl:variable name="qty" select="sum(current-group()/quantity)"/>
<xsl:sequence select="map { 'qty' : $qty, 'value' : current-grouping-key()[1] || ' ' || current-grouping-key()[2] || ' = ' || $qty }[$qty gt 0]"/>
</xsl:for-each-group>
</xsl:function>
<xsl:output method="text" item-separator="; "/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:sequence
select="fold-left(
root!mf:group(report!group),
0,
function($a, $g) { $a[position() lt last()], $g?value, $a[last()] + $g?qty }
)"/>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0
Reputation: 117073
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>
Upvotes: 1