XSL Basics (part 2)

Expand your XSLT vocabulary with conditional constructs, loops and variables.

Mood Lighting

In the first part of this article, I explained the need and rationale for XSLT, together with some of the basic concepts. I demonstrated how XSLT uses template rules to extract data from an XML data source, build a result tree and present it in a different manner. Finally, I examined two of the basic XSLT constructs, <xsl:value-of /> and <xsl:apply-templates />, and illustrated, with examples, how they could be used to print node values and recursively traverse an XML source tree.

In the concluding part of this article, I will be examining a bunch of other XSLT constructs, all designed to make your XSLT experience that much more enjoyable. Some of these constructs are extremely simple and logical; others offer capabilities typically found only in programming languages. Regardless, they are bound to come in useful as you slowly move your development activities to XML and XSL.

Not really in the mood? I don't blame you. But look at it this way - at least you'll have something to hit your boss over the head with at the next performance review...

Mercury Rising

XSLT allows you to perform basic conditional tests within a template rule. The simplest form of conditional instruction is the <xsl:if> instruction, which looks something like this

<xsl:if test="condition">
    do this!
</xsl:if>

The "condition" here is a conditional expression, which evaluates to either true or false. If the statement evaluates to true, the template is used; if not, it is ignored by the processor.

Consider the following XML document:

<?xml version="1.0"?>

<inventory>

    <item>
        <name>Brand Alpha</name>
        <rating>7</rating>
    </item>

    <item>
        <name>Brand Beta</name>
        <rating>9</rating>
    </item>

    <item>
        <name>Brand Gamma</name>
        <rating>2</rating>
    </item>

</inventory>

Here's a stylesheet to display these items as a list. Note how a conditional test has been used to display an asterisk next to items with a high rating.

<?xml version="1.0"?>

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

<xsl:template match="/inventory">
    <html>
    <head>
    <basefont face="Arial" size="2"/>
    </head>
    <body>
    <ul>
    <xsl:apply-templates />
    </ul>
    </body>
    </html>
</xsl:template>

<xsl:template match="item">
<li><xsl:value-of select="name" /></li>
<xsl:if test="rating > 5">
*
</xsl:if>
</xsl:template>

</xsl:stylesheet>

Here's what it looks like:

Here are a couple more examples of how this text can be used. The first template rule places the word "and" before printing the last item in the selected node collection, while the second displays a warning based on the value of a node.

<xsl:template match="person">
<xsl:if test="position() = last()">
and
</xsl:if>
<xsl:value-of select="." />
</xsl:template>

<xsl:template match="/">
<xsl:if test="//temp > 500">
<font size="+2" color="red">Warning! Temperature above acceptable limits!</font>
</xsl:if>
</xsl:template>

The Number Game

In case you find the "if" test a little too primitive for your needs, XSLT also offers the <xsl:choose> instruction. This is a lot like the "switch/case" statements you may be familiar with from PHP or JSP, and it looks like this:

<xsl:choose>
    <xsl:when test="first_condition_is_true">
    do this!
    </xsl:when>

    <xsl:when test="second_condition_is_true ">
    do this!
    </xsl:when>

    <xsl:when test="third_condition_is_true ">
    do this!
    </xsl:when>

    ... and so on ...

    <xsl:otherwise>
    all tests failed, do this!
    </xsl:otherwise>

</xsl:choose>

An example might help to make this clearer. Take a look at this XML document, which contains original and current prices for some fictitious stocks.

<?xml version="1.0"?>

<portfolio>

    <stock>
        <symbol>HHDT</symbol>
        <oprice>100</oprice>
        <cprice>25</cprice>
    </stock>

    <stock>
        <symbol>DFKS</symbol>
        <oprice>250</oprice>
        <cprice>150</cprice>
    </stock>

    <stock>
        <symbol>NHSJ</symbol>
        <oprice>20</oprice>
        <cprice>1000</cprice>
    </stock>

    <stock>
        <symbol>DJSK</symbol>
        <oprice>425</oprice>
        <cprice>2</cprice>
    </stock>

    <stock>
        <symbol>MDSH</symbol>
        <oprice>50</oprice>
        <cprice>50</cprice>
    </stock>

    <stock>
        <symbol>TRVB</symbol>
        <oprice>90</oprice>
        <cprice>86</cprice>
    </stock>

