Recipe16.3.Creating Generic Bounded Aggregation Functions


Recipe 16.3. Creating Generic Bounded Aggregation Functions

Problem

You want to create reusable templates for performing a wide variety of bounded aggregation operations.

Solution

  <xsl:template name="generic:bounded-aggregation">     <xsl:param name="x" select="0"/>     <xsl:param name="func" select=" 'identity' "/>     <xsl:param name="func-param1"/>     <xsl:param name="test-func" select=" 'less-than' "/>     <xsl:param name="test-param1" select="$x + 1"/>      <xsl:param name="incr-func" select=" 'incr' "/>     <xsl:param name="incr-param1" select="1"/>      <xsl:param name="i" select="1"/>     <xsl:param name="aggr-func" select=" 'sum' "/>     <xsl:param name="aggr-param1"/>      <xsl:param name="accum"                 select="$generic:generics[self::generic:aggr-func and                                           @name = $aggr-func]/@identity"/>              <!-- Check if aggregation should continue -->       <xsl:variable name="continue">       <xsl:apply-templates             select="$generic:generics[self::generic:func and                                       @name = $test-func]">         <xsl:with-param name="x" select="$x"/>         <xsl:with-param name="param1" select="$test-param1"/>       </xsl:apply-templates>     </xsl:variable>         <xsl:choose>       <xsl:when test="string($continue)">        <!--Compute func($x) -->          <xsl:variable name="f-of-x">           <xsl:apply-templates select="$generic:generics[self::generic:func                                                           and                                                           @name = $func]">             <xsl:with-param name="x" select="$x"/>             <xsl:with-param name="i" select="$i"/>             <xsl:with-param name="param1" select="$func-param1"/>           </xsl:apply-templates>         </xsl:variable>             <!-- Aggregate current $f-of-x with $accum -->             <xsl:variable name="temp">           <xsl:apply-templates                select="$generic:generics[self::generic:aggr-func and                                           @name = $aggr-func]">             <xsl:with-param name="x" select="$f-of-x"/>             <xsl:with-param name="i" select="$i"/>             <xsl:with-param name="param1" select="$aggr-param1"/>             <xsl:with-param name="accum" select="$accum"/>           </xsl:apply-templates>         </xsl:variable>                    <!-- Compute the next value of $x-->             <xsl:variable name="next-x">           <xsl:apply-templates                 select="$generic:generics[self::generic:func and                                           @name = $incr-func]">             <xsl:with-param name="x" select="$x"/>             <xsl:with-param name="param1" select="$incr-param1"/>           </xsl:apply-templates>         </xsl:variable>                      <!--We tail recursively process the remaining nodes using                position( ) -->           <xsl:call-template name="generic:bounded-aggregation">             <xsl:with-param name="x" select="$next-x"/>             <xsl:with-param name="func" select="$func"/>             <xsl:with-param name="func-param1" select="$func-param1"/>             <xsl:with-param name="test-func" select="$test-func"/>             <xsl:with-param name="test-param1" select="$test-param1"/>              <xsl:with-param name="incr-func" select="$incr-func"/>             <xsl:with-param name="incr-param1" select="$incr-param1"/>              <xsl:with-param name="i" select="$i + 1"/>             <xsl:with-param name="aggr-func" select="$aggr-func"/>             <xsl:with-param name="aggr-param1" select="$aggr-param1"/>              <xsl:with-param name="accum" select="$temp"/>           </xsl:call-template>         </xsl:when>         <xsl:otherwise>           <xsl:value-of select="$accum"/>         </xsl:otherwise>       </xsl:choose>   </xsl:template>

This template does not aggregate a node set, but rather aggregates over a set of values defined by an initial value, an increment function, and a predicate that determines when the aggregation should terminate. The incrementing function and test function each can take their own parameters. The other generic:bounded-aggregation parameters are the same as generic:aggregation in Recipe 16.2.

Discussion

This recipe addresses the problem of aggregating XML content generically. However, sometimes you need to perform aggregation-like operations on mathematically synthesized data.

The simplest thing to do with this generic bounded aggregation function is to implement the factorial and prod-range templates from Recipe 3.5:

