Using XSL, Part 1
posted by January 3, 2006 at 4:02AM

Welcome to my third run at PHP template engines. Based on XSL, the Extensible Stylesheet Language, this new template engine is based on open standards for HTML templates. Each template is portable to any platform or language and as long as the language has support for XML and XSL. That includes PHP, Perl, and Java, among others. PHP 5 has built in support for XSL in its XSLT processor class. The only requirement is that PHP be compiled with the --with-xsl[=DIR]. Here you can read my quick guide to XSL that is not specific to any particular language or engine but will work with virtually every implementation of XSL. This is meant to show you the elements of XSL that can be directly applied to HTML template engines. First, let's start with the most basic XSL file for use in an HTML template engine. Example 1
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method='html'/>
    <xsl:template match="/">
        <html>
            <body>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>
Quite simply, the template in Example 1 will write the HTML opening and closing tags to a document. Between the <xsl:template> opening and closing tags you can put any HTML that you want and it will be output to the browser.

The power of XSL comes from using XML documents to pass data to your template. By creating XML documents to pass to the XSL engine, you can send incredibly complex structures to your templates. As a small example, we'll use this XML file, taken from the PHP XSL documentation: Example 2
<?xml version='1.0'?>
<collection>
    <cd>
        <title>Fight for your mind</title>
        <artist>Ben Harper</artist>
        <year>1995</year>
    </cd>
    <cd>
        <title>Electric Ladyland</title>
        <artist>Jimi Hendrix</artist>
        <year>1997</year>
    </cd>
</collection>
Combining this the template from Example 1, and and again using part of the example from the PHP XSL documentation, we use this template: Example 3
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method='html'/>
    <xsl:template match="/">
        <html>
            <body>
                <xsl:apply-templates select="/collection/cd"/>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="cd">
        <h1><xsl:value-of select="title"/></h1>
        <h2>by <xsl:value-of select="artist"/> - <xsl:value-of select="year"/></h2>
        <hr/>
    </xsl:template>
</xsl:stylesheet>
This above template will output a list of all the albums from the XML file in Example 2. And the great thing about XML files is that they can contain so much varied data. Take, for example, the following XML file which contains information about songs, albums, and artists. Example 4
<?xml version='1.0'?>
<template>
    <albums>
        <album>
            <title>Fight for your mind</title>
            <artist>Ben Harper</artist>
            <year>1995</year>
        </album>
        <album>
            <title>Electric Ladyland</title>
            <artist>Jimi Hendrix</artist>
            <year>1997</year>
        </album>
    </albums>
    <songs>
        <song>
            <name>Voodoo Chile</name>
            <artist>Jimi Hendrix</artist>
            <album>The Jimi Hendrix Experience</album>
            <track>4</track>
        </song>
        <song>
            <name>Little Miss Strange</name>
            <artist>Jimi Hendrix</artist>
            <album>The Jimi Hendrix Experience</album>
            <track>5</track>
        </song>
        <song>
            <name>Long Hot Summer Night</name>
            <artist>Jimi Hendrix</artist>
            <album>The Jimi Hendrix Experience</album>
            <track>6</track>
        </song>
    </songs>
    <artists>
        <artist>
            <name>Jimi Hendrix</name>
        </artist>
        <artist>
            <name>Ben Harper</name>
        </artist>
    </artists>
</template>
We can run multiple loops on this data and get all of it out in separate instances. We can put as much information into this XML file as we want. We can use the XML from Example 4 and the following template to output our new web page: Example 5
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method='html'/>
    <xsl:template match="/">
        <html>
            <body>
                <xsl:apply-templates select="/template/albums/album"/>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="album">
        <h1><xsl:value-of select="title"/></h1>
        <h2>by <xsl:value-of select="artist"/> - <xsl:value-of select="year"/></h2>
        <hr/>
    </xsl:template>
</xsl:stylesheet>
An alternative to the looping mechanism in Example 5 is available using XSL's <xsl:for-each> tag. Using the XML file in Example 4 and the following code to loop over the albums, we can achieve the exact same result the code in Example 5. Example 6
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method='html'/>
    <xsl:template match="/">
        <html>
            <body>

                <xsl:for-each select="/template/albums/album">
                    <h1><xsl:value-of select="title"/></h1>
                    <h2>by <xsl:value-of select="artist"/> - <xsl:value-of select="year"/></h2>
                    <hr/>
                </xsl:for-each>

            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>
