XSL Transformation With PHP And Sablotron

Perform XSL transformations on the server with PHP's Sablotron extension.

Everything You Need, When You Need It

You have to admit, we're a full-service shop.

First, we showed you how to use PHP to develop cutting-edge Web applications. Then we showed you how to impress your boss with your XML and XSLT prowess. And finally, we gave you the skinny on combining the two to capture the best of both worlds, and a hefty paycheck to boot.

In case you were thinking that's all she sang, think again. What you've learned thus far is just the tip of the iceberg. PHP's increasing support for XML-based technologies makes new and different types of applications possible - as this article will demonstrate, by combining PHP with XSLT to perform server-side transformations of XML data. Keep reading.

Getting Down To Business

Before we get into the nitty-gritty of XSLT processing with PHP, I'd like to take some time to explain how all the pieces fit together.

In case you don't already know, XML is a markup language created to help document authors describe the data contained within a document. This description is accomplished by means of tags, very similar in appearance to regular HTML markup. However, where HTML depends on pre-defined tags, XML allows document authors to create their own tags, immediately making it more powerful and flexible. There are some basic rules to be followed when creating an XML file, and a file can only be processed if these rules are followed to the letter.

Once a file has been created, it needs to be converted, or "transformed", from pure data into something a little more readable. XSLT, the Extensible Style Language (Transformations), is typically used for such transformations; it's a powerful language that allows you to generate different output from the same XML data source. For example, you could use different XSL transformations to create an HTML Web page, a WML deck, and an ASCII text file...all from the same source XML.

There's only one problem here: most browsers don't come with an XML parser or an XSL processor. The latest versions of Internet Explorer and Netscape Gecko do support XML, but older versions don't, and none of them have an XSL processor. And this brings up an obvious problem: how do you use an XML data source to generate HTML for these older browsers?

The solution is to insert an additional layer between the client and the server, which takes care of processing the XML with XSLT and returning the rendered output to the browser. And that's where PHP comes in - PHP4 supports XML parsing, through its DOM and XML extensions, and includes an XSL processor, through its Sablotron extension.

Sablotron, you say. Sounds scary. What's that?

