XSL or XSLT

  • XSL = XML Stylesheet Language
  • XSLT = XSL Transformer / Translator
  • XPath = XML Query language for finding content in an XML document.

Introduction

What is XSL ?

  • A language
  • Written in XML
  • Expresses a style sheet
  • Like CSS...
  • XSLT is a transformation language of XSL, to convert rather than just describe how it displays
  • Invented to cover complex examples, like tabulating data
  • XSLT is used to transform one XML document into another
  • Uses XPath to navigate within the document
  • Is a W3C recommendation.

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:

  • Don't think OO
  • Don't think Procedural
  • Think filter or template or trigger

What it can not do:

  • Variables ! - there are but immutable
  • Iteration
  • Fully threaded - no guaranteed processing order, but output does follow tree (partly why there is no variables)

How does it work ?

  • Source document is converted into Destination document
  • Via style sheet
  • Three XML documents - Input, Style sheet (transformer), Output - all XML
    • (Special exception of Text and HTML)

Processing

  • There are many XSL processors
  • Firefox, IE and Safari have built in support for a limited set of XSL (no EXSL or loading of any external documents)
  • Almost every language has libraries to process XSL directly
  • LibXSL is by far the best supported and the fastest - http://www.xmlsoft.org/ - originally developed for GNOME (originally developed for GIMP).
  • Other XSL = SAXON, Xalan, 4XSLT, Sablotron

Processing in a Browser

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

 <?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.

  • "xsltproc inputfile.xml" - will use the XSL defined in the XML file just like the Browser
  • "xsltproc templet.xsl inputfile.xml" - will use template.xsl to process inputfile.xml - and ignore the definition in the xml file.

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).

  • Apache Xalan and Xerces are apache foundation projects which can be added
  • mod-xslt2 - http://www.mod-xslt2.com/ - very powerful and fast module to process XSL automatically
  • Cocoon - http://cocoon.apache.org/ - set of tools for server side processing including XSL
  • AxKit - powerful perl equir of Cocoon - http://www.axkit.org/ (not very well supported on Apache 2 !!!)
  • Many other C and Perl modules for translation
  • Zaltana::Style and Zaltana::XSLT - http://www.zaltana.org/

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

 <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 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 version="1.0"?>
 <?xml-stylesheet type="text/xsl" href="simple.xsl" ?>

And reload in Firefox !

Command line

Try:

  • xsltproc bugs.xml
  • xsltproc simple.xsl bugs.xml

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.

  • Location of your query depends on your current context (you can change that with special modifies covered in advanced section)
  • A slash "/" - goes between elements - so to get access to an entry for a bug you would enter "bug/entry"
  • An AT "@" - designates an attribute, rather than an element - so to get the id of a bug you use - bug/@id, or an entry date - bug/entry/@date
  • Square brackets "[" .. "]" - designate a test, so if we want todays entry on bug 8 we would use "bug[@id="8"]/entry[@date='2007-10-25']"
    • Note that the @id now does not have a slash, as it is not the data we want, but to test it.
    • You can test elements, not just attributes - find tickets with owner nobody - "bug[owner='nobody']"

xsl:stylesheet

 <?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:

  • text - this is a plain text output. Good for CSV, TSV and other text based output
  • xml - output XML - the default for XSL
  • html - Output NON XML HTML - i.e. don't close input, br and img tags and don't add other declarations such as name space.

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:

  • Use xsl:strip-space to remove all white space in our templates
  • Add xsl:text around each text entry
  • Use xsl:text to add a new line.

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 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

                <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:

  • You do not have to use xsl:text when not stripping space - you can just put content in
  • You can add tags anywhere, if they don't start with xsl: (depending on declaration) they are just passed through - this is a template language !
  • We are at the top level of the document, the root - bug entries are in the bugs section so we must use the XPath bugs/bug

xsl:if

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

                        <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 !).

                    <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.

    <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:

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

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

 <?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.

 <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

 <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.

    <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 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.

                    <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 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

  • Name space in Source and Destination documents
  • Name space in XSL and use of internal, external and EXSL directives.
  • EXSL - everything extended.
  • More XPath than you can poke a stick at - descendant, parent, child, etc
  • XSL-FO - format objects
  • Using xsl:choose vs xsl:if
  • LibXSL and how it can be used on return data and XPath
  • xsl:template name and call-template
  • Improved sort - making sets that are reusable.

References

See Also