Now, for more control over our HTML, XSL provides the ability to do conditional statements using <xsl:choose>. Each <xsl:choose> must contain at least one <xsl:when> tag followed by an optional <xsl:otherewise>. A good PHP analogy to this tag is switch. Here is an example of using the <xsl:choose> tag inside of our above loop to show information only when our artist is Ben Harper. Example 7
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method='html'/>
    <xsl:template match="/">
        <html>
            <body>

                <xsl:for-each select="/template/albums/album">
                    <xsl:choose>
                        <xsl:when test="artist='Ben Harper'">
                            <h1><xsl:value-of select="title"/></h1>
                            <h2>by <xsl:value-of select="artist"/> - <xsl:value-of select="year"/></h2>
                            <hr/>
                        </xsl:when>
                        <xsl:otherwise>
                            The artist was someone else.
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:for-each>

            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>
In cases when it comes to referencing variables other than from XML, you have two options: <xsl:param> and <xsl:variable>. The primary differences between the two are that the <xsl:variable> tag can appear both inside and outside of the <xsl:template> tag and that the <xsl:param> can be overridden by values passed to the template. But to use them otherwise is identical.

There are also two important differences from these values and those grafted from XML files. First is that values specified in <xsl:param> and <xsl:variable> must be prefixed with a dollar sign in all uses. For example: <xsl:value-of select="$name"/>. Secondly, these values can appear inside other tags using a special syntax. For example, if you wanted to place a value inside of an HTML attribute, you can do this: <form method="POST" action="{$page_url}">. However, keep in mind that the latter syntax will only work when inside another tag, and will not stand alone.

Another useful example is when one wants to get a value out of an XML file. This can be done using either <xsl:param> or <xsl:variable> by altering its syntax. For example, if we have this XML file: Example 8
<template>
    <artist type="group">Genesis</artist>
</template>
We can get the value of the author tag into a variable using <xsl:variable name="artist" select="/template/artist"/>.

While it is useful to be able to dynamically assign variables throughout the template using <xsl:variable>, scoping becomes an issue just as it does inside other languages. For example, a variable set inside a <xsl:for-each> will not be accessible outside of it, et cetera.

While on the topic of referencing variables, there is the topic of referencing attributes in XML files. For example, how would you reference the type attribute in an XML tag such as the one in the sample XML file immediately above. This is easily done with something like this: <xsl:value-of select="/template/artist/@type">.

Moving on to displaying text, you may want to display something in your template and maintain all white space, or you might want to deliberately place white space into your template, you can wrap text in the <xsl:text> tag.

Next, how do we include other templates to build composite templates? This is very easily done using the <xsl:import> and <xsl:include> tags. What are the differences between the two? First, <xsl:import> must come before any other tag in your template, excepting the <xsl:stylesheet> tag, whereas <xsl:include> can appear anywhere outside the <xsl:template> tag. Secondly, when a file is included, each element in the included file has the same precedence as those from the including file. On the other hand, when a file is imported, the imported content has a lower precedence than the importing template. What this means is that if there are two ambiguous tags, the one with higher precedence will match first. Otherwise, <xsl:include> and <xsl:import> work exactly the same way. Here is an example template that includes another template: Example 9
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:include href="header.xsl"/>
    <xsl:output method='html'/>
    <xsl:template match="/">
        <html>
            <body>
                <xsl:call-template name="header"/>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>
The included template is here: Example 10
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method='html'/>
    <xsl:template name="header">
        <a href="#">a list of links</a>
    </xsl:template>
</xsl:stylesheet>
And that's how to include one template inside of another template. Note that the <xsl:template> tag in our included file doesn't have the match attribute, but rather has the name tag.

Finally, if you want to apply formatting changes with XSL, I highly recommend you use Cascading Style Sheets. You might want to use XSL Formatting Objects, but support for XSL-FO is non-existant in browsers at this point and would not do anything whatsoever. So if you want to do formatting, use CSS.

There is so much more that XSL can do if you read the XSLT documentation provided by the W3. But that is 65 pages worth of puffy language and what I felt was little useful documentation. If you want a useful, inexpensive guide to the functions available in XSL, I recommend that you look at purchasing the XSLT 1.0 Pocket Reference.