Scott Penrose

XSLT Tutorial Part 2

Scott is an expert software developer with over 30 years experience, specialising in education, automation and remote data.

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

  • XSLT
  • Talk