You want to create reusable templates for performing a wide variety of bounded aggregation operations.
<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 recursivly 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 14.3.
Recipe 14.3 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 2.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) < 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 parameter—in 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.