HomePage RecentChanges XSLT Tutorial

XSL or XSLT

Introduction

What is XSL ?

What type of language

Officially it is a functional language. Other examples include LISP and Scheme. But XSL is not really like those, but rather like Haskel, and can be better described as a declarative language.

Rules:

What it can not do:

How does it work ?

Processing

Processing in a Browser

How to activate an XSL processor for a Browser ? Add the following line to the top of your document.

#!XML
 <?xml-stylesheet type="text/xsl" href="your_transform_file_here.xsl" ?>

Useful further data:

And you can access XSL from Javascript

Processing on the command line

"xsltproc" is a command line tool that calls LibXSL, which in turn uses LibXML for parsing and processing the XML.

Processing in Applications like Apache

Apache, like most web based applications, uses a lot of XSL and has many ways to use it. For example - you can enable XSL processing, server side or client side of directory structures. Subversion even allows you to specify an XSL to be provided with all directory listings, providing some very powerful customisation tools for subversion views (note - with subversion it does not do it server side, but provides a browser link, see below).

Processing in a language like Perl

Every language has their own, and almost all languages have binding to LibXML/LibXSLT.

Other processors worth a look:

And of course inside other containers, like Template Toolkit

An Example

You can work along with these.

Input Document

Save as bugs.xml

#!XML
 <bugs>
 
    <bug id="3" severity="1" title="Hello Wold has bugs in it">
        <owner>nobody</owner>
        <sa id="10" owner="george"/>
        <entry date="2007-10-25">
            The bug was formed from a service assist ticket.
        </entry>
        <entry date="2007-10-27">
            This is an entry in the future. Ohhhh....
        </entry>
    </bug>
 
    <bug id="5" severity="2" title="Another pretty bug">
        <owner>bill</owner>
        <sa id="10" owner="george"/>
        <entry date="2007-10-25">
            The bug was formed from a service assist ticket.
        </entry>
        <entry date="2007-10-27">
            This is an entry in the future. Ohhhh....
        </entry>
    </bug>
 
    <bug id="1" severity="2" title="Its a bug not a feature">
        <owner>nobody</owner>
        <sa id="10" owner="pam"/>
        <entry date="2007-10-25">
            The bug was formed from a service assist ticket.
        </entry>
        <entry date="2007-10-27">
            This is an entry in the future. Ohhhh....
        </entry>
    </bug>
 
    <bug id="7" severity="3" title="Nope its a feature">
        <owner>brooke</owner>
        <sa id="10" owner="david"/>
        <entry date="2007-10-25">
            The bug was formed from a service assist ticket.
        </entry>
        <entry date="2007-10-27">
            This is an entry in the future. Ohhhh....
        </entry>
    </bug>
 
    <bug id="8" severity="3" title="No one knows its a bug">
        <owner>amanda</owner>
        <sa id="10" owner="scott"/>
        <entry date="2007-10-25">
            The bug was formed from a service assist ticket.
        </entry>
        <entry date="2007-10-27">
            This is an entry in the future. Ohhhh....
        </entry>
    </bug>
 
    <bug id="11" severity="1" title="Why bugs jump up and down">
        <owner>teha</owner>
        <sa id="10" owner="will"/>
        <entry date="2007-10-25">
            The bug was formed from a service assist ticket.
        </entry>
        <entry date="2007-10-27">
            This is an entry in the future. Ohhhh....
        </entry>
    </bug>
 
    <bug id="21" severity="2" title="Ask the help desk">
        <owner>scott</owner>
        <sa id="10" owner="niall"/>
        <entry date="2007-10-25">
            The bug was formed from a service assist ticket.
        </entry>
        <entry date="2007-10-27">
            This is an entry in the future. Ohhhh....
        </entry>
    </bug>
 
    <bug id="17" severity="3" title="Bug, Issue or Defect?">
        <owner>bosco</owner>
        <sa id="10" owner="nobody"/>
        <entry date="2007-10-25">
            The bug was formed from a service assist ticket.
        </entry>
        <entry date="2007-10-27">
            This is an entry in the future. Ohhhh....
        </entry>
    </bug>
 
 </bugs>

View the document in Firefox.

Simple XSL

Save as simple.xsl

#!XML
 <?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="bug">
        <xsl:text>* </xsl:text>
        <xsl:value-of select="@id"/>
        <xsl:text> - </xsl:text>
        <xsl:value-of select="@title"/>
        <xsl:text>
 </xsl:text>
    </xsl:template>
 
 </xsl:stylesheet>

Don't get hung up on the detail in this style sheet yet...

Making it work in a Browser

Add to the top of bugs.xml.

#!XML
 <?xml version="1.0"?>
 <?xml-stylesheet type="text/xsl" href="simple.xsl" ?>

And reload in Firefox !

Command line

Try:

