| Recipe 16.4. Creating Generic Mapping FunctionsProblemYou want to create reusable templates     for performing
operations on items in a node set. SolutionThe solution involves recursively processing the elements in
$nodes and invoking the generic function,
$func, on each element. You allow the possibility
that the function specified by $func is
parameterized. This parameter can be specified by
$func-param. You further state that the default
value of the $func-param is obtained from an
attribute, @param1, in the generic functions tag.
This stipulation allows the default to be a function of the specified
generic: <xsl:template name="generic:map">
  <xsl:param name="nodes" select="/.."/>
  <xsl:param name="func" select=" 'identity' "/>
  <xsl:param name="func-param1" 
      select="$generic:generics[self::generic:func and @name = $func]/@param1"/>
  <xsl:param name="i" select="1"/>
  <xsl:param name="result" select="/.."/>
  
  <xsl:choose>
    <xsl:when test="$nodes">
      <xsl:variable name="temp">
        <xsl:apply-templates 
             select="$generic:generics[self::generic:func and 
                                       @name = $func]">
            <xsl:with-param name="x" select="$nodes[1]"/>
            <xsl:with-param name="i" select="$i"/>
            <xsl:with-param name="param1" select="$func-param1"/>
        </xsl:apply-templates>
      </xsl:variable>
   
      <xsl:call-template name="generic:map">
        <xsl:with-param name="nodes" select="$nodes[position( ) > 1]"/>
        <xsl:with-param name="func" select="$func"/>
        <xsl:with-param name="func-param1" select="$func-param1"/>
        <xsl:with-param name="i" select="$i +1"/>
        <xsl:with-param name="result" 
                        select="$result | exslt:node-set($temp)"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:apply-templates select="$result" mode="generic:map"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
   
<xsl:template match="/ | node( ) | @*" mode="generic:map">
  <node>
    <xsl:copy-of select="."/>
  </node>
</xsl:template>
 You can see the effect of this process by considering the
incr
 generic function:   <generic:func name="incr" param1="1"/>
  <xsl:template match="generic:func[@name='incr']">
       <xsl:param name="x"/>
       <xsl:param name="param1" select="@param1"/> 
       <xsl:value-of select="$x + $param1"/>
  </xsl:template>
 The incr generic function's
parameter specifies the amount by which to increment and is defaulted
to one. Here you create a stylesheet that maps
incr across a node set of numbers, first using the
default parameter and then by setting it to 10.
For good measure, you also extend the set of generics to include a
reciprocal function and map the numbers using that
function: <xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic"
  xmlns:exslt="http://exslt.org/common"
  extension-element-prefixes="exslt" exclude-result-prefixes="generic">
   
<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:*"/>
   
<!--Add a generic element function for computing reciprocal -->
<generic:func name="reciprocal"/>
<xsl:template match="generic:func[@name='reciprocal']">
     <xsl:param name="x"/>
     <xsl:value-of select="1 div $x"/>
</xsl:template>
   
<!--Test map functionality -->
<xsl:template match="numbers">
   
<results>
   
<incr>
  <xsl:call-template name="generic:map">
    <xsl:with-param name="nodes" select="number"/>
    <xsl:with-param name="func" select=" 'incr' "/>
  </xsl:call-template>
</incr>
<incr10>
    <xsl:call-template name="generic:map">
      <xsl:with-param name="nodes" select="number"/>
      <xsl:with-param name="func" select=" 'incr' "/>
      <xsl:with-param name="func-param1" select="10"/>
   </xsl:call-template>
</incr10>
<recip>
      <xsl:call-template name="generic:map">
        <xsl:with-param name="nodes" select="number"/>
        <xsl:with-param name="func" select=" 'reciprocal' "/>
     </xsl:call-template>
</recip>
</results> 
  
</xsl:template>
   
</xsl:stylesheet>
 This results in the following output:    <incr>
      <node>11</node>
      <node>4.5</node>
      <node>5.44</node>
      <node>78.7777</node>
      <node>-7</node>
      <node>2</node>
      <node>445</node>
      <node>2.1234</node>
      <node>8.77</node>
      <node>4.1415927</node>
   </incr>
   <incr10>
      <node>20</node>
      <node>13.5</node>
      <node>14.440000000000001</node>
      <node>87.7777</node>
      <node>2</node>
      <node>11</node>
      <node>454</node>
      <node>11.1234</node>
      <node>17.77</node>
      <node>13.1415927</node>
   </incr10>
   <recip>
      <node>0.1</node>
      <node>0.2857142857142857</node>
      <node>0.2252252252252252</node>
      <node>0.012857155714298572</node>
      <node>-0.125</node>
      <node>1</node>
      <node>0.0022522522522522522</node>
      <node>0.8901548869503294</node>
      <node>0.1287001287001287</node>
      <node>0.31830988148145367</node>
   </recip>