<xsl:stylesheet version="1.0"    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic"   xmlns:aggr="http://www.ora.com/XSLTCookbook/namespaces/aggregate"   xmlns:exslt="http://exslt.org/common"   extension-element-prefixes="generic aggr exslt">       <xsl:import href="aggregation.xslt"/>      <xsl:output method="xml" indent="yes"/>       <xsl:template name="factorial">     <xsl:param name="n" select="0"/>          <xsl:call-template name="generic:bounded-aggregation">       <xsl:with-param name="x" select="$n"/>       <xsl:with-param name="test-func" select=" 'greater-than' "/>       <xsl:with-param name="test-param1" select="0"/>        <xsl:with-param name="incr-func" select=" 'decr' "/>       <xsl:with-param name="aggr-func" select=" 'product' "/>     </xsl:call-template>       </xsl:template>       <xsl:template name="prod-range">     <xsl:param name="start" select="1"/>     <xsl:param name="end" select="1"/>          <xsl:call-template name="generic:bounded-aggregation">       <xsl:with-param name="x" select="$start"/>       <xsl:with-param name="test-func" select=" 'less-than-eq' "/>       <xsl:with-param name="test-param1" select="$end"/>        <xsl:with-param name="incr-func" select=" 'incr' "/>       <xsl:with-param name="aggr-func" select=" 'product' "/>     </xsl:call-template>       </xsl:template>     <xsl:template match="/">       <results>        <factorial n="0">       <xsl:call-template name="factorial"/>     </factorial>          <factorial n="1">       <xsl:call-template name="factorial">         <xsl:with-param name="n" select="1"/>       </xsl:call-template>     </factorial>         <factorial n="5">       <xsl:call-template name="factorial">         <xsl:with-param name="n" select="5"/>       </xsl:call-template>     </factorial>         <factorial n="20">       <xsl:call-template name="factorial">         <xsl:with-param name="n" select="20"/>       </xsl:call-template>     </factorial>         <product start="1" end="20">       <xsl:call-template name="prod-range">         <xsl:with-param name="start" select="1"/>         <xsl:with-param name="end" select="20"/>       </xsl:call-template>     </product>         <product start="10" end="20">       <xsl:call-template name="prod-range">         <xsl:with-param name="start" select="10"/>         <xsl:with-param name="end" select="20"/>       </xsl:call-template>     </product>       </results>     </xsl:template>    </xsl:stylesheet>

The resulting output is:

<results>    <factorial n="0">1</factorial>    <factorial n="1">1</factorial>    <factorial n="5">120</factorial>    <factorial n="20">2432902008176640000</factorial>    <product start="1" end="20">2432902008176640000</product>    <product start="10" end="20">6704425728000</product> </results>

However, this is only the tip of the iceberg! You can also use generic:bounded-aggregation for numeric integration:

<xsl:stylesheet version="1.0"    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic"   xmlns:aggr="http://www.ora.com/XSLTCookbook/namespaces/aggregate"   xmlns:exslt="http://exslt.org/common"   extension-element-prefixes="generic aggr exslt">       <xsl:import href="aggregation.xslt"/>      <xsl:output method="xml" indent="yes"/>     <!-- Extend the available generic functions --> <xsl:variable name="generic:generics" select="$generic:public-generics |  document('')/*/generic:*"/>       <xsl:template name="integrate">     <xsl:param name="from" select="0"/>     <xsl:param name="to" select="1"/>     <xsl:param name="func" select=" 'identity' "/>     <xsl:param name="delta" select="($to - $from) div 100"/>          <xsl:call-template name="generic:bounded-aggregation">       <xsl:with-param name="x" select="$from"/>       <xsl:with-param name="func" select=" 'f-of-x-dx' "/>       <xsl:with-param name="func-param1">         <params f-of-x="{$func}" dx="{$delta}"/>       </xsl:with-param>       <xsl:with-param name="test-func" select=" 'less-than' "/>       <xsl:with-param name="test-param1" select="$to"/>        <xsl:with-param name="incr-func" select=" 'incr' "/>       <xsl:with-param name="incr-param1" select="$delta"/>        <xsl:with-param name="aggr-func" select=" 'sum' "/>     </xsl:call-template>       </xsl:template>       <xsl:template name="integrate2">     <xsl:param name="from" select="0"/>     <xsl:param name="to" select="1"/>     <xsl:param name="func" select=" 'identity' "/>     <xsl:param name="delta" select="($to - $from) div 100"/>          <xsl:call-template name="generic:bounded-aggregation">       <xsl:with-param name="x" select="$from"/>       <xsl:with-param name="func" select=" 'f-of-x-dx-2' "/>       <xsl:with-param name="func-param1">         <params f-of-x="{$func}" dx="{$delta}"/>       </xsl:with-param>       <xsl:with-param name="test-func" select=" 'less-than' "/>       <xsl:with-param name="test-param1" select="$to"/>        <xsl:with-param name="incr-func" select=" 'incr' "/>       <xsl:with-param name="incr-param1" select="$delta"/>        <xsl:with-param name="aggr-func" select=" 'sum' "/>     </xsl:call-template>       </xsl:template>       <generic:func name="f-of-x-dx"/>   <xsl:template match="generic:func[@name='f-of-x-dx']">        <xsl:param name="x"/>        <xsl:param name="param1"/>               <xsl:variable name="f-of-x">         <xsl:apply-templates select="$generic:generics[self::generic:func          and @name = exslt:node-set($param1)/*/@f-of-x]">           <xsl:with-param name="x" select="$x"/>         </xsl:apply-templates>        </xsl:variable>                <xsl:value-of select="$f-of-x * exslt:node-set($param1)/*/@dx"/>   </xsl:template>       <generic:func name="f-of-x-dx-2"/>   <xsl:template match="generic:func[@name='f-of-x-dx-2']">        <xsl:param name="x"/>        <xsl:param name="param1"/>                <xsl:variable name="func" select="exslt:node-set($param1)/*/@f-of-x"/>        <xsl:variable name="dx" select="exslt:node-set($param1)/*/@dx"/>               <xsl:variable name="f-of-x">         <xsl:apply-templates               select="$generic:generics[self::generic:func and                                         @name = $func]">           <xsl:with-param name="x" select="$x"/>         </xsl:apply-templates>        </xsl:variable>           <xsl:variable name="f-of-x-plus-dx">         <xsl:apply-templates select="$generic:generics[self::generic:func                                                         and @name = $func]">           <xsl:with-param name="x" select="$x + $dx"/>         </xsl:apply-templates>        </xsl:variable>           <!-- This is just the absolute value of $f-of-x-plus-dx - $f-of-x -->                <xsl:variable name="abs-diff"                    select="(1 - 2 *(($f-of-x-plus-dx - $f-of-x) &lt; 0)) *                            ($f-of-x-plus-dx - $f-of-x)"/>                <xsl:value-of select="$f-of-x * $dx + ($abs-diff * $dx) div 2"/>           </xsl:template>       <xsl:template match="/">       <results>        <integrate desc="intgr x from 0 to 1">       <xsl:call-template name="integrate"/>     </integrate>         <integrate desc="intgr x from 0 to 1 with more precision">       <xsl:call-template name="integrate">         <xsl:with-param name="delta" select="0.001"/>       </xsl:call-template>     </integrate>         <integrate desc="intgr x from 0 to 1 with better algorithm">       <xsl:call-template name="integrate2"/>     </integrate>         <integrate desc="intgr x**2 from 0 to 1">       <xsl:call-template name="integrate">         <xsl:with-param name="func" select=" 'square' "/>       </xsl:call-template>     </integrate>         <integrate desc="intgr x**2 from 0 to 1 with better algorithm">       <xsl:call-template name="integrate2">         <xsl:with-param name="func" select=" 'square' "/>       </xsl:call-template>     </integrate>        </results>     </xsl:template>    </xsl:stylesheet>

The challenge here is that you want the user of the integrate templates to pass in any old function of x. However, you need to compute a sum that is a function of that function. Hence, you need a way to define a higher-order function. In addition, you must pass the higher-order function an additional parameterin this case, the delta used to approximate the integration. You can do this by passing an element that you synthesize on the fly, <params f-of-x="{$func}" dx="{$delta}"/>. This compound parameter is used in the higher-order functions f-of-x-dx and f-of-x-dx-2. Unfortunately, XSLT 1.0 forces you to use exsl:node-set to extract information from the compound parameter.

Here is the output of the stylesheet shown earlier:

<results>   <integrate desc="intgr x from 0 to 1">     0.4950000000000004   </integrate>   <integrate desc="intgr x from 0 to 1 with more precision">     0.4995000000000005   </integrate>    <integrate desc="intgr x from 0 to 1 with better algorithm">      0.5000000000000001      </integrate>    <integrate desc="intgr x**2 from 0 to 1">      0.32835000000000036   </integrate>   <integrate desc="intgr x**2 from 0 to 1 with better algorithm">     0.33335000000000037   </integrate> </results>

You are unlikely to need numerical integration in XSLT. Numerical integration is not the point of this example. Instead, it demonstrates the power of the generic programming style to accomplish much by using generic reusable code.




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net