Sablotron is, according to its creators, "...a fast, compact and portable XML toolkit implementing XSLT, DOM and XPath". Developed by Ginger Alliance (http://www.gingerall.com), the Sablotron engine is a powerful, standards-compliant XSLT processor, with support for most common XSLT constructs.

The latest version of PHP comes with an implementation of this engine, allowing PHP developers to perform XSLT transformations using regular PHP functions. Note, however, that you may need to recompile your PHP binary with the "--with-sablot" parameter to activate XML support (Windows users get a pre-built binary with their distribution.)

Over the course of this article, I'll be demonstrating how you can use PHP's Sablotron extension to perform XSLT transformations of XML data on the server. I'll be assuming you know the basics of PHP, XML, XSLT and XPath; if you don't, you should take a look at the links at the end of the article before proceeding further.

Start It Up

Let's start with a simple example. Consider the following XML document:

<?xml version="1.0"?>
<me>
    <name>John Doe</name>
    <address>94, Main Street, Nowheresville 16463, XY</address>
    <tel>738 2838</tel>
    <email>johndoe@black.hole.com</email>
    <url>http://www.unknown_and_unsung.com/</url>
</me>

Now, let's suppose that I need to transform this XML document into an HTML document which looks like this:

Here's the XSLT stylesheet to accomplish this transformation:

<?xml version="1.0"?>

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

<xsl:template match="/">

    <html>
    <head>
    </head>
    <body>
    <h1>Contact information for <b><xsl:value-of select="me/name" /></b></h1>

    <h2>Mailing address:</h2>
    <xsl:value-of select="me/address" />

    <h2>Phone:</h2>
    <xsl:value-of select="me/tel" />

    <h2>Email address:</h2>
    <xsl:value-of select="me/email" />

    <h2>Web site URL:</h2>
    <xsl:value-of select="me/url" />

    </body>
    </html>

</xsl:template>

</xsl:stylesheet>

So I've got the XML data and the XSLT stylesheet. All that remains is to put them together - which is where PHP comes in. Take a look at the following script, which brings it all together:

<?php

// store XML and XSL content as variables
$xmlstring = join('', file('person.xml'));
$xslstring = join('', file('person.xsl'));

// call the XSLT processor directly
xslt_process($xslstring, $xmlstring, $result);

// output the result
echo $result;
?>

Let's dissect this a little.

  1. First, I've used the file() function to read the contents of the XML document and XSLT stylesheet into an array, and then used PHP's very cool join() function to combine all the elements of the array (which correspond to lines from the file) into a single string. Each string is then stored as a variable.
$xmlstring = join('', file('person.xml'));
$xslstring = join('', file('person.xsl'));
  1. You're probably wondering why I bothered. Well, the xslt_process() function, which happens to be PHP's primary workhorse for this sort of thing, accepts three parameters: an XML data string, an XSLT data string, and a variable to hold the results of the transformation.
xslt_process($xslstring, $xmlstring, $result);
  1. Once the processing is complete, and $result contains the output, all that's left is to print it.
echo $result;

And here's what the end product looks like:

Handling Things Better

Now, though the script you just saw is pretty primitive, it gets the job done. However, when you're working on a real project, you need to be more professional in your approach. So let's take this to the next level, with a slightly different version of the script above:

<?php

// the files
$xmlfile = "person.xml";
$xslfile = "person.xsl";

// create the XSLT processor
$xslthandler = xslt_create() or die("Houston, we have a problem. No XSLT handler available. Mission aborted.");

// process the two files to get the desired output
xslt_run($xslthandler, $xslfile, $xmlfile);

// get and print the result
echo xslt_fetch_result($xslthandler);

// free the resources occupied by the handlers
xslt_free($xslthandler);

?>

This is a slightly more structured approach. After defining the filenames for the XML and XSLT content, I've used the xslt_create() function to create a new instance of the XSLT processor and return a handle to it.

// create the XSLT processor
$xslthandler = xslt_create() or die("Houston, we have a problem. No XSLT handler available. Mission aborted.");

This handle is used in all subsequent XSLT operations.

Next, I've used the xslt_run() function to read and process the XML and XSLT files, and store the results of the processing in the default result buffer.

// process the two files to get the desired output
xslt_run($xslthandler, $xslfile, $xmlfile);

Once the processing is complete and the output dumped into the default result buffer, I've used the xslt_fetch_result() function to fetch the contents of the buffer and print it to the browser.

// get and print the result
echo xslt_fetch_result($xslthandler);

Finally, it's a good idea to clean things up by destroying the handle created during this process, so as to not occupy valuable memory.

// free the resources occupied by the handlers
xslt_free($xslthandler);

The xslt_free() function frees up the memory occupied by the XSLT processor.

Using xslt_run() is often preferable to using xslt_process(), since the xslt_run() function is happy to accept file references to the XML and XSLT data as arguments (as opposed to xslt_process(), which only accepts string variables). Using xslt_run() can, therefore, often save you a few lines of code when performing server-side transformation with PHP.

An Evening At The Moulin Rouge

What you've just seen is a generic way to process XML data with PHP. It isn't the only one, though; for larger, more complex files, PHP offers yet another method of applying an XSLT stylesheet to your XML.

Consider the following XML file:

<?xml version="1.0"?>

<review id="57" category="2">

    <title>Moulin Rouge</title>

    <cast>
        <person>Nicole Kidman</person>
        <person>Ewan McGregor</person>
        <person>John Leguizamo</person>
        <person>Jim Broadbent</person>
        <person>Richard Roxburgh</person>
    </cast>

    <director>Baz Luhrmann</director>

    <duration>120</duration>

    <genre>Romance/Comedy</genre>

    <year>2001</year>

    <body>
A stylishly spectacular extravaganza, <title>Moulin Rouge</title> is hard to categorize; it is, at different times, a love story, a costume drama, a musical, and a comedy. Director <person>Baz Luhrmann</person> (well-known for the very hip <title>William Shakespeare's Romeo + Juliet</title>) has taken some simple themes - love, jealousy and obsession - and done something completely new and different with them by setting them to music.
    </body>

    <rating>5</rating>

    <teaser>Baz Luhrmann's over-the-top vision of Paris at the turn of the century is witty, sexy...and completely unforgettable</teaser>

</review>

And the corresponding XSLT stylesheet to transform this information into a simple Web page:

<?xml version="1.0"?>

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

<xsl:template match="/review">
    <html>
    <head>
    <basefont face="Arial" size="2"/>
    </head>
    <body>
    <xsl:apply-templates select="title"/> (<xsl:apply-templates select="year"/>)
    <br />
    <xsl:apply-templates select="teaser"/>
    <p />
    <xsl:apply-templates select="cast"/>
    <br />
    <xsl:apply-templates select="director"/>
    <br />
    <xsl:apply-templates select="duration"/>
    <br />
    <xsl:apply-templates select="rating"/>
    <p>
    <xsl:apply-templates select="body"/>
    </p>
    </body>
    </html>
</xsl:template>

<xsl:template match="title">
<b><xsl:value-of select="." /></b>
</xsl:template>

<xsl:template match="teaser">
<xsl:value-of select="." />
</xsl:template>

<xsl:template match="director">
<b>Director: </b> <xsl:value-of select="." />
</xsl:template>

<xsl:template match="duration">
<b>Duration: </b> <xsl:value-of select="." /> minutes
</xsl:template>

<xsl:template match="rating">
<b>Our rating: </b> <xsl:value-of select="." />
</xsl:template>

<xsl:template match="cast">
<b>Cast: </b>
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="person[position() != last()]">
<xsl:value-of select="." />,
</xsl:template>

<xsl:template match="person[position() = (last()-1)]">
<xsl:value-of select="." />
</xsl:template>

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

<xsl:template match="body">
    <xsl:apply-templates/>
</xsl:template>

<xsl:template match="body//title">
    <i><xsl:value-of select="." /></i>
</xsl:template>

<xsl:template match="body//person">
    <b><xsl:value-of select="." /></b>
</xsl:template>

</xsl:stylesheet>

Here's the PHP script which brings the two together:

<?php

// XML file
$xmlstringarray = file("movie.xml");

// XSL file
// note that you may need to specify an absolute path here
$xslfile = "movie.xsl";

// start transforming...
xslt_output_begintransform($xslfile);

    for ($count=0; $count<sizeof($xmlstringarray); $count++) {
        echo $xmlstringarray[$count];
    }

// end transformation and output
xslt_output_endtransform();
?>

I'll start by storing the content of the XML file in an array.

// XML file
$xmlstringarray = file("movie.xml");

Next, I'll store the location of the XSLT file in a variable.

// XSL file
// note that you may need to specify an absolute path here
$xslfile = "movie.xsl";

Finally, it's time to call the xslt_output_begintransform() function.

xslt_output_begintransform($xslfile);

    for($count = 0;$count < sizeof($xmlstringarray); $count++)
    {
    echo $xmlstringarray[$count];
    }

xslt_output_endtransform();

The xslt_output_begintransform() function is a special function which take only one parameter: the location of the XSLT file. Once this function is invoked, the XSLT processor will transform all the content that it encounters as per the rules in the named file until it's told to stop via the function xslt_output_endtransform().

In this case, I've written a simple "for" loop to iterate through the elements of the XML array, printing each element as it finds it. The XSLT processor will transform this output as it is printed, providing the desired result.

This technique comes in particularly handy if you don't have your XML data in a static file, but generate it dynamically - say, from a database, or by combining multiple XML documents into a single file. You can write PHP code to obtain the data and massage it into an XML-compliant format, and then enclose that code in calls to xslt_output_begintransform() and xslt_output_endtransform() to transform it.

Mistakes Happen

Like any good API, PHP's Sablotron extension comes with an excellent error-handling mechanism. I'll modify one of the examples above to illustrate:

<?php

// the files
$xmlfile = "person.xml";
$xslfile = "person.xsl";

// create the XSLT processor
$xslthandler = xslt_create() or die("Houston, we have a problem. No XSLT handler available. Mission aborted.");

    // process the two files to get the desired output
    if (xslt_run($xslthandler, $xslfile, $xmlfile)) {
        // get and print the result
        echo xslt_fetch_result($xslthandler);
    } else {
        // error
        echo "Something bad just happened.\n";
        echo "Error code: " . xslt_errno($xslthandler) . "\n";
        echo "Error string: " . xslt_error($xslthandler) . "\n";
        exit;
    }

// free the resources occupied by the handlers
xslt_free($xslthandler);

?>

Much of the code here has been culled from the previous examples. The difference: this script includes a primitive error-handling mechanism, which checks whether or not the processing was successful, and returns an error code and message if not.

Both the xslt_errno() and xslt_error() functions accept a handle for the XSLT processor, and return the last error code and message generated by the processor. At least that's the theory - in my experiments with PHP 4.0.6, these functions failed to work as advertised.

Publish Or Die!

Now that you've seen the different techniques available to process XSLT stylesheets, here's a simple example which demonstrates a real-life use of this technology - a simple XML to HTML publishing system.

Let's assume that you have a collection of data, all neatly tagged and marked up in XML. Now, you need to use this information in different places. Some of it may need to be converted into HTML, for use on a Web site; other bits of it may need to be converted into WML, for wireless transfer, or imported into a database, or transformed into PDF documents, or...

With PHP's XSLT engine, accomplishing all this becomes a snap. All you need are separate stylesheets, each one taking care of a particular type of conversion. Feed these stylesheets to a PHP script which knows how to handle them, and Bob's your uncle.

As an example, consider the following script, which accepts XML and XSLT input and saves the results of this transformation as an HTML document:

<?php

// the fodder for the Sablotron XSLT processor
$xmlfile = "person.xml";
$xslfile = "person.xsl";
$htmlfile = "/www/person.html";

// create the XSLT processor
$xslthandler = xslt_create() or die("Can't create XSLT handle!");

    // process the two files to get the desired output
    if (xslt_run($xslthandler, $xslfile, $xmlfile)) {
        // get result buffer
        $result = xslt_fetch_result($xslthandler);

        // publish the result to a static HTML file
        $htmlfp = fopen($htmlfile, "w+");
        fputs($htmlfp, $result);
        fclose($htmlfp);

        echo "File $htmlfile successfully created!";
    } else {
        // error handler
        echo "An error occurred:\n";
        echo "Error number: " . xslt_errno($xslthandler) . "\n";
        echo "Error string: " . xslt_error($xslthandler) . "\n";
        exit;
    }

// clean up
xslt_free($xslthandler);
?>

Granted, it's pretty simple - but isn't that a good thing?

In a similar manner, you could write scripts to convert your XML data into other formats, insert it into a database or exchange it with other servers.

Endzone

That's about it. If you're interested in learning more about PHP and XSLT, take a look at the following links:

The PHP manual, at http://www.php.net/manual/en/ref.xslt.php

A guide to installing PHP and Sablotron on Windows, at http://shanx.com/php/xsl/getXsl.htm

Documentation and a list of known conformance issues with the Sablotron processor, at http://www.gingerall.com/charlie/ga/act/gadoc.act?pg=sablot

XSLT Basics, at INSERT XSLT1 LINK HERE

XML Basics, at INSERT XML1 LINK HERE

Till next time...stay healthy!

Note: All examples in this article have been tested on Linux/i586 with PHP 4.0.6. Examples are illustrative only, and are not meant for a production environment. YMMV!

This article was first published on10 Nov 2001.