</results>
 DiscussionMap can extract a subset of a node set that meets specified criteria.
Here you use a generic function that is a predicate. You achieve the
desired effect by structuring your predicates so that they return
their input when the predicate is TRue and nothing
when the predicate is false. For example:   <generic:func name="less-than"/>
  <xsl:template match="generic:func[@name='less-than']">
       <xsl:param name="x"/>
       <!-- limit -->
       <xsl:param name="param1"/> 
       <xsl:if test="$x < $param1"><xsl:value-of select="$x"/></xsl:if>
  </xsl:template>
 You then capture the nodes of the result
with a simple filtering template: xsl:template match="/ | node( ) | @*" mode="generic:map">
  <xsl:if test="string(.)">
    <node>
      <xsl:copy-of select="."/>
    </node>
  </xsl:if>
</xsl:template>
 Here is a sample of the technique in action: <xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic"
  xmlns:exslt="http://exslt.org/common"
  extension-element-prefixes="exslt" exclude-result-prefixes="generic">
   
<xsl:import href="aggregation.xslt"/>
   
<xsl:output method="xml" indent="yes"/>
   
<!--Test map functionality -->
<xsl:template match="numbers">
   
<results>
   
<less-than-5>
  <xsl:call-template name="generic:map">
    <xsl:with-param name="nodes" select="number"/>
    <xsl:with-param name="func" select=" 'less-than' "/>
    <xsl:with-param name="func-param1" select="5"/>
  </xsl:call-template>
</less-than-5>
   
<greater-than-5>
  <xsl:call-template name="generic:map">
    <xsl:with-param name="nodes" select="number"/>
    <xsl:with-param name="func" select=" 'greater-than' "/>
    <xsl:with-param name="func-param1" select="5"/>
  </xsl:call-template>
</greater-than-5>
   
</results>
   
  
</xsl:template>
   
<xsl:template match="/ | node( ) | @*" mode="generic:map">
  <xsl:if test="string(.)">
    <node>
      <xsl:copy-of select="."/>
    </node>
  </xsl:if>
</xsl:template>
 A test of this stylesheet produces the following output: <results>
   <less-than-5>
      <node>3.5</node>
      <node>4.44</node>
      <node>-8</node>
      <node>1</node>
      <node>1.1234</node>
      <node>3.1415927</node>
   </less-than-5>
   <greater-than-5>
      <node>10</node>
      <node>77.7777</node>
      <node>444</node>
      <node>7.77</node>
   </greater-than-5>
</results>
 Mapping is not limited to numerical processing. Consider the
following stylesheet that finds the length of all
para elements in a DocBook document: <xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic"
  xmlns:exslt="http://exslt.org/common"
  extension-element-prefixes="exslt" exclude-result-prefixes="generic">
   
<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:*"/>
   
<!--Add a generic element function for computing reciprocal -->
<generic:func name="length"/>
<xsl:template match="generic:func[@name='length']">
  <xsl:param name="x"/>
  <xsl:value-of select="string-length($x)"/>
</xsl:template>
   
<!--Test map functionality -->
<xsl:template match="/">
   
<para-lengths>
    <xsl:call-template name="generic:map">
      <xsl:with-param name="nodes" select="//para"/>
      <xsl:with-param name="func" select=" 'length' "/>
    </xsl:call-template>
</para-lengths>
  
</xsl:template>
   
<xsl:template match="/ | node( ) | @*" mode="generic:map">
  <length>
    <xsl:copy-of select="."/>
  </length>
</xsl:template>
   
</xsl:stylesheet>
 Or this example, which creates a document summary by extracting the
first three sentences of each paragraph: <xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:generic="http://www.ora.com/XSLTCookbook/namespaces/generic"
  xmlns:exslt="http://exslt.org/common"
  extension-element-prefixes="exslt" exclude-result-prefixes="generic">
   
<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:*"/>
   