Anatomy of a Template

XPath

XPath is a very important concept to learn when dealing with XSL - every time you do a test, or a select you are using xpath.

You can think of XPath like using shell to move through directories, and XML like the tree you are navigating. Here are some basic rules to keep in mind.

xsl:stylesheet

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

Essential declaration of a stylesheet. Tell the processor the language, version and most importantly namespace of XLS.

xsl:template

This is the basis of the language - think of these like "sub" - on there is no entry point, there is no sub main (you can fake that, see html.xsl below).

Here is where we see our first XPath - match="bug" - that says execute this template for each bug found.

xsl:output

There are many attributes of output, one of the most important is method - which we use often as:

xsl:strip-space and xsl:text

This is a special template, we want to get the formatting text perfect. This means that we have to:

xsl:value-of

Now we have a bug, we want to get the value of an entry. Here we use xsl:value-of - this is the most common way of getting content, and will only get the content, not other elements at that location.

Again we see the use of XPath - in this case we are reading attributes (id and title) of the current location (a bug) and one element internally (owner).

You can almost think of value-of as print or echo.

HTML Output

Lets do an HTML output. It does not matter if you want XHTML (method="xml") or HTML (method="html").

Lets do the HTML header and layout.

#!XML
 <?xml version="1.0" encoding="UTF-8"?>
 <xsl:stylesheet 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
 >
 
    <xsl:output method="html"/>
 
    <xsl:template match="/">
        <html>
            <head>
                <title>Bugs</title>
            </head>
            <body>
                <h1>Bugs</h1>
            </body>
        </html>
    </xsl:template>
 
 </xsl:stylesheet>

Change the entry in your header, or use "xsltproc" to process the new file.

Run in Firefox.

Notice that we have a way of controlling the full output, stopping all other content being executed (even if there were other templates) by having a match on the root or top level object - match="/".

Now lets add in the output - a very simple method.

xsl:for-each

Add this in below your h1

#!XML
                <ul>
                    <xsl:for-each select="bugs/bug">
                        <li>
                            <xsl:value-of select="@id"/>
                            -
                            <xsl:value-of select="@title"/>
                            -
                            <xsl:value-of select="owner"/>
                        </li>
                    </xsl:for-each>
                <ul>
 

Note:

xsl:if

Let us improve our code - only show severity 3 entries. Wrap our list items, inside the for-each with xsl:if

#!XML
                        <xsl:if test="@severity = '3'">
                        <li>
                            <xsl:value-of select="@id"/>
                            -
                            <xsl:value-of select="@title"/>
                            -
                            <xsl:value-of select="owner"/>
                        </li>
                        </xsl:if>

Notice our test entry uses xpath.

xsl:choose

What if you want an alternative? Lets put in code that shows severity 3 in strong (strong, not bold !).

#!XML
                    <xsl:for-each select="bugs/bug">
                        <xsl:choose>
                            <xsl:when test="@severity = '3'">
                            <li>
                                <strong>
                                <xsl:value-of select="@id"/>
                                -
                                <xsl:value-of select="@title"/>
                                -
                                <xsl:value-of select="owner"/>
                                </strong>
                            </li>
                            </xsl:when>
                            <xsl:otherwise>
                            <li>
                                <xsl:value-of select="@id"/>
                                -
                                <xsl:value-of select="@title"/>
                                -
                                <xsl:value-of select="owner"/>
                            </li>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each>
 

YUCK ! Why repeat yourself, lets move our code back to a template.

xsl:apply-templates

Move the code back to a template.

#!XML
    <xsl:template match="bug">
                                <xsl:value-of select="@id"/>
                                -
                                <xsl:value-of select="@title"/>
                                -
                                <xsl:value-of select="owner"/>
    </xsl:template>

And ask it to apply a template instead of produce content:

#!XML
 <xsl:apply-templates select="."/>

Now we are lost... lets get the whole XSL File to this point:

#!XML
 <?xml version="1.0" encoding="UTF-8"?>
 <xsl:stylesheet 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
 >
 
    <xsl:output method="html"/>
 
    <xsl:template match="/">
        <html>
            <head>
                <title>Bugs</title>
            </head>
            <body>
                <h1>Bugs</h1>
                <ul>
                    <xsl:for-each select="bugs/bug">
                        <xsl:choose>
                            <xsl:when test="@severity = '3'">
                            <li>
                                <strong>
                                    <xsl:apply-templates select="."/>
                                </strong>
                            </li>
                            </xsl:when>
                            <xsl:otherwise>
                            <li>
                                <xsl:apply-templates select="."/>
                            </li>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each>
                <ul>
            </body>
        </html>
    </xsl:template>
 
    <xsl:template match="bug">
        <xsl:value-of select="@id"/>
        -
        <xsl:value-of select="@title"/>
        -
        <xsl:value-of select="owner"/>
    </xsl:template>
 
 </xsl:stylesheet>

