Intensive Introduction to XSLT

Dr James Cummings

@jamescummings

Based on the work of Syd Bauman, David Birnbaum, Julia Flanders, 
Martin Holmes, James Cummings and others.
License: CC+by+sa

(Press space to cycle through slides)

Resources 

Vague & Unrealistic TopicTable

  • Beginnings
    • Introduction to XSLT
    • Navigating the XML Tree
      • ​Introduction to XPath
    • Transforming XML using XSLT
  • Transforming
    • The XSLT Template Paradigm
    • Built In Templates
    • XSL Variables
  • Lunch (not included)
  • Building Output
    • XSL Constructors
    • XSL Copy and constructors and identity transforms
    • Template Modes
  • Getting More Advanced
    • Conditionals and Looping
    • Named Templates
    • Using data from another document
    • Multiple Input Files For One Stylesheet
  • Conclusion
     

An Introduction to XSLT

The XML/XPath/XSL Family

  • XML: Extensible Markup Language
  • XPath: An XML Path Language
  • XSLT: Extensible Stylesheet Language Transformations
  • XSL-FO: Extensible Stylesheet Language - Formatting Objects 
  • XQuery: An XML Query Language

And many others...

What Does XSLT Look Like?

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   version="2.0">

<xsl:template match="An-XPath-Here">
  <!-- Template Processing -->
</xsl:template>    

</xsl:stylesheet>

Navigating the XML Tree

<?xml version="1.0" encoding="UTF-8"?>
<book>
  <introduction>Blah blah blah ... </introduction>
  <chapter>
    <heading>Wines</heading>
    <section>White wines ... </section>
    <section>Red wines ... </section>
  </chapter>
  <chapter>
    <heading>Beers</heading>
    <section>Ales ... </section>
    <section>Lagers ... </section>
  </chapter>
  <index> stuff ... </index>
</book>
<?xml version="1.0" encoding="UTF-8"?>
<lg type="limerick" rhyme="aabba" n="3">
  <head>Warp Speed, Ms Bright!</head>
  <l>There was a young lady named <rhyme label="a">Bright</rhyme>,</l>
  <l>Who travelled much faster than <rhyme label="a">light</rhyme>,</l>
  <l>She departed one <rhyme label="b">day</rhyme>,</l>
  <l>In a <term xml:id="t17">relative</term> <rhyme label="b">way</rhyme>,</l>
  <l>And returned on the previous <rhyme label="a">night</rhyme>.</l>
  <note target="#t17">See
    <ptr target="http://en.wikipedia.org/wiki/Theory_of_relativity"/>.</note>
</lg>

Same Fragment With Text and Attribute Nodes

The Context Node

  • Important definition: The context node is where we are now in the XML tree.
  • In XPath, and therefore in XSLT, the processor is always somewhere in the tree.
  • From the context node, you can travel anywhere else in the tree.
  • We do that by travelling along XPath axes.
  • XPaths look a bit like URL Paths:
    • /TEI/text/body/div[@n='1']
    • //div[@n='1']
    • descendent-or-self::div[attribute::n='1']

An XPath Introduction

self::   (or '.')

Descendants (child:: and descendant::)

Ancestors (parent:: and ancestor::)

Preceding (preceding:: and preceding-sibling::)

following (following:: and following-sibling::)

The XPath Butterfly

Common XPath Functions

  • Conversion: boolean, string, number
  • Contexts: count, deep-equals, last, position
  • DateTimes: current-dateTime, day-from-dateTime, months-from-duration, timezone-from-dateTime
  • Math: avg, ceiling, count, floor, max, min, round, sum
  • Logic: true, false, not
  • Nodes: lang, local-name, name, namespace-uri, text
  • Sequences: distinct-values, empty, index-of, subsequence
  • Strings: concat, contains, lower-case, matches, normalize-space, replace, starts-with, string-length, substring, substring-after, substring-before, tokenize, translate, upper-case

Transforming XML Using XSLT

(Using oXygen XML Editor)

Transforming an XML File
in oXygen

Follow along while I:

  • Open the hamlet.xml sample file
  • ... and look through it
  • Open the trans_01.xsl sample file
  • ... and look through it
  • Switch to the XSLT debugger
  • Select the XSLT engine (accept the Saxon-HE default)
  • Verify that you’ve chosen the correct XML and XSLT files
  • (Optional: Specify an output filename to save output)
  • Run the transformation (click on blue arrow)
  • Examine the output as text
  • Open the trans_02.xsl
  • Look through it
  • Switch to or stay in the XSLT debugger
  • Select the XSLT engine (accept the Saxon-HE default)
  • Toggle the XHTML output 
  • Verify that you’ve chosen the new XSLT file
  • Run the transformation straight through (click on blue arrow)
  • Examine the output as XHTML and Text

