Menu

Tunneling Variables

March 24, 2004

Bob DuCharme

While coding for a large, complex stylesheet project at work last year, I wanted to reuse code that I had already written elsewhere in the same template rule. Like a good little programmer, I resisted the temptation to copy the old code and paste it in the location I was working on; instead, I moved the code to be shared into a named template and called the template rule from the two locations.

And it didn't work. The code referenced many local parameters that had been passed to the original template rule, and now that this code was in its own template rule, it didn't have access to those parameters. So I had to find them all, declare them in the new named template, and use the xsl:call-template instruction to pass them to the new template rule. (For some background in the basics of using parameters and local and global variables in XSLT, see my February 2001 column Setting and Using Variables and Parameters.) Sometimes, with as many as seven or eight variables to account for, XSLT's verbosity meant that declaring and passing these parameters added more lines to the stylesheet than I saved by splitting out the shared code into its own template rule and calling it twice instead of just copying the code.

Upon hearing about XSLT 2.0's ability to tunnel parameters, many people responded with blank looks. I had never heard the term before, but I did manage to find it used in software engineering discussions in some web searches. I also found discussions of tunneling parameters in fields such as nanotechnology, often accompanied by trippy pictures (1, 2, 3). Once I understood their new role in XSLT, though, my blank look turned into a big Homer Simpson "woohoo!", because I saw that they would solve the problem I described above.

The XSLT 2.0 specification defines tunnel parameters as having "the property that they are automatically passed on by the called template to any further templates that it calls, and so on recursively." When working on the big, complex stylesheets described above, if I could have identified the parameters passed to the original template as tunnel parameters, there would have been no need to explicitly pass them to templates that it called.

Let's look at an example. You can run the following stylesheet using Saxon 7.8 or later to see tunneled parameters in action.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="2.0">
  <xsl:output omit-xml-declaration="yes"/>

  <xsl:template match="/">
    <xsl:call-template name="template1">
      <xsl:with-param name="p1" tunnel="yes">pink</xsl:with-param>
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="template1">
    <xsl:param name="p1">orange</xsl:param>
    1. p1 in template1: <xsl:value-of select="$p1"/>
    <xsl:call-template name="template2"/>
  </xsl:template>

  <xsl:template name="template2">
    <xsl:call-template name="template3"/>
  </xsl:template>

  <xsl:template name="template3">
    <xsl:param name="p1" tunnel="yes">blue</xsl:param>
    2. p1 in template3: <xsl:value-of select="$p1"/>
  </xsl:template>

</xsl:stylesheet>

Because all of the stylesheet's logic is triggered upon seeing the root of a source document, you can use any source document you like, even the stylesheet itself. It creates the following output:

1. p1 in template1: orange
2. p1 in template3: pink

The stylesheet's first template rule calls the template1 named template, passing the string "pink" as the value of the p1 parameter. The xsl:with-param element that passes this parameter has a tunnel attribute value of "yes" to identify p1 as a tunnel parameter. (You aren't limited to passing tunnel parameters from xsl:call-template instructions—anywhere that you can use xsl:with-param, you can add tunnel="yes".)

A template rule that wants to use a tunneled parameter that's been passed to it must declare it with tunnel="yes". The template1 template rule called by the first template rule above declares a p1 parameter, but doesn't identify it as a tunnel parameter, so when it sends the value of p1 to the output, it's not the tunneled p1, but the non-tunneled one declared in that template rule. This is why it outputs "orange", the default value included in template1's p1 declaration, and not "pink", the p1 value passed as a tunneled parameter. (Note that even though the p1 declared in template1 is not the same as the one passed by the first template rule, you'll get an error if you don't declare it, because in XSLT 2.0, when you call a template and pass it a parameter, a parameter with that name must be declared in that template.)

After outputting its p1 value, template1 calls the template2 named template, passing no parameters. The template2 named template has one line: a call to the template3 named template. While template2 declares no parameters, template3 declares p1. This parameter declaration includes the tunnel="yes" attribute setting to show that it uses the tunneled version of p1. After declaring it, template3 outputs a message about the value of p1, and its value, "pink". This value was set in the original template rule and passed, or "tunneled", through template1 and template2 to template3. If template3 was much longer and more complicated, and I moved a block of its code to a separate named template, I wouldn't need to add an xsl:with-param element to the xsl:call-template instruction that called that template to explicitly pass p1 to that new template; as long as I declare it as a tunneled parameter in the new one, like I did with template3, I would have access to it.

