Book HomeWebmaster in a Nutshell, 3rd EditionSearch this book

10.7. Templates and Patterns

An XSLT style sheet transforms an XML document by applying templates for a given type of node. A template element looks like this:

<xsl:template match="pattern"> 
   ...
</xsl:template>

where pattern selects the type of node to be processed.

For example, say you want to write a template to transform a <para> node (for paragraph) into HTML. This template will be applied to all <para> elements. The tag at the beginning of the template will be:

<xsl:template match="para">

The body of the template often contains a mix of template instructions and text that should appear literally in the result, although neither are required. In the previous example, we want to wrap the contents of the <para> element in <p> and </p> HTML tags. Thus, the template would look like this:

<xsl:template match="para">
   <p><xsl:apply-templates/></p>
</xsl:template>

The <xsl:apply-templates/> element recursively applies all other templates from the style sheet against the <para> element (the current node) while this template is processing. Every style sheet has at least two templates that apply by default. The first default template processes text and attribute nodes and writes them literally in the document. The second default template is applied to elements and root nodes that have no associated namespace. In this case, no output is generated, but templates are applied recursively from the node in question.

Now that we have seen the principle of templates, we can look at a more complete example. Consider the following XML document:

<?xml version="1.0" encoding="iso-8859-1"?>

<!DOCTYPE text SYSTEM "example.dtd">

<chapter>
   <title>Sample text</title>
   <section title="First section">
      <para>This is the first section of the text.</para>
   </section>
   <section title="Second section">
      <para>This is the second section of the text.</para>
   </section>
</chapter>

To transform this into HTML, we use the following template:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:output method="html"/>

   <xsl:template match="chapter">
      <html>
         <head>
            <title><xsl:value-of select="title"/></title>
         </head>
         <body>
            <xsl:apply-templates/>
         </body>
      </html>
   </xsl:template>

   <xsl:template match="title">
      <center>
         <h1><xsl:apply-templates/></h1>
      </center>
   </xsl:template>

   <xsl:template match="section">
      <h3><xsl:value-of select="@title"/></h3>
      <xsl:apply-templates/>
   </xsl:template>

   <xsl:template match="para">
      <p><xsl:apply-templates/></p>
   </xsl:template>

</xsl:stylesheet>

Let's look at how this style sheet works. As processing begins, the current node is the document root (not to be confused with the <chapter> element, which is its only descendant), designated as / (like the root directory in a Unix filesystem). The XSLT processor searches the style sheet for a template with a matching pattern in any children of the root. Only the first template matches (<xsl:template match="chapter">). The first template is then applied to the <chapter> node, which becomes the current node.

The transformation then takes place: the <html>, <head>, <title>, and <body> elements are simply copied into the document because they are not XSL instructions. Between the tags <head> and </head>, the <xsl:value-of select="title"/> element copies the contents of the <title> element into the document. Finally, the <xsl:apply-templates/> element tells the XSL processor to apply the templates recursively and insert the result between the <body> and </body> tags.

This time through, the title and section templates are applied because their patterns match. The title template inserts the contents of the <title> element between the HTML <center> and <h1> tags, thus displaying the document title. The section template works by using the <xsl:value-of select="@title"> element to recopy the contents of the current element's title attribute into the document produced. We can indicate in a pattern that we want to copy the value of an attribute by placing the at symbol (@) in front of its name.

The process continues recursively to produce the following HTML document:

<html>
   <head>
      <title>Sample text</title>
   </head>
   <body>
      <center>
         <h1>Sample text</h1>
      </center>
      <h3>First section</h3>
      <p>This is the first section of the text.</p>
      <h3>Second section</h3>
      <p>This is the second section of the text.</p>
   </body>
</html>

As you will see later, patterns are XPath expressions for locating nodes in an XML document. This example includes very basic patterns, and we have only scratched the surface of what can be done with templates. More information will be found in Section 10.9 later in this chapter.

In addition, the <xsl:template> element has a &mode; attribute that can be used for conditional processing. An <xsl:template match="pattern" mode="mode"> template is tested only when it is called by an <xsl:apply-templates mode="mode"> element that matches its mode. This functionality can be used to change the processing applied to a node dynamically.

10.7.1. Parameters and Variables

To finish up with templates, we should discuss the name attribute. These templates are similar to functions and can be called explicitly with the <xsl:call-template name="name"/> element, where name matches the name of the template you want to invoke. When you call a template, you can pass it parameters. Let's assume we wrote a template to add a footer containing the date the document was last updated. We could call the template, passing it the date of the last update this way:

<xsl:call-template name="footer">
<xsl:with-param name="date" select="@lastupdate"/>
</xsl:call-template>

The call-template declares and uses the parameter this way:

<xsl:template name="footer">
   <xsl:param name="date">today</xsl:param>
   <hr/>
   <xsl:text>Last update: </xsl:text>
   <xsl:value-of select="$date"/>
</xsl:template>

The parameter is declared within the template with the <xsl:param name="date"> element whose content (today) provides a default value. We can use this parameter inside the template by placing a dollar sign ($) in front of the name.

