The Sysadmin Notebook  

Sitemap

XSLT 1.0 Notes

Transforming XML with XSLT 1.0

Contents

XSLT is a declarative programming language written in XML. XSLT's principle function is to convert an XML source tree into an XML result tree. The result tree can then be serialised to the required output format. The W3C XSL Version 1.0 recommendations can be found at W3C XSLT 1.0

On this page we'll use saxon to apply our transforms:

> saxon -o output.html input.xml input.xsl

Basic Structure

Top Bottom

The root element for XSLT documents is a <stylesheet> element or a <transform> element.

 <xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0" >

With the stylesheet element declared we can then use the elements from the XSL namespace and XPath functions and identifiers to create a layout for an XML document

<!-- xml source file -->
<message>Hello World</message>

<!-- xslt stylesheet -->
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="/">
  <html>
    <head>
      <title><xsl:value-of select="/message" /></title>
    </head>
    <body>
      <h3><xsl:value-of select="/message" /></h3>
      <p>The message was <xsl:value-of select="string-length(message)" /> 
	characters long.</p>
    </body>
  </html>
</xsl:template>

</xsl:stylesheet>

XSLT elements

Top Bottom
stylesheet
The root element for every XSLT document. Must contain a version attribute with a value of either "1.0" or "2.0".
template
<template> elements within the XSLT stylesheet are used to match the source tree by setting the match attribute to an XPath location within the source tree. Where matches are found, content from the source tree can be inserted into the template element using XPath identifiers, which can then written to the result tree.
apply-templates
Within a template element, other template elements can be included as functions using the <apply-templates> element. The 'select' attribute of the <apply-templates> element should be set to match an XPath value for content from the source tree. The XSLT processor will then look for template elements that match the selected element. Content from the source tree is then processed through the template and added to the result tree. Additionally, the 'mode' attribute can be set to allow the same content to be sent to different templates with the matching 'mode' attribute set.
value-of
The <value-of> element allows you to get value data from the source tree using XPath locations, to insert into the result tree. The mandatory 'select' attribute needs to be set to the XPath value for the data in the source tree.
copy
Copies a node to the result tree, but does not copy any descendant nodes or attributes of element nodes.
copy-of
Copies a node together with descendant nodes and the nodes attribute nodes
output
Allows specification of the output format by setting the 'method' attribute to the appropriate value.
if
Allows for conditional processing by setting the 'test' attribute.
choose
Allows for conditional processing by using nested <when test="some condition"> elements. The final nested condition can be an <otherwise> element.
for-each
Allows processing of all elements in a node-set
sort
Used to specify sort order for node-sets
param
Used to hold parameters passed in externally, either via a command-line or from a form or other pre-processing. The name attribute should be set, and the parameter can be used later in the stylesheet using $paramName.
variable
Used to store a constant value defined within the stylesheet. Two methods exist to define variables: by setting the 'select' attribute to the variable's value; or by including content between the elements start and end tags. In either case the 'name' attribute must be set, and used to access the variable's value in the form of $variableName.

Template and Apply-template

Top Bottom

Consider the following XML data file:

<commands>
  <command>
    <name>ls</name>
    <description>list directory contents</description>
  </command>
  <command>
    <name>ps</name>
    <description>report a snapshot of current process</description>
  </command>
  <command>
    <name>df</name>
    <description>report file system disk space usage</description>
  </command>
</commands>

We can apply the following XSLT stylesheet:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0" >

<xsl:template match="/">
  <html>
    <head>
      <title>Information about <xsl:value-of 
		select="count(/commands/command)" /> Linux commands</title>
    </head>
    <body>
      <h3>Information about <xsl:value-of 
		select="count(/commands/command)" /> Linux commands</h3>
      <ul><xsl:apply-templates select="/commands/command" mode="toc" /></ul>
      <xsl:apply-templates select="/commands/command" mode="contents" />
    </body>
  </html>
</xsl:template>

<xsl:template match="command" mode="toc">
  <li><xsl:value-of select="name" /></li>
</xsl:template>
<xsl:template match="command" mode="contents">
  <h3><xsl:value-of select="name" /></h3>
  <p><xsl:value-of select="description" /></p>
</xsl:template>

</xsl:stylesheet>