</portfolio>

Now, let's suppose I wanted to find out which of these stocks had risen in value, which had decreased, and which had remained static. I could use an XSLT stylesheet to calculate the change in value and display an appropriate message - maybe with a colour code - next to each stock in the list.

<?xml version="1.0"?>

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

<xsl:template match="/portfolio">
    <html>
    <head>
    <basefont face="Arial" size="2"/>
    </head>
    <body>
    <h3>Portfolio At A Glance:</h3>
    <ul>
    <xsl:apply-templates />
    </ul>
    </body>
    </html>
</xsl:template>

<xsl:template match="stock">
<xsl:choose>

    <!-- if price has gone up -->
    <xsl:when test="(cprice - oprice) > 0">
    <li><font color="blue"><xsl:value-of select="symbol" /></font> (up by <xsl:value-of select="round(((cprice - oprice) * 100 div oprice))" />%)</li>
    </xsl:when>

    <!-- if price has gone down -->
    <xsl:when test="(cprice - oprice) &lt; 0">
    <li><font color="red"><xsl:value-of select="symbol" /></font> (down by <xsl:value-of select="round(((cprice - oprice) * 100 div oprice) * -1)" />%)</li>
    </xsl:when>

    <!-- no change -->
    <xsl:otherwise>
    <li><xsl:value-of select="symbol" /> (no change)</li>
    </xsl:otherwise>

</xsl:choose>
</xsl:template>

</xsl:stylesheet>

Here's what it looks like:

As you can see, the <xsl:choose> conditional statement has been used to find out whether a stock has increased or decreased in value, with a template generated appropriately. The calculations you see within the template rule are all used to display the change in value in percentage terms, and again illustrate how XSLT can be used to combine disparate bits of information from the source tree to create a new and different view in the result tree.

A Node By Any Other Name

XSLT also comes with a bunch of instructions designed to help authors create nodes in the result tree using XSLT instructions, rather than literal character data. These come in particularly handy when elements, attributes, PIs or text strings need to be generated dynamically.

In order to illustrate this, let's consider a small snippet from the stock data you just saw:

<?xml version="1.0"?>

<portfolio>

    <stock>
        <symbol>HHDT</symbol>
        <oprice>100</oprice>
        <cprice>25</cprice>
    </stock>

    <stock>
        <symbol>NHSJ</symbol>
        <oprice>20</oprice>
        <cprice>1000</cprice>
    </stock>

</portfolio>

Now, let's suppose that, for some twisted reason of my own, I wanted to convert this source tree into the following result tree:

<portfolio>

<HHDT oprice="100" cprice="25" />

<NHSJ oprice="20" cprice="1000" />

</portfolio>

Here's how I could accomplish it with XSLT:

<?xml version="1.0"?>

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

<xsl:template match="/">
<xsl:element name="portfolio">
    <xsl:apply-templates />
</xsl:element>
</xsl:template>

<xsl:template match="//stock">
<xsl:element name="{symbol}">
    <xsl:attribute name="oprice">
    <xsl:value-of select="oprice" />
    </xsl:attribute>

    <xsl:attribute name="cprice">
    <xsl:value-of select="cprice" />
    </xsl:attribute>
</xsl:element>
</xsl:template>

</xsl:stylesheet>

The <xsl:element> instruction is used to create a new element in the result tree; the name of the element to be created is specified via the "name" attribute. In case the element name needs to be generated dynamically - as in the above example, where the element name depends on the stock symbol - an expression can be used, enclosed within curly braces.

In the example above, the elements to be created are "empty" elements - that is, they do not enclose any content. In case I wanted to add some content between the opening and closing tags, I would use the following instruction:

<xsl:element name="{symbol}">
I rock!
</xsl:element>

If you take a close look at the first template rule above, you'll see that I've actually used this technique when creating the outermost "portfolio" element.

Once an element has been created, it may be necessary to assign it some attributes - this is accomplished via the <xsl:attribute> instruction. The syntax here is similar to that of <xsl:element>, although <xsl:attribute> instructions must always be contained within either literal or dynamically-created parent elements.