We can also declare variables using the <xsl:variable name="name"> element, where the content of the element gives the variable its value. The variables are used like parameters by placing a dollar sign ($) in front of their names. Note that even though they are called variables, their values are constant and cannot be changed. A variable's visibility also depends on where it is declared. A variable that is declared directly as a child element of <xsl:stylesheet> can be used throughout the style sheet as a global variable. Conversely, when a variable is declared in the body of the template, it is visible only within that same template.

10.7.2. Style Sheet Import and Rules of Precedence

Style sheets may be imported using the <xsl:import href= "uri"> element, where the &href; attribute indicates the path of the style sheet to be imported. Note that an <xsl:import> statement must be a direct child of the <xsl:stylesheet> element.

Imported style sheet templates have lower precedence than templates contained in the file into which they are incorporated. This means that if two templates compete for the processing of an element, the template of the original file takes precedence over the template of the imported file. Thus, imported templates can be overridden by redefining them in the original style sheet.

The rules of precedence can be changed in two ways:

Style sheets can also be included in an XSL file with the <xsl:include href="uri"/> element. The precedence of an included template is the same as that of the calling style sheet templates.

10.7.3. Loops and Tests

To process an entire list of elements at the same time, use the <xsl:for-each> loop. For example, the following template adds a table of contents to our example:

<xsl:template name="toc">
<xsl:for-each select="section">
<xsl:value-of select="@title"/>
<br/>
</xsl:for-each>
</xsl:template>

The body of this <xsl:for-each> loop processes all the <section> elements that are children of the current node. Within the loop, we output the value of each section's title attribute, followed by a line break.

XSL also defines elements that can be used for tests:

<xsl:if test="expression">
The body of this element is executed only if the test expression is true.

<xsl:choose>
This element allows for several possible conditions. It is comparable to switch in the C and Java languages. The <xsl:choose> element is illustrated as follows:

<xsl:choose>
   <xsl:when test="case-1">
      <!-- executed in case 1 -->
   </xsl:when>
   <xsl:when test="case-2">
      <!-- executed in case 2 -->
   </xsl:when>

   <xsl:otherwise>
      <!-- executed by default -->
   </xsl:otherwise>
</xsl:choose>

The body of the first <xsl:when> element whose test expression is true will be executed. The XSL processor then moves on to the instructions following the closing </xsl:choose> element tag, skipping the remaining tests. The <xsl:otherwise> element is optional; its body is executed only if none of the preceding elements were executed.

10.7.4. Numbering Elements

XSL provides a simple method for numbering elements with the <xsl:number> element. Let's assume we want to number the sections and paragraphs in a document. We can do this by adding the following code before displaying the section titles and the content of the paragraphs:

<xsl:number count="sect|para"
   level="multiple" format="1.1"/>
<xsl:text>- </xsl:text>

The result is:

1 - First section
1.1 - This is the first section of text.
2 - Second section
2.1 - This is the second section of text.

The count attribute decides which elements should be numbered. Elements must be separated by a |. The level attribute specifies the level of numbering and may take one of three string values: single, multiple, or any. single tells the processor to number only one level. In this case, paragraph numbers will not indicate the section number. multiple numbers several levels, meaning that the first part of the paragraph number is the section number in our previous example. any tells the processor to add numbering without regard to level. Here, the numbers of the sections and paragraphs are consecutive.

The format attribute indicates the style of numbering. Letters or numbers may be used, with a separator in between. The letters may be A or a (for alphabetical numbering in upper- or lowercase), I or i (for numbering in upper- or lowercase Roman numerals), or 1 (for numbering in Arabic numerals). For example, to number sections with Roman numerals and paragraphs with lowercase letters, use this format attribute:

format="I.a"

10.7.5. Output Method

An XSLT processor can be instructed to produce a specific type of output with the <xsl:output/> element. For example, <xsl:output method="html"/> causes the processor to execute certain transformations needed for the resulting document to be valid HTML. Specifically, it transforms empty tags. For example, the XML <hr/> tag is converted to the HTML <hr> tag (for horizontal rules) without a closing slash.

It is also possible to indicate an XML output method (method="xml"), where the XSLT processor adds the standard XML header (<?xml version="1.0"?>). It may seem strange to produce an XML document from another XML document, yet it is often helpful to convert a document from one DTD to a valid document for another DTD. Thus, XSLT is also a language for inter-DTD conversions.

Finally, you can specify a text output method (method="text") to produce pure text. XSLT has built-in outputs for XML, HTML, and text, but some processors may support other output methods (sometimes identified by URLs).

We should point out that when you choose the HTML or XML output method, the processor may remove or rearrange whitespace in blocks of text (spaces, tabs, and carriage returns). However, there are several solutions for preserving whitespace. The first is to indicate the list of elements to be preserved in the <xsl:preserve-space elements="list"> element. The second is to add the &indent="no"; attribute to the <xsl:output> element to indicate that you do not want the resulting document to be indented. Note, however, that spaces are no longer preserved in <xsl:text> elements where content is written as-is in the resulting document. No indenting is produced for the text output method.



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.