Perform SUM operation in XSLT

2019-08-31 00:59发布

问题:

In the below input we have to check the supplier code if it is match to any of the node supplier code then we have to perform sum operation on Quantity.otherwise directly map the quantity.

input:

<Move-Afile>
  <Afile>
    <Item>
    <suppliercode>1</suppliercode>
      <PackNumber>1234</PackNumber>
      <Quantity>12</Quantity>
    </Item>
    <Item>
    <suppliercode>2</suppliercode>
      <PackNumber>567</PackNumber>
      <Quantity>3</Quantity>
    </Item>
    <Item>
    <suppliercode>1</suppliercode>
      <PackNumber>567</PackNumber>
      <Quantity>8</Quantity>
    </Item>
    <Item>
    <suppliercode>3</suppliercode>
      <PackNumber>126</PackNumber>
      <Quantity>11</Quantity>
    </Item>
    <Item>
    <suppliercode>4</suppliercode>
      <PackNumber>876</PackNumber>
      <Quantity>32</Quantity>
    </Item>
  </Afile>
</Move-Afile>

If supplier code is equal then perform sum operation on Quantity,otherwise directly map the Quantity.

output:

<A>
  <target>
    <Item>
    <suppliercode>1</suppliercode>
      <PackNumber>1234</PackNumber>
      <Quantity>20</Quantity>
    </Item>
    <Item>
    <suppliercode>2</suppliercode>
      <PackNumber>567</PackNumber>
      <Quantity>3</Quantity>
    </Item>
    <Item>
    <suppliercode>1</suppliercode>
      <PackNumber>567</PackNumber>
      <Quantity>20</Quantity>
    </Item>
    <Item>
    <suppliercode>3</suppliercode>
      <PackNumber>126</PackNumber>
      <Quantity>11</Quantity>
    </Item>
    <Item>
    <suppliercode>4</suppliercode>
      <PackNumber>876</PackNumber>
      <Quantity>32</Quantity>
    </Item>
  </target>
</A>

i need the sum logic in a separate temporary variable like below.

<varaible name=tempvar>
<xsl:choose>
<xsl:when suppliercode=suppliercode>

<xsl:value-of select=sum(quntity)/>

<xsl:when>

<xsl:otherwise>

<xsl:value-of select=quntity/>

</xsl:otherwise>

</xsl:choose>

</variable>

回答1:

This stylesheet does what you require. It copies all elements from Item downwards, and has a special template to change the value of Quantity by adding the values of all Quantity elements from Item elements that have the same value of suppliercode.

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

  <xsl:strip-space elements="*"/>
  <xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="yes"/>

  <xsl:template match="/">
    <A>
      <target>
        <xsl:apply-templates select="Move-Afile/Afile/Item"/>
      </target>
    </A>
  </xsl:template>

  <xsl:template match="node()">
    <xsl:copy>
      <xsl:apply-templates select="node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Quantity">
    <xsl:variable name="supplier-code" select="parent::Item/suppliercode"/>
    <xsl:copy>
      <xsl:value-of select="sum(ancestor::Afile/Item[suppliercode = $supplier-code]/Quantity)"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

output

<A>
   <target>
      <Item>
         <suppliercode>1</suppliercode>
         <PackNumber>1234</PackNumber>
         <Quantity>20</Quantity>
      </Item>
      <Item>
         <suppliercode>2</suppliercode>
         <PackNumber>567</PackNumber>
         <Quantity>3</Quantity>
      </Item>
      <Item>
         <suppliercode>1</suppliercode>
         <PackNumber>567</PackNumber>
         <Quantity>20</Quantity>
      </Item>
      <Item>
         <suppliercode>3</suppliercode>
         <PackNumber>126</PackNumber>
         <Quantity>11</Quantity>
      </Item>
      <Item>
         <suppliercode>4</suppliercode>
         <PackNumber>876</PackNumber>
         <Quantity>32</Quantity>
      </Item>
   </target>
</A>

Update To put the total into a variable before using it, you can replace the last template with this

  <xsl:template match="Quantity">
    <xsl:variable name="supplier-code" select="parent::Item/suppliercode"/>
    <xsl:variable name="total" select="sum(ancestor::Afile/Item[suppliercode = $supplier-code]/Quantity)"/>
    <xsl:copy>
      <xsl:value-of select="$total"/>
    </xsl:copy>
  </xsl:template>

which sets the value of $total to the sum of the quantities with the same suppplier code.



回答2:

This short and efficient (using a key) transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kQuantityBySupplier" match="Quantity" use="../suppliercode"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="/*">
  <A>
    <target>
      <xsl:apply-templates select="*/node()"/>
    </target>
  </A>
 </xsl:template>

 <xsl:template match="Quantity/text()">
  <xsl:value-of select="sum(key('kQuantityBySupplier', ../../suppliercode))"/>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<Move-Afile>
  <Afile>
    <Item>
    <suppliercode>1</suppliercode>
      <PackNumber>1234</PackNumber>
      <Quantity>12</Quantity>
    </Item>
    <Item>
    <suppliercode>2</suppliercode>
      <PackNumber>567</PackNumber>
      <Quantity>3</Quantity>
    </Item>
    <Item>
    <suppliercode>1</suppliercode>
      <PackNumber>567</PackNumber>
      <Quantity>8</Quantity>
    </Item>
    <Item>
    <suppliercode>3</suppliercode>
      <PackNumber>126</PackNumber>
      <Quantity>11</Quantity>
    </Item>
    <Item>
    <suppliercode>4</suppliercode>
      <PackNumber>876</PackNumber>
      <Quantity>32</Quantity>
    </Item>
  </Afile>
</Move-Afile>

produces the wanted, correct result:

<A>
   <target>
      <Item>
         <suppliercode>1</suppliercode>
         <PackNumber>1234</PackNumber>
         <Quantity>20</Quantity>
      </Item>
      <Item>
         <suppliercode>2</suppliercode>
         <PackNumber>567</PackNumber>
         <Quantity>3</Quantity>
      </Item>
      <Item>
         <suppliercode>1</suppliercode>
         <PackNumber>567</PackNumber>
         <Quantity>20</Quantity>
      </Item>
      <Item>
         <suppliercode>3</suppliercode>
         <PackNumber>126</PackNumber>
         <Quantity>11</Quantity>
      </Item>
      <Item>
         <suppliercode>4</suppliercode>
         <PackNumber>876</PackNumber>
         <Quantity>32</Quantity>
      </Item>
   </target>
</A>

Do note:

The time complexity of this transformation is linear (O(N)). This can be orders of magnitude more efficient than repeatedly scanning all elements to find the ones having a given suppliercode -- which has quadratical (O(N^2)) time complexity.


Update:

The OP has specified a new requirement, that the sum or the single quantity be captured in a variable:

Just modify this:

 <xsl:template match="Quantity/text()">
  <xsl:value-of select="sum(key('kQuantityBySupplier', ../../suppliercode))"/>
 </xsl:template>

into this:

 <xsl:template match="Quantity/text()">
  <xsl:variable name="vSum" select="sum(key('kQuantityBySupplier', ../../suppliercode))"/>
  <xsl:value-of select="$vSum"/>
 </xsl:template>


标签: xslt