Here are three possible solutions:
Solution1 XSLT2:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select="sum(/*/*/(rate * quantity))"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<parts>
<part>
<rate>0.37</rate>
<quantity>10</quantity>
</part>
<part>
<rate>0.03</rate>
<quantity>10</quantity>
</part>
</parts>
The wanted result is produced:
4
The XSLT 2.0 solution uses the fact that in XPath 2.0 it is allowed that the right argument of the last "/" operator can be an expression or generally a function. This expression/function is applied for each of the nodes selected so far acting as the context node, and each function application produces one result.
Solution2 XSLT 1.0:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="sumProducts">
<xsl:with-param name="pList" select="*/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="sumProducts">
<xsl:param name="pList"/>
<xsl:param name="pAccum" select="0"/>
<xsl:choose>
<xsl:when test="$pList">
<xsl:variable name="vHead" select="$pList[1]"/>
<xsl:call-template name="sumProducts">
<xsl:with-param name="pList" select="$pList[position() > 1]"/>
<xsl:with-param name="pAccum"
select="$pAccum + $vHead/rate * $vHead/quantity"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pAccum"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When applied on the above XML document, the correct result is produced:
4
This is a typical XSLT 1.0 recursive solution. Do note how the sumProducts
template calls itself recursively, until the entire input list, passed in the parameter $pList
is processed.
Solution3 FXSL (XSLT 1.0):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:test-map-product="test-map-product"
exclude-result-prefixes="xsl ext test-map-product"
>
<xsl:import href="sum.xsl"/>
<xsl:import href="map.xsl"/>
<xsl:import href="product.xsl"/>
<!-- This transformation is to be applied on:
salesMap.xml
It contains the code of the "sum of products" from the
article "The Functional Programming Language XSLT"
-->
<test-map-product:test-map-product/>
<xsl:output method="text"/>
<xsl:template match="/">
<!-- Get: map product /sales/sale -->
<xsl:variable name="vSalesTotals">
<xsl:variable name="vTestMap" select="document('')/*/test-map-product:*[1]"/>
<xsl:call-template name="map">
<xsl:with-param name="pFun" select="$vTestMap"/>
<xsl:with-param name="pList1" select="/sales/sale"/>
</xsl:call-template>
</xsl:variable>
<!-- Get sum map product /sales/sale -->
<xsl:call-template name="sum">
<xsl:with-param name="pList" select="ext:node-set($vSalesTotals)/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="makeproduct" match="*[namespace-uri() = 'test-map-product']">
<xsl:param name="arg1"/>
<xsl:call-template name="product">
<xsl:with-param name="pList" select="$arg1/*"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<sales>
<sale>
<price>3.5</price>
<quantity>2</quantity>
<Discount>0.75</Discount>
<Discount>0.80</Discount>
<Discount>0.90</Discount>
</sale>
<sale>
<price>3.5</price>
<quantity>2</quantity>
<Discount>0.75</Discount>
<Discount>0.80</Discount>
<Discount>0.90</Discount>
</sale>
</sales>
The correct result is produced:
7.5600000000000005
In the last case for each sale
we calculate the product of price
, quantity
and all available (variable number of) discount
-s.
FXSL is a pure XSLT implementation of higher order functions. In this example the higher-order function f:map()
is used to map the function f:product()
on the list of children of every sale
element. Then the results are summed to produce the final result.