Three ways to Filter

xsl:if

In our xsl:for-each we can add a xsl:if to select only those entries that are severity 3.

#!XML
 <xsl:if test="@severity = '3'">
  ...
 </xsl:if>

Using a xsl:if is very easy, but means w are iterating through every line, and then throwing out most of them.

xsl:for-each with xpath

This is when we want to use xpath within our query

#!XML
 <xsl:for-each select="bug[@severity = '3']">
  ...
 </xsl:if>

Notice that the select statement has a "[]" set in it. This is exactly the same as the test attribute of an xsl:if, except that the context it applies is based on the rest of the xpath.

Now we only get, like the if - one entry. What about apply templates but different ones depending on severity ?

xsl:template

In each of our cases we are going through the set of objects inside another template, but there is a better way.

#!XML
    <xsl:template match="bug[@severity!='2']">
        <xsl:value-of select="@id"/>
        -
        <xsl:value-of select="@title"/>
        -
        <xsl:value-of select="owner"/>
    </xsl:template>
 
    <xsl:template match="bug[@severity='3']">
        <strong>
            <xsl:value-of select="@id"/>
            -
            <xsl:value-of select="@title"/>
        </strong>
        -
        <xsl:value-of select="owner"/>
    </xsl:template>

The advantages of templates over for-each is that they are much more efficient, easier to inherit and overload. All our previous examples use xsl:value-of, which is a way of working similar to procedural programming, rather than template driven.

Now we have best of all worlds - we can add content in for-each entry - in this case list item elements. We can have different entries in templates depending on severity...

The whole thing again:

#!XML
 <?xml version="1.0" encoding="UTF-8"?>
 <xsl:stylesheet 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
 >
 
    <xsl:output method="html"/>
 
    <xsl:template match="/">
        <html>
            <head>
                <title>Bugs</title>
            </head>
            <body>
                <h1>Bugs</h1>
                <ul>
                    <xsl:apply-templates select="bugs/bug"/>
                <ul>
            </body>
        </html>
    </xsl:template>
 
    <xsl:template match="bug[@severity!='2']">
        <li>
        <xsl:value-of select="@id"/>
        -
        <xsl:value-of select="@title"/>
        -
        <xsl:value-of select="owner"/>
        </li>
    </xsl:template>
 
    <xsl:template match="bug[@severity='3']">
        <li>
        <strong>
            <xsl:value-of select="@id"/>
            -
            <xsl:value-of select="@title"/>
        </strong>
        -
        <xsl:value-of select="owner"/>
        </li>
    </xsl:template>
 
    <xsl:template match="*">
    </xsl:template>
 
 </xsl:stylesheet>

Both !

This time we are going to do a bit of both, to allow for better sorting - lets sort by id.

At the apply-templates above, just add back in a for-each and a sort.

#!XML
                    <xsl:for-each select="bugs/bug">
                        <xsl:sort select="@id" data-type="number" order="descending"/>
                        <xsl:apply-templates select="."/>
                    </xsl:for-each>

We need to tell XSL that this is a number, not a string. xsl:sort also allows ignore case. We also want this descending - newest ticket first (although id is not as reliable as dates - it all depends on the meaning of your xml).

Another copy of the whole XSL:

#!XML
 <?xml version="1.0" encoding="UTF-8"?>
 <xsl:stylesheet 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
 >
 
    <xsl:output method="html"/>
 
    <xsl:template match="/">
        <html>
            <head>
                <title>Bugs</title>
            </head>
            <body>
                <h1>Bugs</h1>
                <ul>
                    <xsl:for-each select="bugs/bug">
                        <xsl:sort select="@id" data-type="number" order="descending"/>
                        <xsl:apply-templates select="."/>
                    </xsl:for-each>
                <ul>
            </body>
        </html>
    </xsl:template>
 
    <xsl:template match="bug[@severity!='3']">
        <li>
        <xsl:value-of select="@id"/>
        -
        <xsl:value-of select="@title"/>
        -
        <xsl:value-of select="owner"/>
        </li>
    </xsl:template>
 
    <xsl:template match="bug[@severity='3']">
        <li>
        <strong>
            <xsl:value-of select="@id"/>
            -
            <xsl:value-of select="@title"/>
        </strong>
        -
        <xsl:value-of select="owner"/>
        </li>
    </xsl:template>
 
    <xsl:template match="*">
    </xsl:template>
 
 </xsl:stylesheet>

Advanced or Tomorrow

References

See Also

Software error:

Can't locate object method "endform" via package "CGI" at /data/scott.dd.com.au/wiki/modules/search.pl line 15.

For help, please send mail to the webmaster (webmaster@dd.com.au), giving this error message and the time and date of the error.