Which gives us the following when transformed by saxon:

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
   
      <title>Information about 3 Linux commands</title>
   </head>
   <body>
      <h3>Information about 3 Linux commands</h3>
      <ul>
         <li>ls</li>
         <li>ps</li>
         <li>df</li>
      </ul>
      <h3>ls</h3>
      <p>list directory contents</p>
      <h3>ps</h3>
      <p>report a snapshot of current process</p>
      <h3>df</h3>
      <p>report file system disk space usage</p>
   </body>
</html>

The stylesheet begins with a template element set to match the root element of our source tree. Then literal result elements (html, head, title) are added to the result tree. The first XSL element encountered is the <value-of> element. Since the select attribute of the element is set to "count(/commmands/command)", this element allows us to insert the count of command elements that are children of the commands element into the result tree. Then we want to use apply-templates to format the contents of the command nodes from the result tree. However we need to use two templates to process the commands node-set: once to build a rudimentary table of contents, and again to build the main content of the page. To call different templates using the same XPath location (/commands/command) we set the "mode" attribute in the apply-templates element, and a matching mode attribute in the template to apply. For each 'apply-templates' element, the select attribute is set to match command elements in the result tree that are children of the commands element. This makes the processor look for a template element with the match attribute set to 'command' and a matching 'mode' value. Each command element in the source tree is then processed by the matching template and inserted into the result tree. The "command" template with a mode of 'contents' inserts the value of the name element that is a child of the command element into a literal <h3> tag. Similarly, it inserts the value of the description element that is a child of the command element into a literal <p> tag.

Attribute, Element and Copy

Top Bottom

Consider the following XML data file:

<commands>
  <command>
    <name>ls</name>
    <description>list directory contents</description>
  </command>
  <command>
    <name>ps</name>
    <description>report a snapshot of current process</description>
  </command>
  <command>
    <name>df</name>
    <description>report file system disk space usage</description>
  </command>
</commands>

We can apply the following XSLT stylesheet:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	version="1.0" >
<xsl:template match="/">
  <commands>
  <xsl:apply-templates match="/commands/command" />
  </commands>
</xsl:template>

<xsl:template match="command">
  <xsl:copy>
    <xsl:attribute name="name"> <xsl:value-of select="name" /> 
    </xsl:attribute>
    <xsl:attribute name="description"> <xsl:value-of select="description" />
    </xsl:attribute>
  </xsl:copy>
</xsl:template>
</xsl:stylesheet>

which produces:

<commands>
  <command name="ls" description="list directory contents"/>
  <command name="ps" description="report a snapshot of current process"/>
  <command name="df" description="report file system disk space usage"/>
</commands>

The first template element is set to match the root of the XML source file. Following this a literal <commands> element is declared and the contents are filled in by the results of processing the 'command' template. The 'command' template uses the XSL 'copy' instruction to copy the current element (command) to the result tree. The XSL attribute command is used to add two attributes to the current element in the result tree, whose values are assigned the values of the name and description child elements from the source tree for the current command element. The result of applying the transformation is to produce another XML document from the original source XML document.

We can reverse the process to produce the original XML source file from the result file using the following stylesheet:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	version="1.0" >
<xsl:template match="/">
  <commands>
  <xsl:apply-templates match="/commands/command" />
  </commands>
</xsl:template>

<xsl:template match="command">
<command>
    <xsl:element name="name"> <xsl:value-of select="@name" /> 
    </xsl:element>
    <xsl:element name="description"> <xsl:value-of select="@description" />
    </xsl:element>
</command>
</xsl:template>
</xsl:stylesheet>

This time the 'command' template element declares a literal <command> element. We could use the 'copy' instruction, but the XML::XSLT perl module incorrectly copies the attributes along with the element. Then we use the 'element' command to create two child elements whose content is filled by the values of the attributes of the child elements of the command element from the source tree. The 'copy-to' instruction can be used to do a recursive copy of an element, that is it will copy the element with attributes and children to the result tree.

Variables, If and Choose

Top Bottom

If we modify the commands.xml to add a 'type' attribute for each command element:

<?xml version="1.0" ?>
<commands>
  <command type="2">
    <name>ls</name>
    <description>list directory contents</description>
  </command>
  <command type="2">
    <name>ps</name>
    <description>report a snapshot of current process</description>
  </command>
  <command type="2">
    <name>df</name>
    <description>report file system disk space usage</description>
  </command>
  <command type="1">
    <name>ipconfig</name>
    <description>report a network interface configuration</description>
  </command>
  <command type="2">
    <name>ifconfig</name>
    <description>configure a network interface</description>
  </command>