As you may have guessed, XSLT doesn't just restrict you to creating elements and attributes dynamically - you can also create text nodes, PIs and comments, using the <xsl:text>, <xsl:processing-instruction> and <xsl:comment> instructions respectively. Take a look at the following examples, which demonstrate how this works:

<xsl:template match="/node">
<xsl:comment>
Superman was here!
</xsl:comment>
</xsl:template>

<xsl:template match="/node">
<xsl:text>
Mary had a little lamb
</xsl:text>
</xsl:template>

<xsl:template match="/node">
<xsl:processing-instruction name="bite_me" />
</xsl:template>

Obviously, text, comments and PIs can also be generated dynamically, as previously demonstrated with elements and attributes.

Looping The Loop

In addition to conditional tests, XSLT also allows for a primitive looping mechanism via the <xsl:for-each> instruction. If both the source tree and result tree follow a defined structure, this instruction can go a long way towards making your XSLT stylesheets more efficient and compact.

The <xsl:for-each> instruction always comes with a "select" attribute, which is used to select a node collection for the template rule to act upon. Once the node collection has been obtained, the templates contained within the <xsl:for-each> instruction are applied to every node within the node collection.

If this sounds confusing - the following example should make things clearer. Consider the following XML document:

<?xml version="1.0"?>

<top_five>

    <movie>
        <name>Star Wars: A New Hope</name>
        <cast>Mark Hamill, Carrie Fisher, Harrison Ford</cast>
        <director>George Lucas</director>
        <rank>1</rank>
    </movie>

    <movie>
        <name>The Patriot</name>
        <cast>Mel Gibson, Heath Ledger, Jason Isaacs</cast>
        <director>Roland Emmerich</director>
        <rank>5</rank>
    </movie>

    <movie>
        <name>The Whole Nine Yards</name>
        <cast>Bruce Willis, Matthew Perry</cast>
        <director>Jonathan Lynn</director>
        <rank>3</rank>
    </movie>

    <movie>
        <name>Gladiator</name>
        <cast>Russell Crowe, Connie Nielsen, Joaquin Phoenix</cast>
        <director>Ridley Scott</director>
        <rank>4</rank>
    </movie>

    <movie>
        <name>Unbreakable</name>
        <cast>Bruce Willis, Samuel L. Jackson</cast>
        <director>M. Night Shyamalan</director>
        <rank>2</rank>
    </movie>

</top_five>

As you can see, this is pretty clearly-structured, with every movie an independent entity within the document. This makes it perfect for a <xsl:for-each> loop.

<?xml version="1.0"?>

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

<xsl:template match="/">
<html>
<head>
<basefont face="Arial" size="4" />
</head>
<body>
<ol>
<xsl:apply-templates />
</ol>
</body>
</html>
</xsl:template>

<xsl:template match="/top_five">
<xsl:for-each select="movie">
    <li><xsl:value-of select="name" /></li>
    <br />
    <font size="2"><xsl:value-of select="cast" /> | <xsl:value-of select="director" /></font>
</xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Now, the loop will iterate as many times as there as "movie" elements in the document, applying the template contained within its opening and closing tags on every iteration. Here's the resulting HTML output,

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<basefont face="Arial" size="4">
</head>

<body>
<ol>

<li>Star Wars: A New Hope</li>
<br>
<font size="2">Mark Hamill, Carrie Fisher, Harrison Ford | George Lucas</font>

<li>The Patriot</li>
<br>
<font size="2">Mel Gibson, Heath Ledger, Jason Isaacs | Roland Emmerich</font>

<li>The Whole Nine Yards</li>
<br>
<font size="2">Bruce Willis, Matthew Perry | Jonathan Lynn</font>

<li>Gladiator</li>
<br>
<font size="2">Russell Crowe, Connie Nielsen, Joaquin Phoenix | Ridley Scott</font>

<li>Unbreakable</li>
<br>
<font size="2">Bruce Willis, Samuel L. Jackson | M. Night Shyamalan</font>

</ol>
</body>
</html>

and here's what it looks like:

Sorting Things Out

Now, if you've been paying attention, you'll have noticed a small problem with the preceding example. Though the XML document putatively contains a list of someone's top five movies, ranked according to the "rank" element, the stylesheet does not process this data and prints the movies in the order in which it finds them.

