Clark C. Evans
Following is a collection of my observations
regarding XSLT template mechanism. I hope it may
be interesting.
XSLT's Template Dispatch by Clark C. Evans
December 1, 2000
XSLT is a language for transforming XML texts. The
language includes familiar constructs such as for-each
iteration, conditional statements, and callable
functions. XSLT also includes an additional control
structure, the template dispatch, which occurs through
the interaction of apply-template/select and
template/match.
This paper presents two templates, one of which uses
the template dispatch, and a second, functionally equivalent
to the first, which is constructed without the help of this
control structure. This paper then concludes with a comparison
which may help to elucidate the power and elegance of XSLT's
template dispatch.
Consider the XML input
<bookcase>
<book>
<title>The C Programming Language</title>
<author>Brian W. Kernighan</author>
<author>Dennis M. Richie</author>
</book>
<book>
<title>Compilers: Principles, Techniques, and Tools</title>
<author>Alfred V. Aho</author>
<author>Ravi Sethi</author>
<author>Jeffrey D. Ullman</author>
</book>
</bookcase>
processed by the XSLT stylesheet
<stylesheet
xmlns="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<template match="author">
Author: <apply-templates/>
</template>
<template match="book">
Book: <apply-templates select="title|author" />
</template>
</stylesheet>
to produce the following output: Book: The C Programming Language
Author: Brian W. Kernighan
Author: Dennis M. Richie
Book: Compilers: Principles, Techniques, and Tools
Author: Alfred V. Aho
Author: Ravi Sethi
Author: Jeffrey D. Ullman
Given any input text, the following stylesheet will
produce exactly the same output as the stylesheet above,
only it will do so without the aid of
apply-template/select and apply-template/match. As a
consequence, much of its code necessarily emulates
functionality required by the XSLT specification and
built into a compliant XSLT processor.
<stylesheet
xmlns="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<template match="/">
<call-template name="dispatch"/>
</template>
<template name="dispatch">
<variable name="id" select="generate-id(.)" />
<choose>
<when test="//author[generate-id(.) = $id]">
Author: <call-template name="apply" />
</when>
<when test="//book[generate-id(.) = $id]">
Book: <call-template name="apply" >
<with-param name="select" select="title|author" />
</call-template>
</when>
<when test="self::text()">
<value-of select="." />
</when>
<otherwise>
<call-template name="apply" />
</otherwise>
</choose>
</template>
<template name="apply">
<param name="select" select="node()" />
<for-each select="$select">
<call-template name="dispatch" />
</for-each>
</template>
</stylesheet>
The entry point for a procedural stylesheet is marked by
<template match="/">, a special case similar to a "C"
style main() function. When this template is executed,
the current node for the process is initialized to root
node, and then the body, <call-template name="dispatch" />,
transfers control to the template named dispatch.
The dispatch template begins by creating a variable,
$id, which is used to hold an unique string identifier
generated by the XSLT processor for the current node.
Following is a conditional switch statement having three
when clauses and a single otherwise. For each when
clause, a path expression is evaluated and converted
into a boolean value. If true, then the corresponding
body is executed and control resumes immediately after
the choose construct ends. If all of the when clauses
fail to fire, then the body of the otherwise is executed.
The first case, <when
test="//author[generate-id(.)=$id]">, has a path
expression returning the node-set consisting of any
author element having an identifier equal to the current
node's identifier. Therefore, if the current node
happens to be an author, the node-set returned will be
non-empty, which converts to a true boolean value. This
body is executed with two operations: the non-empty text
node "\n Author: " is printed and then control is
transferred to the apply template as specified by
<call-template name="apply"/>.
The second case, <when
test="//book[generate-id(.)=$id]">, is very similar.
Only here, the apply template is called with a parameter
named select. The select parameter is the node-set
containing all element children of the current node with
a name of either title or author.
The third case, <when test="self::text()">, only fires
when the current node is a text node. Here <value-of select=".">
instructs the processor to print the text value of the current
node. Finally, in default, the apply template is called when
none of the previous when clauses have executed.
The last template in the procedural stylesheet, apply,
has an optional parameter select, which is passed as a
node-set. If this parameter is missing, then every child
of the current node is selected. The remainder of this
function then iterates through the selected node-set,
calling the dispatch template.
A comparison of the two stylesheets reveals that the
first when clause described above corresponds directly
to the first template of the original stylesheet. In a
similar manner, the second when clause corresponds to
the second template of the original stylesheet. The
third when and the default otherwise clause are needed
to emulate the built-in-rule required by the XSLT
specification. A fully compliant emulation would also
order the tests according to XSLT's priority rules.
As you can see with this emulation, a good amount of
code is built into the XSLT processor. Specifically,
functionality similar to the apply template, a default
entry point, and a mechanism similar to the dispatch
template are included. Furthermore, the complexity of
this dispatch mechanism increases when the import
statement and mode attribute are considered. The
administration of these and other details is handled by
the XSLT processor, allowing succinct stylesheets like
the first one presented.
Examination of this procedural stylesheet also clarifies
the complementary roles played by select and match
expressions. The select expression chooses which nodes
to visit, and, for each node, the match expressions
designate which template to execute. This allows an
ordered set to be selected as a whole, yet each node in
the set to be treated individually. This decoupling of
roles is elegantly managed by the XSLT processor and
invoked by apply-template/select and template/match.
The decoupling of select and match also allows a
processor to pre-compute the match expressions up-front.
For example, given an in-memory node-based implementation,
an additional pointer could be added to each node. After
the tree is loaded and before processing commences, the
processor could visit each node in the tree, filling in
a pointer to the template which best matches the node
according to the priority rules. Then, while iterating
through a selected node-set, the template to dispatch is
immediately available without further computation.
XSLT's template mechanism may not be the best solution
to every transformation requirement; however, when the
inputs have varying structure such that relative order
among nodes with different matching criteria is important,
its template dispatch approach is a clear winner. |