Tunneling Variables
March 24, 2004
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.