Maintained by: David J. Birnbaum (djbpitt@pitt.edu) Last modified: 2021-12-27T22:03:50+0000
One advantage that XSLT provides over CSS is that while CSS can “decorate the tree,” XSLT
can rearrange it, fetching nodes from one place in the input tree and writing them
somewhere else in the output tree. A subtle side-benefit is that not only can XSLT move
a node to a new location, but it can output the same node in multiple locations and
treat it differently each time. For example, if your input document has chapters with
titles, your XSLT can create output that formats the titles and the chapters as they
might appear in a book, but it can also use the chapter titles again, differently, to
create a table of contents at the beginning of the output document. It does this by
using <xsl:apply-templates/>
more than once; in the table of contents
it applies templates just to the titles, while when it is formatting the body it might
apply templates to each chapter, and then, within each chapter, apply templates first to
the chapter title and then to the chapter body contents.
Reusing the same input nodes more than once in the output raises the question of how you
might treat a node in different ways when you reuse it. For example, when you output the
actual chapters you might want to render the chapter titles as HTML
<h3>
elements before the chapter contents, so that they look like
large, bold chapter titles. In the table of contents, though, you might want to create a
bulleted list with an HTML <ul>
element, where each chapter title
gets created inside the list as an HTML <li>
element. How do you
write template rules that can create an <h3>
element when needed for
a chapter title in the body, and that can also create a <li>
element
when needed for the same chapter title in a table of contents at the front of the output
document?
There are several possible solutions to the question of how to reuse parts of the input
tree to output them differently in different locations in the output tree, but the one
that is often most convenient is the XSLT @mode
attribute.
XSLT has a @mode
attribute that can appear on
<xsl:template>
and <xsl:apply-templates>
elements. The @mode
attribute can be used to create a separate set of rules
for processing titles in the table of contents that can operate alongside the regular
template rules that you might use to output the main text. For example, suppose your
input document is something like:
<report> <chapter> <title>This is the title of the first chapter</title> <paragraph>This paragraph is the content of the first chapter. In real life a chapter would probably have a lot of paragraphs.</paragraph> </chapter> <chapter> <title>This is the title of the second chapter</title> <paragraph>This paragraph is the content of the second chapter.</paragraph> </chapter> <chapter> <title>This is the title of the third chapter</title> <paragraph>This paragraph is the content of the third chapter.</paragraph> </chapter> <chapter> <title>This is the title of the fourth chapter</title> <paragraph>This paragraph is the content of the fourth chapter.</paragraph> </chapter> </report>
You already know how to generate HTML output from this input:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="#all" version="3.0"> <xsl:output method="xhtml" html-version="5" omit-xml-declaration="no" include-content-type="no" indent="yes"/> <xsl:template match="/"> <html> <head> <title>My output</title> </head> <body> <xsl:apply-templates select="//chapter"/> </body> </html> </xsl:template> <xsl:template match="chapter"> <xsl:apply-templates/> </xsl:template> <xsl:template match="title"> <h3> <xsl:apply-templates/> </h3> </xsl:template> <xsl:template match="paragraph"> <p> <xsl:apply-templates/> </p> </xsl:template> </xsl:stylesheet>
The <xsl:template>
and <xsl:apply-templates>
elements in this stylesheet have no @mode
attribute. You can now augment
the stylesheet by adding additional rules that specify a @mode
attribute;
you can name the mode anything you want, and I normally use “toc” for tables of
contents.
The rules you add to your stylesheet are highlighted below:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="#all" version="3.0"> <xsl:output method="xhtml" html-version="5" omit-xml-declaration="no" include-content-type="no" indent="yes"/> <xsl:template match="/"> <html> <head> <title>My output</title> </head> <body> <h2>Contents</h2> <ul> <xsl:apply-templates select="//title" mode="toc"/> </ul> <hr/> <xsl:apply-templates select="//chapter"/> </body> </html> </xsl:template> <xsl:template match="chapter"> <xsl:apply-templates/> </xsl:template> <xsl:template match="title"> <h2> <xsl:apply-templates/> </h2> </xsl:template> <xsl:template match="paragraph"> <p> <xsl:apply-templates/> </p> </xsl:template> <xsl:template match="title" mode="toc"> <li> <xsl:apply-templates/> </li> </xsl:template> </xsl:stylesheet>
Where we want to generate the table of contents (in this case, before the actual
contents), we create the <ul>
container that will hold the list of
chapter titles. Inside it we apply templates to all of the titles as a way of rounding
them up for processing, but when we apply templates there, we specify
mode="toc"
. The presence of the @mode
attribute on the
<xsl:apply-templates>
element tells the system to ignore any
template rule for <title>
nodes that does not specify the same value
for the @mode
attribute. The system therefore ignores the regular
(modeless) rule that we wrote for titles, and chooses instead the new one (with
mode="toc"
), which specifies that titles should be wrapped
<li>
tags.
Meanwhile, when we later process titles a second time, to render each title as an HTML
<h2>
before the chapter contents in the body of the output, the
old rule applies. The new, modal rules don’t affect the logic of the original
stylesheet; our old, modeless rules continue to work as before, but they are now
augmented by modal rules that let us also generate a table of contents that treats
titles differently from the way they’re treated in the main output.
You can have as many modes as you need in a stylesheet and you can call them anything you
want. Note also that you can call a template that is in one mode from inside a template
that is in a different mode. In the example above, the template rule for titles that is
in the “toc” mode applies templates to the contents of each title, that is, to the
text()
nodes that contain the actual title text, and it doesn’t specify
a mode. We can use the regular (modeless and built-in) template rule in this case
because we don’t need to process the text any differently than we do in the body of the
output document. The point is that there is no prohibition against calling a modeless
template from inside a modal one or vice versa; whether you specify a mode depends only
on the processing you need at that point in the transformation.
The most common mistake people make with modal XSLT is forgetting to specify the mode
value when needed. We have mentioned before that we often think of XSLT as involving
throwing and catching, where <xsl:apply-templates>
throws nodes out
into the ether and template rules sit around waiting to catch them as they come by. If
you have a rule like <xsl:apply-templates select="//title"
mode="toc"/>
, it throws the <title>
elements out, but
it specifies that they can be caught only by a template rule that not only matches the
correct GI (GI
is the standard abbreviation for generic identifier,
which
is XML-speak for what humans would call an element name
) with
match="title"
, but also invokes the correct mode with
mode="toc"
.