
Tunneling Variables
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 | |
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.
- Your XML disciple
2004-03-26 09:46:15 Anush Titanian - Your XML disciple
2004-03-26 10:30:07 Bob DuCharme - Your XML disciple
2004-03-26 10:31:08 Bob DuCharme - Fantastic!
2004-03-25 01:57:01 Daniel Zambonini - Fantastic!
2004-03-25 07:32:07 Bob DuCharme