Tunneling Parameters Through Built-in Template Rules

XSLT processors assume the existence of a built-in template rule that looks like this:

<xsl:template match="*|/">
  <xsl:apply-templates/>
</xsl:template>

If a stylesheet has no template rule specifying what to do with the source document root, or if the XSLT processor finds any elements in the source tree with no template rules in the stylesheet addressing them, it executes this built-in template rule. The template rule's xsl:apply-templates instruction acts on the default node set: the context node's children. "Children" here means element children and text node children; attributes are not considered children.

XSLT 1.0 processors assume the existence of this built-in template rule, as do 2.0 processors; but 2.0 processors do a little more for you: they tunnel all parameters through the built-in template rules. Let's look at a stylesheet that demonstrates this when it processes the following little document.

<alpha>
  <beta>
    <gamma/>
  </beta>
</alpha>

When the first template rule in the stylesheet below sees the source document's alpha element, its xsl:apply-templates instruction passes along a flavor parameter with a value of "mint". The source document's alpha element has a beta child, but the stylesheet has no template rule for beta elements, so the built-in template rule gets executed. The built-in template rule's xsl:apply-templates instruction that we saw above tells the XSLT processor to apply any relevant template rules, and there is one for the beta element's only child, the gamma element.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="2.0">
  <xsl:output omit-xml-declaration="yes"/>
  
  <xsl:template match="alpha">
    <xsl:apply-templates>
      <xsl:with-param name="flavor">mint</xsl:with-param>
    </xsl:apply-templates>
  </xsl:template>
  
  <xsl:template match="gamma">
    <xsl:param name="flavor">vanilla</xsl:param>
    Today's flavor: <xsl:value-of select="$flavor"/>
  </xsl:template>
  
</xsl:stylesheet>

The gamma template rule declares a flavor parameter with a default value of "vanilla", then outputs the flavor parameter's value, resulting in this output:

         Today's flavor: mint

The flavor parameter did not have the default value of "vanilla" specified in its declaration because another value, "mint", was passed to the template rule, overriding the default value. The alpha template rule assigned the value "mint" to the flavor parameter, and it was tunneled through the built-in template rule to the gamma one.

But... it doesn't say tunnel="yes" anywhere in the example; why was mint treated as a tunneled parameter? Because in XSLT 2.0, the built-in template rule treats all parameters as tunneled parameters. For parameters only being passed through the built-in template rule, the lack of a need for the tunnel attribute is a nice convenience.

    

Also in Transforming XML

Automating Stylesheet Creation

Appreciating Libxslt

Push, Pull, Next!

Seeking Equality

The Path of Control

What would happen if the built-in template rule didn't treat this example's flavor parameter as a tunnel parameter? Because this won't happen with an XSLT 1.0 processor, you can try it and see for yourself: change the xsl:stylesheet element's version attribute to "1.0" and run the example using Saxon 6, Xalan, or another XSLT 1.0 processor. (Saxon 7.* is a fork of the Saxon project specifically as a testbed for XSLT 2.0 support.)

Tunnel Parameters and xsl:apply-templates

My second example demonstrates another advantage of tunneled parameters: when you call named templates using the xsl:call-template instruction, you have a good idea of which template rule called which template rule, because you wrote out the calls yourself. Because xsl:apply-templates means "apply the relevant template rules as needed," it's not always clear which template rules are being invoked by which template rules, so the management of passed parameters is more difficult. Because XSLT 2.0 lets you add tunnel="yes" when you declare a tunneled parameter and tunnel="yes" whenever you use one, without worrying about the sequence of template rule calls that led from the parameter's declaration to its use, tunnel parameters are particularly handy with the xsl:apply-templates instruction.

The example included in the XSLT 2.0 Working Draft's section on tunnel parameters includes a good example of this. A match="equation" template rule declares a tunnel parameter named equation-format that can be used by any template rules applied to descendants of the equation element. It's similar to a global variable, with one important difference: while a global variable has the same value throughout the execution of the stylesheet, this equation-format variable can be set to a different value upon each execution of the match="equation" template rule, passing a customized equation-format value to the template rules for the equation element's descendants each time. It's a nice demonstration of how tunnel parameters combine a big advantage of global variables (the ability to reference the value from many different template rules in a stylesheet) with the advantages of parameters that get explicitly set and passed as needed.