Digital humanities


Maintained by: David J. Birnbaum (djbpitt@pitt.edu) [Creative Commons BY-NC-SA 3.0 Unported License] Last modified: 2021-12-27T22:03:50+0000


Modal XSLT

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 modes

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".