It's to resolve precisely this kind of situation that XSLT also includes a powerful sorting mechanism, which can be used to rearrange the data within the document in a specific order. The <xsl:sort> instruction uses the "select" attribute to select the elements against which to sort the data, as also whether to use ascending or descending order.

Let's rewrite the template rule above to present the top five movies in correct order:

<xsl:template match="/top_five">
<xsl:for-each select="movie">
    <xsl:sort select="rank" />
    <li><xsl:value-of select="name" /></li>
    <br />
    <font size="2"><xsl:value-of select="cast" /> | <xsl:value-of select="director" /></font>
</xsl:for-each>
</xsl:template>

In case I wanted to reverse the order - just to confuse readers and because I'm a nasty evil person - I could add an "order" attribute to specify the sort order:

<xsl:template match="/top_five">
<xsl:for-each select="movie">
    <xsl:sort select="rank" order="descending" />
    <li><xsl:value-of select="name" /></li>
    <br />
    <font size="2"><xsl:value-of select="cast" /> | <xsl:value-of select="director" /></font>
</xsl:for-each>
</xsl:template>

XSLT allows you to specify more than one <xsl:sort> instruction, so that it becomes possible to sort by more than one key - for example, rank followed by name followed by director.

Be Cool

The example you just saw used an ordered list to display the items in correct numeric order. Now, while this is fine and dandy so long as you're only outputting HTML, what happens if you suddenly need to output the same data as ASCII text, which doesn't come with a convenient auto-numbering feature?

Well, have no fear - XSLT comes with its own built-in numbering system, accessed via the <xsl:number> instruction. This construct comes in very handy when you need to print a series of sequential numbers, or print numbers in a style different from the standard decimal system.

Let's take a look at how it works, by using the following XML document as source data:

<?xml version="1.0"?>

<bookstore>
    <title>Be Cool</title>
    <title>Mystic River</title>
    <title>Hit List</title>
    <title>Silent Joe</title>
    <title>The Travel Detective</title>
</bookstore>

And here's the XSLT stylesheet:

<?xml version="1.0"?>

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

<xsl:template match="title">
<xsl:number value="position()"/>
<xsl:value-of select="." />
</xsl:template>

</xsl:stylesheet>

And the output would be:

1Be Cool
2Mystic River
3Hit List
4Silent Joe
5The Travel Detective

In this case, the position of each node in the collection is used as the number.

In case the number format doesn't work for you, you can alter it with the "format" attribute - try the format "I" for Roman numerals, or "a" for alphabetic numbering. The rule

<xsl:template match="title">
<xsl:number value="position()" format="I. "/>
<xsl:value-of select="." />
</xsl:template>

returns

I. Be Cool
II. Mystic River
III. Hit List
IV. Silent Joe
V. The Travel Detective

In addition to this simple numbering mechanism, XSLT also allows you to count and display specific types of nodes within the document. For example, if I needed to display a number next to each of the steps in the process below,

<?xml version="1.0"?>

<recipe>

    <ingredients>
            <item>Boneless chicken breasts</item>
            <item>Chopped onions</item>
            <item>Ginger</item>
    </ingredients>

    <process>
        <step>Cut chicken into cubes, wash and apply lime juice and salt</step>
        <step>Add ginger, garlic, chili, coriander and lime juice in a separate bowl</step>
        <step>Mix well, and add chicken to marinate for 3-4 hours</step>
        <step>Place chicken pieces on skewers and barbeque</step>
        <step>Remove, apply butter, and barbeque again until meat is tender</step>
        <step>Garnish with lemon and chopped onions</step>
    </process>

</recipe>

I could use the following stylesheet.

<?xml version="1.0"?>

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

<xsl:template match="/">
Preparation:
<xsl:apply-templates select="recipe/process" />
</xsl:template>

<xsl:template match="process/step">
<xsl:number count="step" format="a. " />
<xsl:value-of select="." />
</xsl:template>

</xsl:stylesheet>

Here, the second template rule specifies that only "step" elements are to be counted and numbered. Note, however, that this numbering mechanism only works on nodes at the same level in the node tree - if you'd like to include nodes at other levels, there's an additional "level" attribute you need to use.

Flavour Of The Month