</commands>

...we can then use the type value in an 'if' element in our XSLT, to select the command type and process the source tree data accordingly. Notice that we have created a variable ($unwanted) to use in the first comparison operation. The variable element has also been used earlier in the stylesheet to store a string value. The string value has to be quoted within the double-quotes by single quotes.

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0" >
<xsl:template match="/">
<xsl:variable name="titleheading" select="'Some basic OS Commands'" />
  <html>
    <head><title><xsl:value-of select="$titleheading" /></title></head>
    <body>
      <h3>Information about <xsl:value-of select="$titleheading" /></h3>
      <br />
      <xsl:apply-templates select="/commands/command" />
    </body>
  </html>
</xsl:template>

<xsl:template match="command">
  <xsl:variable name="unwanted" select="1" />
  <xsl:if test="@type &gt; $unwanted" >
    <h3><xsl:value-of select="name" /> (Linux Command)</h3>
    <p><xsl:value-of select="description" /></p>
  </xsl:if>
  <xsl:if test="@type &lt; 2" >
    <h3><xsl:value-of select="name" /> (Windows Command)</h3>
    <p><xsl:value-of select="description" /></p>
  </xsl:if>
<br />
</xsl:template>

</xsl:stylesheet>

Applying the transform using saxon, produces the following:

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
   
      <title>Some basic OS Commands</title>
   </head>
   <body>
      <h3>Information about Some basic OS Commands</h3><br><h3>ls (Linux Command)</h3>
      <p>list directory contents</p><br><h3>ps (Linux Command)</h3>
      <p>report a snapshot of current process</p><br><h3>df (Linux Command)</h3>
      <p>report file system disk space usage</p><br><h3>ipconfig (Windows Command)</h3>
      <p>report a network interface configuration</p><br><h3>ifconfig (Linux Command)</h3>
      <p>configure a network interface</p><br></body>
</html>

The <if> element does not come with an elsif or else element. To achieve this effect we need a combination of <choose>, <when> and <otherwise> elements:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0" >
<xsl:template match="/">
<xsl:variable name="titleheading" select="'Some basic OS Commands'" />
  <html>
    <head><title><xsl:value-of select="$titleheading" /></title></head>
    <body>
      <h3>Information about <xsl:value-of select="$titleheading" /></h3>
      <br />
      <xsl:apply-templates select="/commands/command" />
    </body>
  </html>
</xsl:template>

<xsl:template match="command">
  <xsl:choose>
    <xsl:when test="@type &#61; 2" >
      <h3><xsl:value-of select="name" /> (Linux Command)</h3>
      <p><xsl:value-of select="description" /></p>
    </xsl:when>
    <xsl:otherwise>
      <h3><xsl:value-of select="name" /> (Unknown Command)</h3>
      <p><xsl:value-of select="description" /></p>
    </xsl:otherwise>
  </xsl:choose>
<br />
</xsl:template>

</xsl:stylesheet>

Foreach and Sort

Top Bottom

The <for-each> element can be used to iterate through a node-set, the <sort> element can be used to sort the node-set:

<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	version="1.0">
<xsl:template match="/">
<xsl:variable name="heading" select="'Some Basic Commands'" />
  <html>
    <head><title><xsl:value-of select="$heading" /></title></head>
    <body>
      <h3><xsl:value-of select="$heading" /></h3>
      <xsl:apply-templates select="/commands" />
    </body>
  </html>
</xsl:template>

<xsl:template match="commands">
  <dl>
    <xsl:for-each select="command">
    <xsl:sort select="." order="ascending" />
      <xsl:if test="@type &#61; 2">
      <dt><xsl:value-of select="name" /></dt>
      <dd><xsl:value-of select="description" /></dd>
    </xsl:if>
  </xsl:for-each>
</dl>
</xsl:template>

</xsl:stylesheet>
      

Applying this transform to the commands.xml file, we get the following output:

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
   
      <title>Some Basic Commands</title>
   </head>
   <body>
      <h3>Some Basic Commands</h3>
      <dl>
         <dt>df</dt>
         <dd>report file system disk space usage</dd>
         <dt>ifconfig</dt>
         <dd>configure a network interface</dd>
         <dt>ls</dt>
         <dd>list directory contents</dd>
         <dt>ps</dt>
         <dd>report a snapshot of current process</dd>
      </dl>
   </body>
</html>