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)
And many others...
<?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>
<?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
An XPath Introduction
(Using oXygen XML Editor)
Follow along while I:
Save As: Provide a file name to save
Create a transformation scenario:
10:30
Not required, but strongly recommended for convenience
Set the name saxon to, e.g.,
java -jar c:\bin\saxon_9.2\saxon9he.jar
xsl:stylesheet
xsl:output
xsl:template
xsl:apply-templates
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" encoding="utf-8"/>
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>.
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: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>
<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>
(And How To Override Them)
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>
<xsl:template match="div" priority="10">
<act>
<xsl:apply-templates/>
</act>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="//head"/>
</xsl:template>
Playing with Template Priority:
<xsl:variable name="institutionName" select="'Newcastle University'" />
<!-- 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>
<!-- 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"/>.
<!-- 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>
XSL variables can be very convenient in a lot of different circumstances:
<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>
12:30
<div class="chapter"> [...] </div>
<list type="bulleted"> → <ul>
<list type="ordered"> → <ol>
<!-- 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)?
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>
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.
<xsl:template match="head">
<h2>
<xsl:apply-templates/>
</h2>
</xsl:template>
<p xml:id="para_1">
This is my first paragraph.
</p>
<xsl:template match="p[@xml:id='para_1']">
<xsl:copy-of select="." />
</xsl:template>
<p xml:id="para_1">
This is my first paragraph.
</p>
<p xml:id="para_2">
This is my second paragraph.
</p>
<xsl:template match="p[@xml:id='para_2']">
<xsl:copy />
</xsl:template>
<p></p>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
Can you figure out what this is doing?
[...]
<text>
<body>
<div0>
<head>Title...</head>
<div1><p>Some stuff...</p>[...]</div1>
<div1><p>Some more stuff...</p>[...]</div1>
</div0>
</body>
</text>
[...]
<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>
Identity Transform
<role xml:id="Claudius">Claudius</role>
<!-- becomes -->
<role xml:id="Claudius">Claudius
(Lines: 528)
</role>
<?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>
<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>
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>
<div id="tableOfContents">
<ul class="tocList">
<xsl:apply-templates select="//div[@type='chapter']/head" mode="toc"/>
</ul>
</div>
Modes
<!-- 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>
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.
15:00
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>
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>
<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>
<xsl:value-of
select="
if (count($docAuthors/author) gt 1) then
'Authors: '
else
'Author: '
" />
XSL Conditionals
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>
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>
<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">
<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>
Looping
<xsl:template match="item">
<li><xsl:apply-templates/></li>
</xsl:template>
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.
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>
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
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?
Named Templates
Write a named template which:
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 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>
<!-- Same Folder -->
doc('personography.xml')
<!-- Relative file path -->
doc('../people/personography.xml')
<!-- Somewhere else entirely -->
doc('http://mapoflondon.uvic.ca/literary_personography.xml')
<xsl:variable name="personography"
select="if (doc-available('tiny_personography.xml'))
then doc('tiny_personography.xml') else ()"/>
<xsl:result-document href="foo.html">
<!-- add instructions to generate document content here -->
</xsl:result-document>
<xsl:result-document href="foo.html">
<!-- add instructions to generate document content here -->
</xsl:result-document>
<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>
The collection() function takes a URI parameter, which can point to files in three ways:
<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')"/>
<xsl:variable name="documents" select="collection('chapters')"/>
<xsl:variable name="documents"
select="collection('.?select=*.xml')"/>
<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)"/>
Using collection()