Just as XML has entities, pre-defined constants which can be used at different places within your document, so too does XSLT have variables. However, XSLT variables are not the same as the ones you may be used to working with in standard programming languages. XSLT variables, once defined, cannot be altered; they remain static and can merely be used at different places within the XSLT stylesheet.

XSLT variables are defined with the <xsl:variable> instruction, and a value is attached to them either via a "select" attribute or enclosed within the opening and closing tags of the instruction; this value may be a literal, or obtained from an expression. In case a variable lacks both a "select" attribute and a content value, it is automatically assigned an empty string value.

Variables may be defined either within or outside template rules. A variable defined outside a template rule (at the top-level of the document) is globally available, while a variable defined within a template rule is available only to descendants of that rule.

Once defined, variables can be accessed by referencing the variable name with a preceding dollar($) symbol.

Consider the following stylesheet, again with reference to the preceding XML data.

<?xml version="1.0"?>

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

<xsl:variable name="flavour">strawberry</xsl:variable>
<xsl:template match="/">
<html>
<head>
<body>
-- root level -- My favourite flavour is <xsl:value-of select="$flavour" /><br />
<xsl:apply-templates select="recipe/ingredients" />
<xsl:apply-templates select="recipe/process" />
-- root level -- My favourite flavour is <xsl:value-of select="$flavour" /><br />
</body>
</head>
</html>
</xsl:template>

<xsl:template match="ingredients/item">
<xsl:variable name="flavour">raspberry</xsl:variable>
-- item level -- My favourite flavour is <xsl:value-of select="$flavour" /><br />
</xsl:template>

<xsl:template match="process/step">
-- step level -- My favourite flavour is <xsl:value-of select="$flavour" /><br />
</xsl:template>

</xsl:stylesheet>

Here, I've first set up a global variable, $flavour, with the value "strawberry". I've displayed it in my very first template rule, which matches the document root. Since this template rule does not contain any overriding variable definition, it will use and print the value of the global variable, "strawberry".

Then I've transferred control to the second template rule, which matches "item" elements. Over here, I've defined a new value for the variable, "raspberry". This time, the value of the global variable will be superseded by the value within the template rule, and an attempt to print $flavour will return "raspberry".

Once that node collection is exhausted, control goes to the third template rule, which matches "step" elements. This rule does not contain any variable definition; further, it does not have access to the value defined within the second template rule, since it is not a direct descendant of that rule. Consequently, it will also use the value "strawberry".

Here's the output:

-- root level -- My favourite flavour is strawberry
-- item level -- My favourite flavour is raspberry
-- item level -- My favourite flavour is raspberry
-- item level -- My favourite flavour is raspberry
-- step level -- My favourite flavour is strawberry
-- step level -- My favourite flavour is strawberry
-- step level -- My favourite flavour is strawberry
-- step level -- My favourite flavour is strawberry
-- step level -- My favourite flavour is strawberry
-- step level -- My favourite flavour is strawberry
-- root level -- My favourite flavour is strawberry

Endgame

And that's about it. Over the course of this article, I've expanded your understanding of the basic XSLT vocabulary by demonstrating how to use conditional constructs and loops, together with examples of how XSLT allows you to dynamically create elements, attributes and other nodes in the result tree. I've also demonstrated the basics of XSLT variables, numbering and sorting, taught you how to make a spicy chicken dish, and provided you with a list of movies to watch this weekend. Whew!

In case you're looking for more XSLT references - well, the Web is full o' them. Here are some recommendations:

XML Basics, at http://www.melonfire.com/community/columns/trog/article.php?id=78

XPath Basics, at http://www.melonfire.com/community/columns/trog/article.php?id=83

The XSLT specification, at http://www.w3.org/TR/xslt.html

XSLT.com's tutorial section, at http://www.xslt.com/resources_tutorials.htm

XML.com's XSLT tutorials, at http://www.xml.com/pub/rg/135

The XSL FAQ, at http://www.dpawson.co.uk/xsl/xslfaq.html

The W3C's list of useful XSLT resources, with links to tutorials and software, at http://www.w3.org/Style/XSL/

Take care, and I'll see you soon!

Note: All examples in this article have been tested on Microsoft Internet Explorer 5.5 and Saxon 6.4.3. Examples are illustrative only, and are not meant for a production environment. YMMV!

This article was first published on24 Aug 2001.