Home > Net >  Summing Variables Using For-each-group
Summing Variables Using For-each-group

Time:10-30

I have a list of nodes for which I need to compute a total then sum said total as a group. I have created the appropriate variables but when I use a sum() as my final output, it produces a concatenated string as opposed to a total value.

Here is the input XML:

<LineItems>
        <LineItem>
            <OrderLine>
                <OrderQty>1</OrderQty>
                <UnitPrice>105.28</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        <LineItem>
            <OrderLine>
                <OrderQty>75</OrderQty>
                <UnitPrice>2.88</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>3</OrderQty>
                <UnitPrice>155.36</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>12</OrderQty>
                <UnitPrice>1.64</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>2</OrderQty>
                <UnitPrice>2.28</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>5</OrderQty>
                <UnitPrice>3.6</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>1</OrderQty>
                <UnitPrice>405.24</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>5</OrderQty>
                <UnitPrice>79.04</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>1</OrderQty>
                <UnitPrice>2.15</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>9</OrderQty>
                <UnitPrice>2.15</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
    </LineItems>

And here is what I have been using thus far:

<tpi:Fact name="TotalLinePrice" factType="Virtual">
                <xsl:for-each-group select="/PurchaseOrder/LineItems/LineItem/OrderLine" group-starting-with="OrderLine[normalize-space(OrderQty)]">
                    <xsl:variable name="uPrice" as="node()*">
                    <xsl:for-each select="current-group()">
                        <xsl:variable name="unitPrice">
                            <xsl:copy-of select="xs:decimal(current-group()/UnitPrice)"/>
                        </xsl:variable>
                        <xsl:variable name="orderQty">
                            <xsl:copy-of select="xs:decimal(current-group()/OrderQty)"/>
                        </xsl:variable>
                        <xsl:variable name="calcPriceBasis">
                            <xsl:choose>
                            <xsl:when test="current-group()[UnitPriceBasis='TP']">
                                <xsl:copy-of select="round(($unitPrice div 1000))"/>
                            </xsl:when>
                            <xsl:when test="current-group()[UnitPriceBasis='HT']">
                                <xsl:copy-of select="round(($unitPrice div 1000))"/>
                            </xsl:when>
                            <xsl:when test="current-group()[UnitPriceBasis='HP']">
                                <xsl:copy-of select="round(($unitPrice div 100))"/>
                            </xsl:when>
                            <xsl:when test="current-group()[UnitPriceBasis='HTH']">
                                <xsl:copy-of select="round(($unitPrice div 100000))"/>
                            </xsl:when>
                            <xsl:when test="current-group()[UnitPriceBasis='PD']">
                                <xsl:copy-of select="round(($unitPrice div 12))"/>
                            </xsl:when>
                            <xsl:when test="current-group()[UnitPriceBasis='PN']">
                                <xsl:copy-of select="round(($unitPrice div 10))"/>
                            </xsl:when>
                            <xsl:when test="current-group()[UnitPriceBasis='TT']">
                                <xsl:copy-of select="round(($unitPrice div 10000))"/>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:copy-of select="$unitPrice"/>
                            </xsl:otherwise>
                            </xsl:choose>
                        </xsl:variable>
                        <total>
                            <xsl:sequence select="format-number($calcPriceBasis * $orderQty, '0.00')"/>
                        </total>
                    </xsl:for-each>
                    </xsl:variable>
                    <xsl:value-of select="sum($uPrice)"/>
                </xsl:for-each-group>
            </tpi:Fact>

The output I am getting is:

"TOTALLINEPRICE":"105.28216466.0819.684.5618405.24395.22.1519.35"

which is each node of this calculation

<xsl:sequence select="format-number($calcPriceBasis * $orderQty, '0.00')"/>

appended to itself as a concatenated string. But I would hope to see

"TOTALLINEPRICE": "1456.84"

which would be the sum total of each calculated value.

TOTALLINEPRICE is a new node created inside of the XML much like adding

<TOTALLINEPRICE>
</TOTALLINEPRICE>

As you can see, I need to compute a different price basis based on an input code (UnitPriceBasis) within each nodes itself. I know that xslt is not a programmatic language and variables are technically immutable. But there has to be a way to do this.

For reference, I am using:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:tpi="http://www.spscommerce.net/tpi">

Where have I gone wrong?

CodePudding user response:

Consider the following simplified example:

XML

<items>
    <item>
        <qty>2</qty>
        <price>10.00</price>
        <group>A</group>
    </item>
    <item>
        <qty>3</qty>
        <price>5.00</price>
        <group>A</group>
    </item>
    <item>
        <qty>4</qty>
        <price>8.00</price>
        <group>A</group>
    </item>
    <item>
        <qty>6</qty>
        <price>3.00</price>
        <group>B</group>
    </item>
    <item>
        <qty>3</qty>
        <price>9.00</price>
        <group>B</group>
    </item>
    <item>
        <qty>7</qty>
        <price>8.00</price>
        <group>C</group>
    </item>
 </items>