<!--A generic function for extracting sentences -->
<generic:func name="extract-sentences" param1="1"/>
<xsl:template match="generic:func[@name='extract-
sentences']" name="generic:extract-sentences">
  <xsl:param name="x"/>
  <xsl:param name="param1" select="@param1"/> 
   <xsl:choose>
    <xsl:when test="$param1 >= 1 and contains($x,'.')">
      <xsl:value-of select="substring-before($x,'.')"/>
      <xsl:text>.</xsl:text>
      <xsl:call-template name="generic:extract-sentences">
        <xsl:with-param name="x" select="substring-after($x,'.')"/>
        <xsl:with-param name="param1" select="$param1 - 1"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise/>
  </xsl:choose>
</xsl:template>
   
<xsl:template match="/">
   
<summary>
    <xsl:call-template name="generic:map">
      <xsl:with-param name="nodes" select="//para"/>
      <xsl:with-param name="func" select=" 'extract-sentences' "/>
      <xsl:with-param name="func-param1" select="3"/>
    </xsl:call-template>
</summary>
  
</xsl:template>
   
<xsl:template match="/ | node( ) | @*" mode="generic:map">
  <para>
    <xsl:copy-of select="."/>
  </para>
</xsl:template>
   
</xsl:stylesheet>
 These examples are convoluted ways of creating results that can
easily be obtained with more straightforward stylesheets. Creating
stylesheets that perform transformations a node at a time is easy.
For example, the 
summary stylesheet is more clearly
implemented as follows: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
     
<xsl:template name="extract-sentences">
  <xsl:param name="text"/>
  <xsl:param name="num-sentences" select="1"/> 
   <xsl:choose>
    <xsl:when test="$num-sentences >= 1 and contains($text,'.')">
      <xsl:value-of select="substring-before($text,'.')"/>
      <xsl:text>.</xsl:text>
      <xsl:call-template name="extract-sentences">
        <xsl:with-param name="text" select="substring-after($text,'.')"/>
        <xsl:with-param name="num-sentences" select="$num-sentences - 1"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise/>
  </xsl:choose>
</xsl:template>
   
<xsl:template match="/">
  <summary>
    <xsl:apply-templates select=".//para"/>
  </summary>
</xsl:template>
   
<xsl:template match="para">
  <para>
      <xsl:call-template name="extract-sentences">
        <xsl:with-param name="text" select="."/>
        <xsl:with-param name="num-sentences" select="3"/>
      </xsl:call-template>
    </para>
</xsl:template>
     
</xsl:stylesheet>
 However, if in a single stylesheet you need to perform several
map-like operations, then the generic implementation could result in
less custom-written code. An alternate mapping generic function is
generic:map2. Rather than applying a unary
function to a single node set, generic:map2
applies a binary function to the elements of two node sets and
returns the resulting node set: <xsl:template name="generic:map2">
  <xsl:param name="nodes1" select="/.."/>
  <xsl:param name="nodes2" select="/.."/>
  <xsl:param name="func" select=" 'identity' "/>
  <xsl:param name="func-param1" select="$generic:generics[self::generic:func and 
@name = $func]/@param1"/>
  <xsl:param name="i" select="1"/>
  <xsl:param name="result" select="/.."/>
  
  <xsl:choose>
    <xsl:when test="$nodes1 and $nodes2">
      <xsl:variable name="temp">
        <xsl:apply-templates 
             select="$generic:generics[self::generic:aggr-func and 
                                       @name = $func]">
            <xsl:with-param name="x" select="$nodes1[1]"/>
            <xsl:with-param name="accum" select="$nodes2[1]"/>
            <xsl:with-param name="i" select="$i"/>
            <xsl:with-param name="param1" select="$func-param1"/>
        </xsl:apply-templates>
      </xsl:variable>
   
      <xsl:call-template name="generic:map2">
        <xsl:with-param name="nodes1" select="$nodes1[position( ) > 1]"/>
        <xsl:with-param name="nodes2" select="$nodes2[position( ) > 1]"/>
        <xsl:with-param name="func" select="$func"/>
        <xsl:with-param name="func-param1" select="$func-param1"/>
        <xsl:with-param name="i" select="$i +1"/>
        <xsl:with-param name="result" select="$result | exslt:node-set($temp)"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:apply-templates select="$result" mode="generic:map"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
 generic:map2 operates on two independent node sets
in parallel and thus can apply any binary generic operation to the
successive nodes. As with generic:map, the utility
is a function of the frequency of     usage. |