Configuring a Transformation Scenario

  • Switch back to Editor perspective
  • Click on the wrench with the red triangle or type Ctrl-Shift-c (Cmd-Shift-c on Mac) or use the Document - Transformation menu
  • Click on 'New' -- Scenario type is XML transformation with XSLT
    • Choose project options
    • Create a new scenario
    • XSLT tab 
      • XML URL: use the default of ${currentFileURL}
      • XSL URL: browse to your XSLT file
      • Transformer: use Saxon-HE version 9 or higher
    • Output tab
      • Save As: Provide a file name to save

      • Check preferences (suggested: “Open in browser” and either “Open in Editor” or one of the “Show As” options)

Exercise 1

Create a transformation scenario:

  • named My Special Transformation
  • that transforms the ${currentFileURL}
  • using the generic_xslt.xsl
  • and save the results to your hard drive as ${cfn}.html
  • the result should also open in a Browser
  • but not show in the results view

 

10:30

Transforming XML on the Command Line

 

  • Install command-line saxon processor and add to path
  • Save XML and XSLT to the same directory
    • Convenient for one-off projects
    • For reuse of XSLT, save it to a library directory, but you then need to specify the path
  • Syntax
    • Create an alias or batch file (if not done by installation)
      • Not required, but strongly recommended for convenience

      • Set the name saxon to, e.g.,
        java -jar c:\bin\saxon_9.2\saxon9he.jar

    • saxon -o:outputfilename -s:xmlfilename -xsl:xsltfilename
    • saxon -o:sample.html -s:input.xml -xsl:transform-input.xsl
      • Other command-line switches are available

 

The XSLT Template Paradigm

Nuts and bolts of an

XSLT stylesheet

  • xsl:stylesheet

  • xsl:output

  • xsl:template

  • xsl:apply-templates

  • Creating the XHTML output framework

<xsl:stylesheet>

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns="http://www.w3.org/1999/xhtml"
    xpath-default-namespace="http://www.tei-c.org/ns/1.0"
    version="2.0">
  • Wrapper element that contains the entire stylesheet
  • xmlns:xsl="" always required
  • xmlns="" namespace of output (XHTML in this case)
  • xpath-default-namespace="" 
    • optional, but useful when transforming TEI
    • XPaths will be assumed to be TEI unless otherwise specified
  • version="2.0" required (2.0 much better than 1.0, there is now a 3.0 as well)

<xsl:output>

<xsl:output method="xhtml" indent="yes" encoding="utf-8"/>
  • Empty element ; describes output method and encoding
  • method="" may be:
    • xhtml (If creating HTML, this one is best)
    • xml
    • text
    • html 
  • indent="" Set to yes to wrap long lines and indent nested elements for ease in reading the output

<xsl:template match="XPath">

Contains instructions to be executed when the template fire:

<xsl:template match="div">

The match attribute specifies an XPath to which the template will be applied. This examples matches any <div> element.

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

When you encounter a <div>, create a <p>. Then process the contents of the <div>, putting any output of those template rules inside the <p>.

Not Matching a Node

You can also choose not to match a node, that is stop any further rules applying within it:

 

<xsl:template match="teiHeader" />

Can you think of why you might want to stop processing parts of a file?

<xsl:apply-templates select="XPath"/>

  • Usually an empty element.
  • The select attribute identifies the XPath of the nodes to process at this point.
  • What process means is determined by the template rule for the node selected.
    • <xsl:apply-templates select="head"/>
      • Process all of the head children of the context node
    • <xsl:apply-templates select="//body/div/div/sp[1]"/>
      • Process the first speech (sp) of the first div that is immediately under another div immediately under the body. In Hamlet, this is the first speech of each scene.
    • <xsl:apply-templates/>
      • Process all of the children of the context node.

Creating the XHTML Output Framework

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns="http://www.w3.org/1999/xhtml"
  xpath-default-namespace="http://www.tei-c.org/ns/1.0"
  version="2.0">
  <xsl:output method="xhtml" indent="yes"/>
  <xsl:template match="/">
    <html>
      <head>
        <title>Title goes here</title>
      </head>
      <body>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <!-- other template rules go here -->

</xsl:stylesheet>

Exercise 2

  • Open the hamlet.xml file
  • Open the generic_xslt.xsl file
  • Switch to the XSLT Debugger view
  • Together we will work through adding some more simple templates 

Exercise 2: Possible Answer

 <xsl:template match="teiHeader | front"/>
    
  <xsl:template match="body/div">
    <div><xsl:apply-templates/></div>
  </xsl:template>
  
  <xsl:template match="body/div/head">
    <h1><xsl:apply-templates/></h1>
  </xsl:template>
  
  <xsl:template match="body/div/div/head">
    <h2><xsl:apply-templates/></h2>
  </xsl:template>
  
  <xsl:template match="sp"><p><xsl:apply-templates/></p></xsl:template>
  <xsl:template match="speaker"><b><xsl:apply-templates/></b><br/></xsl:template>
  <xsl:template match="l"><xsl:apply-templates/><br/></xsl:template>
  <xsl:template match="div/stage"><p><xsl:apply-templates/></p></xsl:template>
  <xsl:template match="sp/stage">
    <span  class="stage"><xsl:apply-templates/></span>
  </xsl:template>

Built-In Templates

(And How To Override Them)

What's Built In

  • Where do we start?
    • at the document node (itself the parent of the root element)
  • What happens when there is no applicable template rule?
    • The built-in defaults are applied. 
  • What happens where there is exactly one applicable template rule?
    • Er ... it gets applied.
  • What happens when more than one template rule would seem to apply?
    • Built-in priority 
    • User-specified priority 

Launching a Transformation

  • Processing starts at the document node, which sits above the root element (the top-level element that contains all other nodes).
  • In a TEI document, the root element is normally TEI and in its namespace.
  • Built-in template rules will walk the tree and process the entire document.
  • Override the built-in rules by specifying your own to:
    • Perform non-built-in processing
    • Cause items not to be processed

Built-In Template Rules

  • Element: process children (elements and text nodes), applying matching template rules (built-in or specified)
  • Attribute: do nothing (no output)
  • Text: output string value  

Built-In Priority

  • Built-in priority: the rule with the most specific match is applied

<xsl:template match="div">
  <act>
    <xsl:apply-templates/>
  </act>
</xsl:template>

<xsl:template match="div/div">
  <scene>
    <xsl:apply-templates/>
  </scene>
</xsl:template>

User-Specified Priority

  • Built-in priorities range from -0.5 to 0.5
  • User-specified priorities may be any number (including negative)
<xsl:template match="div" priority="10">
  <act>
    <xsl:apply-templates/>
  </act>
</xsl:template>

Overriding Templates

  • The built-in rule for any element is to process its child elements and text nodes.
  • The built-in rule for the document node would process its single root element. In the case of a TEI document, that’s usually the single TEI node.
<xsl:template match="/">
  <xsl:apply-templates/>
</xsl:template>
<xsl:template match="/">
  <xsl:apply-templates select="//head"/>
</xsl:template>
  • In the example above, instead you grab all head elements everywhere. No other nodes are processed unless they’re inside a head.

Exercise 3

Playing with Template Priority:

  • open the hamlet.xml file and
  • the template_priority.xsl file
  • switch to the oXygen XSLT Debugger
  • experiment with template priority:
    • remove the select="//head" to see the effect, then put it back
    • add a priority="-10" to the <xsl:template> to see the effect, then remove it
    • add a new template for 'head' with a higher priority 

XSL Variables

About XSL Variables

  • XSL variables allow you to store values (strings, integers etc.) so that you can re-use them easily.
  • XSL variables are NOT MODIFIABLE (in a typical programming sense). They are variable for each run of a template.
  • Once you set the value, you're stuck with it for that run of the template / for-loop / or stylesheet depending on its context
  • This is different from other programming languages, where variables can have different values assigned to them and then be changed in the same context

Creating an XSL Variable

<xsl:variable name="institutionName" select="'Newcastle University'" />
  • The required name attribute gives us a way to refer to the variable later, using a dollar sign: $institutionName
  • The optional select attribute specifies a value that is assigned to the variable.
  • This value is an XPath so needs 'single quotes' inside if you want to just provide a string
  • The variable is local to its current context, so if declared outside an <xsl:template> it is global

Creating an XSL Variable

<!-- A Hard Coded Integer -->
<xsl:variable name="myAge" select="21" />

<!-- A Calculation -->
<xsl:variable name="numCards" select="13 * 4 + 2" />

<!-- A Hard Coded String -->
<xsl:variable name="myName" select="'James Cummings'" />

<!-- An Element From The Input Document Via XPath -->
<xsl:variable name="docTitle" select="/TEI/teiHeader/titleStmt/title[1]" />
<xsl:variable name="docAuthor" select="/TEI/teiHeader/titleStmt/author[1]" />

<!-- A Set Of Elements From The Input Document -->
<xsl:variable name="docAuthors" select="/TEI/teiHeader/fileDesc/titleStmt/author" /> 

<!-- An XML fragment you create -->
<xsl:variable name="authorityList">
 <list>
   <item n="1">First item</item>
   <item n="2">Second item</item>
   <item n="3">Third item</item>
   <item n="4">Fourth item</item>
 </list>
</xsl:variable>

Using an XSL Variable

<!-- If your variable contains an atomic value such as a 
string or a number, you can output it with xsl:value-of: -->
My name is <xsl:value-of select="$myName"/>.

<!-- You can do XPath calculations with the value of your variable: -->
In ten years I shall be <xsl:value-of select="$myAge + 10"/>.

<!-- If your variable contains an element, you can treat it 
just like an element. -->
This book was written by
<xsl:value-of select="$docAuthor/persName/forename"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$docAuthor/persName/surname"/>.

Using an XSL Variable (2)

<!-- If your variable contains a sequence of elements, 
you can treat it just like any sequence. -->

<xsl:template match="/TEI/teiHeader">
<p>This book was written by the following people:</p>
<ul>
  <xsl:apply-templates select="$docAuthors"/>
</ul>

<p>But <xsl:value-of select="$docAuthors[1]/persName/surname"/> 
is listed first.</p>
</xsl:template>

<xsl:template match="author">
  <li>
    <xsl:text>author #</xsl:text>
    <xsl:value-of select="position()"/>
    <xsl:text> is </xsl:text>
    <xsl:apply-templates select="./persName/forename"/>
    <xsl:text> </xsl:text>
    <xsl:apply-templates select="./persName/surname"/>
    <xsl:text>
    </xsl:text>
  </li>
</xsl:template>

Reasons To Use XSL Variables 

XSL variables can be very convenient in a lot of different circumstances:

  • When you have a static value that you want to use many times in your output.
    • For instance, you might need to output the title of a document many times in different places. If you put it in an XSL variable, and the title changes, you only need to change it in one location in your XSLT.
  • To avoid running the same costly function many times during a transformation.
    • For instance, you may want to use the value of the current date hundreds of times. Instead of calling current-date() every time, you can call it once, and store the value in a variable.
  • When you need to disambiguate contexts in a complex XPath expression

Reasons To Use XSL Variables (2)


<xsl:variable name="maxGeos" select="max(//place/count(descendant::geo))"/>
<xsl:value-of select="//place[count(descendant::geo) = $maxGeos]/@xml:id"/>


<xsl:for-each select="ref">
  <xsl:variable name="targetBiblId" select="substring-after(@corresp, '#')" />
  <xsl:value-of select="//div[@xml:id='bibliography']//bibl[@xml:id=$targetBiblId]/title"/>
</xsl:for-each>
  • Variables can be extremely useful in breaking up and simplifying tasks
  • You can store any node or nodeset in a variable
  • You can xsl:apply-templates to a variable
  • You can process a whole document in a variable, and then re-process that variable as a document 

12:30

XSL Constructors

XSLT Constructors

  • Normally, to create a result element in your output, you just type it literally:
    <div class="chapter"> [...] </div>
  • However, there are some circumstances when you can't do that. For instance, the name of the element you want to create may change based on the XML input. For instance:
    • If you're processing a TEI <list> element, you may need to produce either <ul> or <ol> in your output, depending on whether it's an unordered or an ordered list:
    • <list type="bulleted"> → <ul>
      <list type="ordered">  → <ol>
  • We can handle this with the XSLT element constructor.

Element Constructors

<!-- Simple Element Constructor -->
<xsl:element name="h2">
  [...contents of the h2 element...]
</xsl:element>


<!-- Complex Element Constructor -->
<xsl:element name="{if (@type='ordered') then 'ol' else 'ul'}">
  [...]
</xsl:element>

<!-- The XPath logic in an attribute is contained 
in {curly braces}. Be careful with nested quotes! -->

How else could you achieve this (i.e. with multiple xsl:template elements)?

Attribute Constructors

Just as you can create an element with a constructor, you can also create an attribute:

<xsl:attribute name="class">
  [... value of the class attribute ...]
</xsl:attribute>

You might do this if you need to generate the attribute value dynamically based on the content.

Like xsl:variable, the value can be given as the value of a select attribute, or as the content.

<xsl:template match="div | p | ab">
  <div>
    <xsl:attribute name="class" select="local-name()"/>
    <xsl:apply-templates/>
  </div>
</xsl:template>

Exercise 4

Element Constructors:
Inside a template matching a “div” element, write a constructor for an output element also called “div” with an attribute called "class", whose value is "chapter".

 

<xsl:template match="div">
<xsl:element name="div">
  <xsl:attribute name="class">chapter</xsl:attribute>
</xsl:element> 
</xsl:template>
<xsl:element name="div">

    [What can go in this location?]
    
  <xsl:attribute name="class">chapter</xsl:attribute>

    [What can go in this location?]
    
</xsl:element>

Only Attribute Constructors!

Lots of things can go here, including <xsl:apply-templates>, other attribute  constructors, and other element constructors.

XSLT Constructors

  • When would I ever actually use this?
  • There is a use-case in the Hamlet transformation:
<xsl:template match="head">
  <h2>
    <xsl:apply-templates/>
  </h2>
</xsl:template>
  • This matches head elements for both Act headings and Scene headings.
  • Why might that be a potential problem?

Copying and Identity Transforms

Copy Constructors

  • Sometimes, you simply want to copy some of the XML in your tree to the output document.
  • This is often the case when you're using XSLT to make small modifications to your XML files.
  • You can use xsl:copy and xsl:copy-of to do this.
  • Although they look similar, they have completely different effects.

<xsl:copy-of/>

  • <xsl:copy-of /> is the simplest of the two copy constructors.
  • It makes a complete copy of the node, including its attributes and descendants, in the output tree.
  • For instance, if your input is this:
  <p xml:id="para_1">
      This is my first paragraph.
  </p>
 
  • and your template is this:
<xsl:template match="p[@xml:id='para_1']">
      <xsl:copy-of select="." />
</xsl:template>
  • then your output will be this:
 <p xml:id="para_1">
      This is my first paragraph.
 </p>

<xsl:copy/>

  • <xsl:copy/> copies only the node itself, and not its descendants or attributes, to the output tree.
  • For instance, if your input is this:
<p xml:id="para_2">
    This is my second paragraph.
</p>
  • and your template is this:
<xsl:template match="p[@xml:id='para_2']">
      <xsl:copy />
</xsl:template>
  • then your output will be this:
<p></p>
  • Only the node (p) is copied. Its xml:id attribute and text content are ignored.

Identity Transforms

  • You're probably wondering why copy constructors would be useful. After all, usually we're converting our XML into something else such as XHTML, so we can't just copy TEI nodes to the output document.
  • However, we also use XSLT to edit, amend or improve existing TEI documents, transforming TEI into TEI, with minor changes.
  • This is done through the so-called identity transform(ation). One version of it looks like this:
<xsl:template match="node()|@*">
  <xsl:copy>
    <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
</xsl:template> 

Can you figure out what this is doing?

Identity Transform Example

  • Imagine you have an old TEI file which has a lot of div0 and div1 elements in it:
[...]
<text>
  <body>
    <div0>
      <head>Title...</head>
      <div1><p>Some stuff...</p>[...]</div1>
      <div1><p>Some more stuff...</p>[...]</div1>
    </div0>
  </body>
</text>
[...] 
  • You have, quite sensibly, decided that both div0 and div1 should be turned into plain div elements. We can do this with an identity transform.

Identity Transform Example 2

<xsl:template match="node()|@*" priority="-1">
  <xsl:copy>
    <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>
             
<xsl:template match="div0 | div1">
  <div>
    <xsl:apply-templates select="node()|@*"/>
  </div>
</xsl:template>

Exercise 5

Identity Transform

  • Load the hamlet.xml file
  • Load identity_transform.xsl
  • Together let's write a new stylesheet based on this which adds the total number of lines spoken by this character.  
<role xml:id="Claudius">Claudius</role> 

<!-- becomes -->

<role xml:id="Claudius">Claudius
  (Lines: 528)
</role>

Exercise 5 (continued)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xpath-default-namespace="http://www.tei-c.org/ns/1.0">

  <xsl:template match="node()|@*" priority="-1">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="role">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
      <xsl:variable name="currentRoleId" select="@xml:id"/>
      (Lines: <xsl:value-of 
     select="count(//l[parent::sp[contains(@who, $currentRoleId)]])"/>)
    </xsl:copy>
  </xsl:template>
            
</xsl:stylesheet>

Exercise 5: Questions

  • Q: Why did we include 
    xpath-default-namespace="http://www.tei-c.org/ns/1.0"?
  • A: So we don't have to include the TEI namespace or a prefix every time we reference a TEI element.
  • Q: Why did we use a variable to store the xml:id of the role?
  • A: Because if we didn't, it would be difficult to retrieve the xml:id during the subsequent line count, because the line-count XPath places us in a different context (the context of the l elements we're counting).
  • Q: Why did we use [contains(@who, $currentRoleId)] instead of [@who = $currentRoleId]?
  • A: Because some lines are spoken by more than one speaker (Cornelius1 and Voldemar, for instance).

Template Modes

Template Modes

  • You know how to write templates to process nodes. Typically, you have one template to process head nodes, another to process p nodes, and so on.
  • However, sometimes you want to process the same node in different ways.
  • One effective way to do this is using the mode attribute.
<body>
  <div type="chapter" n="1">
    <head>Chapter 1: How it all started</head>
    <p>[...]</p>[...]<p>[...]</p>
  </div>
  <div type="chapter" n="2">
    <head>Chapter 2: What happened next</head>
    <p>[...]</p>[...]<p>[...]</p>
  </div>
  <div type="chapter" n="3">
    <head>Chapter 3: Things that subsequently transpired</head>
    <p>[...]</p>[...]<p>[...]</p>
  </div>
  [...]
</body>

Two Templates For Same Node

Using modes, we can write two different templates for the same node:

<!-- Template for head element -->
<xsl:template match="head">
  <h2 id="chapter_{parent::div/@n}"><xsl:apply-templates /></h2>
</xsl:template>            
          
<!-- Template for head element for table of contents -->
<xsl:template match="head" mode="toc">
  <li>
    <a href="#chapter_{parent::div/@n}"><xsl:apply-templates /></a>
  </li>
</xsl:template>    
  • The first template, which processes the head at the beginning of the chapter, creates an h2 element with a unique id.
  • The second template, which processes the head element in the context of a table of contents, creates a list item (li) element containing a link which points to the chapter heading.

Creating the Table of Contents

<div id="tableOfContents">
  <ul class="tocList">
    <xsl:apply-templates select="//div[@type='chapter']/head" mode="toc"/>
  </ul>
</div>

More On Modes

  • One template can serve multiple modes:
    <xsl:template match="head" mode="toc index abstract">
        
  • There are two special mode values you can apply to an xsl:template:
    • #all (applies to all modes)
    • #default (applies when no mode attribute is specified)
  • There is one special mode value you can apply to an xsl:apply-templates:
    • #current (apply templates using whatever mode is current in the processing cascade)
  • These only really become useful when you are using lots of modes throughout a transformation.

Exercise 6

Modes

  • Open the file copperfield.xml
  • Open the simple XSLT copperfield.xsl
  • Set up a transformation scenario or load into the XSLT Debugger
  • We'll work together to create a Table of Contents

Exercise 6: Answers

<!-- In root template -->
<p id="tableOfContents">
    <ul class="tocList">
      <xsl:apply-templates select="//div[@type='chapter']" mode="toc"/>
    </ul>
</p>


<!-- Add to body/div template -->

  <div id="{concat('chapter', @n)}">
  [...]

<!-- As a new template -->


  <xsl:template match="div" mode="toc">
    <li><a href="{concat('#chapter', @n)}">
      <xsl:value-of select="head"/></a></li>
  </xsl:template>

Exercise 7

More Modes (Optional Extra Exercise)

  • In the same David Copperfield file, you'll see that at the end of each chapter, there's a note element.

    Your task is to turn the notes into footnotes, and put numbers in the text which link to the footnotes.

  • First create a normal template for note which turns the note into a number.
  • Turn the number into a link.
  • Create another template for note, with a different mode, which outputs the contents of the note inside a li element.
  • Add a ul element to the root template before the end of the body.
  • Inside the ul element, apply templates to //note with the new mode.

15:00

Exercise 7: Answers

15:00

<!-- add to root template -->         
 <ul>
    <xsl:apply-templates select="//note" mode="footnotes"/>
 </ul>
     

<!-- later in document -->
<xsl:template match="note">
    <xsl:variable name="num">
     <xsl:number count="note" from="body" level="any"/>
    </xsl:variable>
    <a href="{concat('#note', $num)}"><sup><xsl:value-of select="$num"/></sup></a>
</xsl:template>
  
<xsl:template match="note" mode="footnotes">
    <xsl:variable name="num">
     <xsl:number count="note" from="body" level="any"/>
    </xsl:variable>
    <li id="{concat('note', $num)}"><xsl:apply-templates/></li>
</xsl:template>
  

Conditionals and Looping

XSL Conditionals: ifs, chooses, whens and otherwises

  • Almost all programming languages have conditional branching structures.
  • XSL has two: xsl:if and xsl:choose.
  • XPath also has an if-then-else structure that you may or may not know about!

Using xsl:if

  • A simple example: using xsl:if to pluralize "author" if there are multiple authors

Author<xsl:if test="count($docAuthors/author) gt 1">s</xsl:if>: 
<xsl:for-each select="$docAuthors/author">
  <xsl:value-of select="forename" /> <xsl:value-of select="surname" /><br/>
</xsl:for-each>
  • The test attribute contains an XPath expression which evaluates to true() or false().
  • What is inside the xsl:if tag is only implemented if it evaluates to true().

Using xsl:choose

  • Sometimes you need to handle two or more conditions. This is done with xsl:choose:
<div>      
  <head>DHSI Dress Code</head>
  <p><xsl:choose>
      <xsl:when  test="surname='Bauman'">
                tie, no footwear
      </xsl:when>
      <xsl:when  test="surname='Cummings'">
                footwear, no tie
      </xsl:when>
      <xsl:otherwise>
                unpredictable
      </xsl:otherwise>
  </xsl:choose></p>
</div> 
  • The processor looks at each xsl:when in turn; when it finds one whose test evaluates to true(), it processed that one, and then exits the xsl:choose.
  • If none are true, it processes xsl:otherwise (assuming there is one).

Using XPath if-then-else

  • If your condition is very simple, and the processing you want to do as a result of it does not involve creating tags and attributes, then you can just use an if-then-else structure in XPath:
<xsl:value-of 
      select="
            if (count($docAuthors/author) gt 1) then 
                'Authors: '
            else 
                'Author: '
   " />
           

Exercise 8

XSL Conditionals

  • Open the places.xml file and look at it
  • Open the conditionals.xsl file and look at it
  • Show these in the XSLT Debugger View
  • Follow the instructions in the comments of the XSLT file

Exercise 8

A possible solution

<xsl:template match="/">
  <xsl:for-each select="//place">
    <xsl:value-of select="placeName" />
    <xsl:choose>
      <xsl:when test="count(location/geo) eq 1"> (point)</xsl:when>
      <xsl:when test="location[@type='path']"> (path)</xsl:when>
      <xsl:otherwise> (polygon)</xsl:otherwise>
    </xsl:choose>
    <xsl:text>
</xsl:text>
  </xsl:for-each>
</xsl:template>
          

XSL Looping

for (i = 0; i < 9; i++){
   alert('i = ' + i);
}

Most programming languages have a looping construct:

XSLT has something similar:

<xsl:for-each select="//author">
  <xsl:value-of select="surname"/>
  <xsl:text>, </xsl:text>
  <xsl:value-of select="forename"/>
</xsl:for-each>

Looping vs Templates

<xsl:for-each select="//author">
  <xsl:value-of select="surname"/>
  <xsl:text>, </xsl:text>
  <xsl:value-of select="forename"/>
</xsl:for-each>

You might wonder how this:

is different to: 

<xsl:template match="author">
  <xsl:value-of select="surname"/>
  <xsl:text>, </xsl:text>
  <xsl:value-of select="forename"/>
</xsl:template>
[...]
<xsl:apply-templates select="//author">
          

Sorting While Looping

<xsl:for-each select="//author">
  <xsl:sort select="surname"/>
  <xsl:value-of select="surname"/>
  <xsl:text>, </xsl:text>
  <xsl:value-of select="forename"/>
</xsl:for-each>
<xsl:for-each select="//author">
  <xsl:sort select="surname" lang="is"/>
  <xsl:value-of select="surname"/>
  <xsl:text>, </xsl:text>
  <xsl:value-of select="forename"/>
</xsl:for-each>
  • You can also sort according to the sort rules of another language, using the lang attribute:

Exercise 9

Looping

  • Open the hamlet.xml file
  • Open the looping.xsl file
  • Run the transformation and look at the result
  • Add sorting to the <xsl:for-each>
  • Look at the result in the XSLT Debugger or with a Transformation Scenario 
  • What else could you sort by?

Named Templates and Functions

Named Templates

  • So far you've looked at templates that have a match attribute:
<xsl:template match="item">
  <li><xsl:apply-templates/></li>
</xsl:template>
  • However, there is another way to invoke a template: we can give it a name, and call it using that name.

Named Templates

First you give it a name:

<xsl:template name="dateToday">
  <p>
Today's date is: <xsl:value-of select="current-date()"/>
</p>
</xsl:template>

then you call it:

<xsl:call-template name="dateToday"/>

This is obviously useful; you can write a particular piece of logic once, and call it from multiple locations.

Passing Parameters

  • Named templates are a little like functions in conventional programming languages.
  • As with programming functions, you can also pass parameters to named XSLT templates.

Here's a named template with a parameter:

<xsl:template name="greetSomeone">
  <xsl:param name="whoToGreet"/>
  <p>Hello <xsl:value-of select="$whoToGreet"/>!</p>
</xsl:template>

And here's how you call it:

<xsl:call-template name="greetSomeone">
  <xsl:with-param name="whoToGreet">Fred</xsl:with-param>
</xsl:call-template>

Recursive Templates (1)

Let's say we have a simple template that outputs a number:

<xsl:template name="showNumber">
  <xsl:param name="theNumber" select="0"/>
  <xsl:value-of select="$theNumber"/>
</xsl:template>

And we call it as so:

<xsl:call-template name="showNumber">
  <xsl:with-param name="theNumber" select="3"/>
</xsl:call-template>
              

The result should be: 3

Recursive Templates (2)

But let's add a new bit to that template:

<xsl:template name="showNumber">
  <xsl:param name="theNumber" select="0"/>
  <xsl:value-of select="$theNumber"/>
  <xsl:if test="$theNumber gt 1">
    <xsl:call-template name="showNumber">
      <xsl:with-param name="theNumber" select="$theNumber - 1"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>

Now what should our template do when called with 3 as a parameter?

Exercise 10

Named Templates

  • Write a named template which:

  • is called "makeLink".
  • has a parameter called "href" with a default value of "http://www.tei-c.org".
  • has a parameter called "linkText" with a default value of "Click here".
  • creates an XHTML anchor element using the href parameter and the link text.
  • Add this template to one of your XSLT files, and try calling it from elsewhere in that file.  Solution:

<xsl:template name="makeLink">
  <xsl:param name="href">http://www.tei-c.org</xsl:param>
  <xsl:param name="linkText">Click here</xsl:param>
  <a href="{$href}"><xsl:value-of select="$linkText"/></a>
</xsl:template>