XSLT 2.0

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

<xsl:template match="/items">
    <xsl:variable name="subtotals">
        <xsl:for-each-group select="item" group-by="group">
            <subtotal group="{current-grouping-key()}">
                <xsl:value-of select="sum(current-group()/(price*qty))"/>
            </subtotal>
        </xsl:for-each-group>
    </xsl:variable>
    <output>
        <xsl:copy-of select="$subtotals"/>
        <total>
            <xsl:value-of select="sum($subtotals/subtotal)"/>
        </total>
    </output>
</xsl:template>

</xsl:stylesheet>

Result

<?xml version="1.0" encoding="UTF-8"?>
<output>
   <subtotal group="A">67</subtotal>
   <subtotal group="B">45</subtotal>
   <subtotal group="C">56</subtotal>
   <total>168</total>
</output>

CodePudding user response:

It seems that want you wanted was actually this XSLT transformation (obtained from the one provided in the problem and fixed a few issues):

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

  <xsl:template match="/">
   <Fact name="TotalLinePrice" factType="Virtual">
    <xsl:variable name="uPrice">
          <xsl:for-each-group select="/PurchaseOrder/LineItems/LineItem/OrderLine" 
                         group-starting-with="OrderLine[normalize-space(OrderQty)]">
               <xsl:for-each select="current-group()">
                 <xsl:variable name="unitPrice">
                       <xsl:copy-of select="xs:decimal(current-group()/UnitPrice)"/>
                 </xsl:variable>
                 <xsl:variable name="orderQty">
                       <xsl:copy-of select="xs:decimal(current-group()/OrderQty)"/>
                 </xsl:variable>
                 <xsl:variable name="calcPriceBasis">
                     <xsl:choose>
                        <xsl:when test="current-group()[UnitPriceBasis='TP']">
                            <xsl:copy-of select="round(($unitPrice div 1000))"/>
                        </xsl:when>
                        <xsl:when test="current-group()[UnitPriceBasis='HT']">
                            <xsl:copy-of select="round(($unitPrice div 1000))"/>
                        </xsl:when>
                        <xsl:when test="current-group()[UnitPriceBasis='HP']">
                            <xsl:copy-of select="round(($unitPrice div 100))"/>
                        </xsl:when>
                        <xsl:when test="current-group()[UnitPriceBasis='HTH']">
                            <xsl:copy-of select="round(($unitPrice div 100000))"/>
                        </xsl:when>
                        <xsl:when test="current-group()[UnitPriceBasis='PD']">
                            <xsl:copy-of select="round(($unitPrice div 12))"/>
                        </xsl:when>
                        <xsl:when test="current-group()[UnitPriceBasis='PN']">
                            <xsl:copy-of select="round(($unitPrice div 10))"/>
                        </xsl:when>
                        <xsl:when test="current-group()[UnitPriceBasis='TT']">
                            <xsl:copy-of select="round(($unitPrice div 10000))"/>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:copy-of select="$unitPrice"/>
                        </xsl:otherwise>
                     </xsl:choose>
                 </xsl:variable>
                 <total>
                   <xsl:sequence select=
                     "xs:decimal(format-number($calcPriceBasis * $orderQty, '0.00'))"/>
                 </total>
               </xsl:for-each>
          </xsl:for-each-group>
     </xsl:variable>
     <xsl:copy-of select="sum($uPrice/*)"/>
   </Fact>
  </xsl:template>
</xsl:stylesheet>

When the above, corrected transformation is applied on the provided XML document (corrected to be made a well-formed XML document):

<PurchaseOrder>
    <LineItems>
        <LineItem>
            <OrderLine>
                <OrderQty>1</OrderQty>
                <UnitPrice>105.28</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>75</OrderQty>
                <UnitPrice>2.88</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>3</OrderQty>
                <UnitPrice>155.36</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>12</OrderQty>
                <UnitPrice>1.64</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>2</OrderQty>
                <UnitPrice>2.28</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>5</OrderQty>
                <UnitPrice>3.6</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>1</OrderQty>
                <UnitPrice>405.24</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>5</OrderQty>
                <UnitPrice>79.04</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>1</OrderQty>
                <UnitPrice>2.15</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
        <LineItem>
            <OrderLine>
                <OrderQty>9</OrderQty>
                <UnitPrice>2.15</UnitPrice>
                <UnitPriceBasis>UM</UnitPriceBasis>
            </OrderLine>
        </LineItem>
    </LineItems>
</PurchaseOrder>

the seemingly wanted single result sum is produced:

<Fact name="TotalLinePrice" factType="Virtual">1651.5399999999997</Fact>
  • Related