[xsl] Transform and Sum (Was Re: sum function)

Subject: [xsl] Transform and Sum (Was Re: sum function)
From: Dimitre Novatchev <dnovatchev@xxxxxxxxx>
Date: Fri, 16 Nov 2001 14:08:52 -0800 (PST)
> How do I use the sum function on xml such as:
> 
> <Amount>12,345.12</Amount>
> <Amount>132,345.12</Amount>
> <Amount>2,345.12</Amount>
> 
> If I use Total Price = <xsl:value-of select="sum(//Amount)"/> I get the
> result NaN. However, if the data does not contain ',' the sum works
> correctly.

The functional solution to this problem is:

transform-and-sum funTrans ls  =  foldl (add.funTrans) 0 ls

In case we already have the XSLT implementation of the above function, we can simply
call it, providing as a parameter our own funTrans function:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:func-transform="f:func-transform"
exclude-result-prefixes="xsl func-transform"
>
   <xsl:import href="transform-and-sum.xsl"/>

   <xsl:output method="text"/>
   
   <func-transform:func-transform/>

    <xsl:template match="/">
      <xsl:call-template name="transform-and-sum">
        <xsl:with-param name="pFuncTransform" 
                        select="document('')/*/func-transform:*[1]"/>
        <xsl:with-param name="pList" select="/*/*"/>
      </xsl:call-template>
    </xsl:template>
    
    <xsl:template match="func-transform:*">
      <xsl:param name="arg" select="0"/>
      <xsl:value-of select="translate($arg, ',', '')"/>
    </xsl:template>

</xsl:stylesheet>

And in case the source xml doc is the following:

<t>
  <Amount>12,345.12</Amount>
  <Amount>132,345.12</Amount>
  <Amount>2,345.12</Amount>
</t>

we get the result:

147035.36

We can even implement our own eliminateCommas function, although this would be much
less efficient than using translate() directly.

Why would we implement transform-and-sum for such seemingly very simple case?

The answer is: because once implemented, it can be re-used to solve many other
problems, some of them quite complex, for which direct XSLT support (like
translate()) is not available.

Here's an example: find the sum of hexadecimal numbers.

We're using the same function transform-and-sum, passing to it this time another
function as parameter -- this one converts the hexadecimal representation of a
number into its decimal representation. here's how the code looks like in this case:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:func-transform2="f:func-transform2"
exclude-result-prefixes="xsl func-transform2"
>
   <xsl:import href="transform-and-sum.xsl"/>
   <xsl:import href="hex-to-decimal.xsl"/>

   <xsl:output method="text"/>
   
   <func-transform2:func-transform2/>

    <xsl:template match="/">
      <xsl:call-template name="transform-and-sum">
        <xsl:with-param name="pFuncTransform" 
                        select="document('')/*/func-transform2:*[1]"/>
        <xsl:with-param name="pList" select="/*/*"/>
      </xsl:call-template>
    </xsl:template>
    
    <xsl:template match="func-transform2:*">
      <xsl:param name="arg" select="0"/>
      
      <xsl:call-template name="hex-to-decimal">
        <xsl:with-param name="pxNumber" select="$arg"/>
      </xsl:call-template>
    </xsl:template>

</xsl:stylesheet>

This, when applied to the following xml source doc:

<t>
  <Amount>4000</Amount>
  <Amount>1000</Amount>
</t>

Gives the following result:
20480

Now, here's the code of the more interesting functions "transform-and-sum" and
"hex-to-decimal":

transform-and-sum.xsl:
---------------------
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:sum-fold-func="f:sum-fold-func"
exclude-result-prefixes="xsl sum-fold-func"
>
   <xsl:import href="foldl.xsl"/>

   <sum-fold-func:sum-fold-func/>

    <xsl:template name="transform-and-sum">
      <xsl:param name="pFuncTransform" select="/.."/>
      <xsl:param name="pList" select="/.."/>

      <xsl:variable name="vrtfFoldFun">
        <sum-fold-func:sum-fold-func/>
        <xsl:copy-of select="$pFuncTransform"/>
      </xsl:variable>
      
      <xsl:call-template name="foldl">
        <xsl:with-param name="pFunc" select="msxsl:node-set($vrtfFoldFun)/*"/>
        <xsl:with-param name="pList" select="$pList"/>
        <xsl:with-param name="pA0" select="0"/>
      </xsl:call-template>
    </xsl:template>

    <xsl:template name="add" match="sum-fold-func:*">
         <xsl:param name="arg0" select="/.."/>
         <xsl:param name="arg1" select="0"/>
         <xsl:param name="arg2" select="0"/>
         
         <xsl:variable name="vPartialCompose">
           <xsl:apply-templates select="$arg0">
             <xsl:with-param name="arg" select="$arg2"/>
           </xsl:apply-templates>
         </xsl:variable>
         
         <xsl:value-of select="$arg1 + $vPartialCompose"/>
    </xsl:template>

</xsl:stylesheet>

hex-to-decimal.xsl:
------------------
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:hex-converter="f:hex-converter"
>
  <xsl:import href="str-foldl.xsl"/>
  
  <hex-converter:hex-converter/>
  
  <xsl:variable name="hexDigits" select="'0123456789ABCDEF'"/>
  
  <xsl:template name="hex-to-decimal">
    <xsl:param name="pxNumber"/>
    
    <xsl:variable name="vFunXConvert" 
                  select="document('')/*/hex-converter:*[1]"/>
    
    <xsl:call-template name="str-foldl">
      <xsl:with-param name="pFunc" select="$vFunXConvert"/>
      <xsl:with-param name="pA0" select="0"/>
      <xsl:with-param name="pStr" select="$pxNumber"/>
    </xsl:call-template>
  </xsl:template>
  
  <xsl:template match="hex-converter:*">
    <xsl:param name="arg1"/> <!-- $pA0 -->
    <xsl:param name="arg2"/> <!-- a char (digit) -->
    
    <xsl:value-of select="16 * $arg1 
                        + string-length(substring-before($hexDigits, $arg2))"/>
  </xsl:template>
</xsl:stylesheet>

As usual, these functions use either "foldl" or "str-foldl" to perform any necessary
generic list-folding operations (respectively on a list of nodes, or on a string
(list of characters)).

The XSLT implementations of "foldl" and "str-foldl" have been already provided at:

http://sources.redhat.com/ml/xsl-list/2001-11/msg00214.html

and

http://sources.redhat.com/ml/xsl-list/2001-11/msg00488.html



Cheers,

Dimitre Novatchev.

__________________________________________________
Do You Yahoo!?
Find the one for you at Yahoo! Personals
http://personals.yahoo.com

 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread