Continuation of XSLT Tutorial
Named Templates
You can give an xsl:template a name and then call it.
Normally we do the following:
<xsl:apply-templates select="blah[@id = '3']"/>
Which will find any matching template, which could be...
<xsl:template match="blah">... <xsl:template match="blah[@id]"> <xsl:template match="*"> ...
Instead we can have named templates for specific calling. It is not used as often as matched templates, but can be useful.
<xsl:template match="/> <xsl:call-template name="doit"/> </xsl:template> <xsl:template name="doit"> <hello>World...</hello> </xsl:template>
The Catch All
We often want to write XSL as a filter - something which acts on some parts of the document and then lets the rest pass through. One of the easiest ways to do that is to write in a catch all, that copies through the rest of the content.
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
The match above will find any attribute (@*) or any node (better than * as it will catch evertyhing, including text).
It will then instruct to copy that element through to the next, and recursively copy the rest. Note that because it calls apply-template again, it will still actively execute your other templates.
Here is an exmample which updates owners to email address in our bugs.xml
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
>
<xsl:template match="owner">
<a href="mailto:{.}@editure.com"><xsl:value-of select="."/>@editure.com</a>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This also demonstrates a neat trick on adding content within an attribute. Add "{}" around what you would normally use in a select in an attribute. E.g.
<a href="http://rt.internal.schools.net.au/goto?id={@id}">
<xsl:value-of select="@id"/>
</a>
Mode Templates and the Catch All
xhtml2to2 is a good example of how mode can be used, in combination with a catch all for use in converting XHTML2 to XHTML1.
e.g.
<xsl:template match="@*|node()"><xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy></xsl:template>
<xsl:template match="h:nl"><ul><xsl:apply-templates select="." mode="href"/></ul></xsl:template>
<xsl:template match="h:name"><li class="name"><xsl:apply-templates select="." mode="href"/></li></xsl:template>
<xsl:template match="h:line">
<span><xsl:apply-templates select="." mode="href"/></span><br/>
</xsl:template>
Advanced Attribute Handling
What if you want to add a attribute conditionally to an element?
You can't really use the "{}" shortuct we have above.
<div class="@class"> ... </div>
The code above will always have a class= entry, even if it is just "" - this is sometimes not desirable - we may want to leave out the class attribute altogether.
<div>
<xsl:if test="@class">
<xsl:attribute name="class">
<xsl:value-of select="@class"/>
</xsl:attribute>
</xsl:if>
...
</div>
Warning - an xsl:attribute must be the first thing after an element that is output. You can use if, and choose, but you can't add other content first.
Once you use xsl:attribute, you can then put content inside our outside of it. You can have a xsl:choose which adds different attributes, or you can have a single attribute and have an xsl:choose inside that to select the entry. You can even call or apply templates.
Variables and Parameters
Variables
Variables are simply a named set of data that can be used within a context.
<xsl:template match="bug">
<xsl:variable name="email">
<xsl:value-of select="owner"/>@editure.com.au
</xsl:variable>
<a href="mailto:{$email}"><xsl:value-of select="$email"/></a>
...
</xsl:template>
As you can see, we can save ourselves from doing more complicated calculations if we have to use the same data multiple times. How do we know what we are getting?
- Elements start with no sigil.
- Attributes start with an "@"
- Variables and Parameters start with a "$"
You are not limited to just data, but also sets of data.
<xsl:variable name="all" select="bug"/> <xsl:for-each select="$all"> ... </xsl:for-each>
Parameters
Parameters can be defined by the calling method of a style sheet - e.g. Top Level parameters, or by a method of calling a template - call-template or apply-templates.
Top level templates must be defined outside of any templates, but inside the top level XML entry - xsl:stylesheet
<xsl:param name="show_image">1</xsl:param> <!-- Should we show the image ? --> <xsl:param name="show_description">1</xsl:param> <!-- Should we show the descriptions of each item --> <xsl:param name="max_items">10</xsl:param> <!-- How many items should we show -->
The above shows three parameters you can use (used as $show_image like variables) with default values. That means if the calling system (Perl XML::LibXSLT or similar) does not provide a show_image entry, then it will use the default deined here.
Passing parameters to templates is very useful.
<xsl:template match="bug">
<xsl:param name="strong" select="0"/>
<xsl:value-of select="@id"> -
<xsl:choose>
<xsl:when test="$strong = '1'">
<strong><xsl:value-of select="@title"/></strong>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@title"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Then to call the above we can do:
<xsl:with-param name="strong">
<xsl:if test="@severity = '3'">
1
</xsl:if>
</xsl:with-param>
Advanced XPath
First or Last entry
- position() = returns the position
- last() = matches the last
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
>
<xsl:strip-space elements="*"/>
<xsl:output method="text"/>
<xsl:template match="bugs">
<xsl:text>First:</xsl:text> <xsl:apply-templates select="bug[position() = 1]"/>
<xsl:text>Last: </xsl:text> <xsl:apply-templates select="bug[position() = last()]"/>
</xsl:template>
<xsl:template match="bug">
<xsl:text>* </xsl:text>
<xsl:value-of select="@id"/>
<xsl:text> - </xsl:text>
<xsl:value-of select="@title"/>
<xsl:text> - </xsl:text>
<xsl:value-of select="owner"/>
<xsl:text>(new line removed)</xsl:text>
</xsl:template>
</xsl:stylesheet>
Parent
bugsbugs.xml is a modified version of bugs, which has bugs inside bugs (have a look at attachments).
This style sheet now shows us if a parent of a bug has a hight severity.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
>
<xsl:strip-space elements="*"/>
<xsl:output method="text"/>
<!-- We need this - or the catch all will drop everything -->
<xsl:template match="bugs">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="bug">
<xsl:if test="parent::bug/@severity = '1'">
<xsl:text>PARENT is SEV 1 - </xsl:text>
</xsl:if>
<xsl:text>* </xsl:text>
<xsl:value-of select="@id"/>
<xsl:text> - </xsl:text>
<xsl:value-of select="@title"/>
<xsl:text> - </xsl:text>
<xsl:value-of select="owner"/>
<xsl:text>(new line removed)</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<!-- Catch all - we only want to display the bugs -->
<xsl:template match="@*|node()"/>
</xsl:stylesheet>
This has introduced us a set of new problems that needed to be solved:
- Now we have bugs in bugs, we want to recursively apply templates, so add xsl:apply-templates inside the main templae.
- Now that we are applying all templates, the nodes and other bigs get printed out that we are not using. So we add a catch all that gets rid of everything.
- But now we get no content - because bugs is was not a match, and was being caught by the catch all and lost.
- So add in a bugs template that applies templates.
- Phew...
Child and Sibling
The same rules apply for child - what are the rules?
- A special selector like parent or child - are added at the start of an XPath and then two colons - "::" - e.g. parent::bug.
- Why "::" becuase name space - like "xsl:" is single ":".
CGI Demo Kit
- Working CGI Kit
