XSLT: Extensible Stylesheet Transformation Language

Part 2: Creating your own XSLT

This material is adapted from the excellent tutorials at: http://www.tei-c.org.uk/Talks/OUCS/2005-02/talk-transform.pdf (2005-07-05)

Let's return to our last example

Take this

<text>
  <title>My Story</title>
  <para>As the day began...</para>
</text>

and make this

<html>
  <h1>My Story</h1>
  <p>As the day began...</p>
</html>

How do you express that in XSL?

<xsl:stylesheet version = '1.0' 
    xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
    
    <xsl:template match="/"> 
        <html>
            <xsl:apply-templates />			
        </html>
    </xsl:template>
    
    <xsl:template match="title">
        <h1><xsl:value-of select="."/></h1>
    </xsl:template>
    
    <xsl:template match="para">
        <p><xsl:value-of select="."/> </p>
    </xsl:template>
    
</xsl:stylesheet>

The Structure of an XSL file

<!-- the opening stylesheet element -->
<xsl:stylesheet version = '1.0' 
    xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
    
    <xsl:template match="[xpath expression]"> 
        <!-- do something with matched elements -->
    </xsl:template>
    
<!-- the closing stylesheet element -->
</xsl:stylesheet>

The Golden Rules of XSLT

Building a TEI stylesheet (1)

Process everything in the document and make an HTML document:

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    
    <!-- match the whole TEI document (i.e. '/') 
      and process the tags inside-->
    <xsl:template match="/">
        <html>
            <xsl:apply-templates/>
        </html>
    </xsl:template>
    
    <!-- but ignore the <teiHeader> 
      (only process the <text> element) -->
    <xsl:template match="TEI.2">
       <xsl:apply-templates select="text"/>
     </xsl:template>

 <!-- and do the <front> and <body> separately -->
    <xsl:template match="text">
        <h1>FRONT MATTER</h1>
        <xsl:apply-templates select="front"/>
        <h1>BODY MATTER</h1>
        <xsl:apply-templates select="body"/>
    </xsl:template>

</xsl:stylesheet>

Building a TEI stylesheet (2)

Add templates for paragraphs and headings:

   <!-- add template for paragraphs, divs, and headers -->
    <xsl:template match="p">
        <p><xsl:apply-templates/></p>
    </xsl:template>
    
    <xsl:template match="div">
        <div>
           <h2><xsl:value-of select="head"/></h2>
           <xsl:apply-templates/>
        </div>
    </xsl:template>

    <!-- suppress a <head> inside a <div>  Why? -->
    <xsl:template match="div/head">
    </xsl:template>

Notice how we avoid getting the heading text twice. Why did we need to qualify it to deal with just <head> inside <div>?

Building a TEI stylesheet (3)

Now for TEI lists:we'll need to look at the �type� attribute to decide what sort of HTML list to produce:

<!-- output  lists -->
    <xsl:template match="list">
        <xsl:choose>
            <xsl:when test="@type='ordered'">
                <ol><xsl:apply-templates/></ol>
            </xsl:when>
            <xsl:when test="@type='unordered'"> 
                <ul><xsl:apply-templates/></ul>
            </xsl:when>
            <xsl:when test="@type='gloss'">
                <dl><xsl:apply-templates/></dl>
            </xsl:when>
        </xsl:choose>
    </xsl:template>
    
    <!-- list items are simple -->
    <xsl:template match="item">
        <li><xsl:apply-templates/></li>
    </xsl:template>

Building a TEI stylesheet (4)

It would be nice to number the paragraphs, so let's change the template and let XSLT do it for us:

<!-- paragraphs, with a running count -->
    <xsl:template match="p">
        <p>
            <xsl:number level="any" count="p" /><xsl:text>:
            </xsl:text>
            <xsl:apply-templates/>
        </p>
    </xsl:template>
    </xsl:template>

Is the count correct? What happens if you change drop the level attribute to <xsl:number>? If your paragraph count is not correct, why not? How can we correct it (hint: use an XPath expression).

Building a TEI stylesheet (5)

At the bottom, let�s sum up that paragraph count.

<xsl:template match="/">
        <html>
            <xsl:apply-templates/>
            <p>Num para: <xsl:value-of select="count(//p)"/></p>
        </html>
    </xsl:template>

Review

We have met the following XSL basic controls:

<xsl:stylesheet>
<xsl:template match="...">
<xsl:apply-templates select="...">
<xsl:value-of select="...">
<xsl:text>
<xsl:choose>

and the following extras:

<xsl:number>
count()

Note that count() does not use <>s. It is an XSLT function, and there are many of these defined that perform specific operations.

More functions

concat: (string, string)
substring-before: (string, string)
substring-after: (string, string)
string-length: (string)
normalize-space: (string)

Modes

You can process the same elements in different ways using modes. Let's add a table of contents by using a mode:

<xsl:template match="/">
    <xsl:apply-templates select=".//tei:div" mode="toc"/>
    <xsl:apply-templates/>
</xsl:template>

<xsl:template match="tei:div" mode="toc">
    Heading <xsl:value-of select="tei:head"/>
</xsl:template>

This is a very useful technique when the same information is processed in different ways in different places.

Named templates, parameters and variables

<xsl:template name="...">: define a named template
<xsl:call-template>: call a named template
<xsl:param>: specify a parameter in a template definition
<xsl:with-param>: specify a parameter when calling a template
<xsl:variable name="...">: define a variable

Example:

<xsl:template match="/div">
    <html>
        <xsl:call-template name="header">
            <xsl:with-param name="title" select="head"/>
        </xsl:call-template>
        <xsl:apply-templates/>
    </html>
</xsl:template>

<xsl:template name="header">
    <xsl:param name="title"/>
    <head>
        <title><xsl:value-of select="$title"/></title>
    </head>
</xsl:template>

Variables

In XSLT, you can assign and reference the value of variables. In the example below, the variable "n" is created and assigned a value. Then it is output by using the <xsl:value-of> tag as well as output directly by embedding it in {} s. Note that variables are preceeded with a $ when they are referenced (but NOT when they are assigned).

<xsl:template match="p">
    <xsl:variable name="n">
        <xsl:number/>
    </xsl:variable>
    Paragraph <xsl:value-of select="$n"/>
    <a name="P{$n}"/>
    <xsl:apply-templates/>
</xsl:template>

Top-level commands

<xsl:import href="...">: include a file of XSLT templates, overriding them as needed
<xsl:include href="...">: include a file of XSLT templates, but do not override them
<xsl:output>: specify output characteristics of this job

Using XSLT as a Query language

Very often, we will sit on the root element and process all the occurrences of a specific element by using the descendant axis. Here, we just return a page count:

<xsl:template match="/">
    <html>
        <body>
        Pages: <xsl:value-of select="count(descendant::pb)"/>
        </body>
    </html>
</xsl:template>

Explore

Program your own XSLT stylesheet for TEI or other XML-based language.