XSLT Functions

  • XSLT has <xsl:function> as well which enables you to write your own functions

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:jc="http://james.blushingbunny.net/ns.html"
  version="2.0"
  exclude-result-prefixes="jc">
  <xsl:output method="text"/>
  
  <!-- declare your function -->  
  <xsl:function name="jc:reverse" as="xs:string">
    <xsl:param name="sentence" as="xs:string"/>
    <xsl:sequence  
      select="if (contains($sentence, ' '))
        then concat(jc:reverse(substring-after($sentence, ' ')), ' ',
        substring-before($sentence, ' '))
       else $sentence"/>
    </xsl:function>
    
  <!-- Use the function -->
  <xsl:template match="/">
     <xsl:value-of select="jc:reverse('DOG BITES MAN')"/>
  </xsl:template>
  
</xsl:stylesheet>

Using Data From Another Document

What We Know So Far:

What We Will Now Learn:

The doc() function

  • The doc() function takes a URI parameter as a string, pointing to the external XML file to use
<!-- Same Folder -->
doc('personography.xml')

<!-- Relative file path -->
doc('../people/personography.xml')

<!-- Somewhere else entirely -->
doc('http://mapoflondon.uvic.ca/literary_personography.xml')
  • It's helpful to load the document into a variable at the beginning of your transformation so it's easy to refer to it anywhere.
  • If you want to check whether the document exists before loading it, use doc-available():
<xsl:variable name="personography" 
select="if (doc-available('tiny_personography.xml')) 
then doc('tiny_personography.xml') else ()"/>

Multiple Output Files From One Input File

<xsl:result-document>

<xsl:result-document href="foo.html">
 <!-- add instructions to generate document content here -->
</xsl:result-document>
  • xsl:result-document enables output to a different file than the specified output
  • it must have an href attribute saying what the output file should be called, but this can be a variable if looping
  • Often used to 'burst out' many output files from a single XML source. (e.g. a single script to create an entire pre-generated HTML website.

<xsl:result-document>

<xsl:result-document href="foo.html">
 <!-- add instructions to generate document content here -->
</xsl:result-document>
  • xsl:result-document enables output to a different file than the specified output
  • it must have an href attribute saying what the output file should be called, but this could contain an escaped XPath (e.g. a variable) if looping
  • it may have a method attribute to indicate the output method

Multiple Result Documents

<xsl:template match="/">      
    <xsl:for-each select="//body/div">
      <xsl:result-document method="xml" href="{@xml:id}.xml">
        <xsl:copy-of select="."/>
      </xsl:result-document>
    </xsl:for-each>
</xsl:template>
  • To get multiple result documents you can iterate over a node-set outputting a document each time
  • How would you output each act of the hamlet.xml file to a new file? Let's try to do it together. Solution:
  • What would you put in place of the xsl:copy-of if you wanted to output html from other templates? 

Multiple Input Files From One Stylsheet 

We know how to use doc()

More Input Documents?

No Main Input Document?

The collection() Function

  • The collection() function takes a URI parameter, which can point to files in three ways:

  • A file containing a list of documents (a "catalogue").
  • A folder containing documents.
  • A folder along with a "filter" expression which limits the documents retrieved.

Using a Catalogue File

  • You can use a file to list the files and then point to it with the collection() function
<collection>
  <doc href="chapters/chap1.xml"/>
  <doc href="chapters/chap2.xml"/>
  <doc href="chapters/chap3.xml"/>
  <doc href="chapters/chap4.xml"/>
</collection>
<xsl:variable name="documents" select="collection('catalogue.xml')"/>

Pointing to a Directory

  • You can use the collection() function to point to a directory:
<xsl:variable name="documents" select="collection('chapters')"/>
  • This will load all the documents in the directory called "chapters" which is a sibling of the XSLT file itself.

Directory with a Filter

  • You can use the collection() function to point to a directory (and you can filter this)
<xsl:variable name="documents" 
select="collection('.?select=*.xml')"/>
  • There are other parameters that can be given here (recurse and on-error)
<xsl:param name="path2collection">../foo/</xsl:param>
<xsl:variable name="path">
  <xsl:value-of
   select="concat('../',$path2collection,'?select=*.xml;recurse=yes;on-error=warning')"/>
</xsl:variable>

<xsl:variable name="docs" select="collection($path)"/>

Exercise 11

Using collection()

  • Open the file collection.xsl
  • Delete any files in the same folder that are not wel-formed XML (e.g. XSLT and XML files are ok if you haven't broken them)
  • Switch to the XSLT Debugger
  • Make sure HTML output is selected
  • Nifty Trick: Select collection.xsl as XML and XSL so it is processing itself (on the commandline you can name a template to start with).

Conclusion

Intensive Introduction to XSLT

By James Cummings

Intensive Introduction to XSLT

An Intensive Introduction to XSLT for Transforming XML

  • 3,389