Putting XSL and PHP to work

We all know PHP is great for web development. We also know that XSLT is a great way of formatting, or tranforming, XML data. Combine these technologies and we have a very powerful toolkit in developing large scale web sites where content is clearly separated from logic and presentation. This article will show you how to put the revamped XSLT API in PHP 4.1 to good use.

Introduction

The xslt api in php changed quite a bit in version 4.1. One of the major reasons is to make the xslt extension more loosely tied to the actual XSLT processor used. Still only Sablotron from Ginger Alliance is supported but plans are to support other libraries such as Xalan or libxslt at a later stage.

Among the more notable changes are the removal of the functions xslt_run() and xslt_output_begintransform(). When running configure you should now use the options –enable-xslt –with-xslt-sablot instead of the old option –with-sablot. If you use Sablotron with javascript-support you also need to specify –with-sablot-js. This last option is common to overlook.

A warning might be in place. This article is written on the assumption that you already know or at least have a basic understanding of both xslt and php.

Template and data

In most of the examples I will be using the xml and xslt below. Both are very simple and I will use a traditional example with books.

<?xml version='1.0'?>
<library>
  <book>
      <title>Lord of the Rings</title>
      <author>Tolkien</author>
      <description>A nice little tale about hobbits</description>
    </book>
    <book>
      <title>Soldier of the Mist</title>
      <author>Wolfe</author>
      <description>Sad story about a soldiers memory loss</description>
  </book>
</library>
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
  <html>
  <head><title>Booklist</title></head>
  <body>
  <xsl:apply-templates />
  </body>
  </html>
</xsl:template>

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

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

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

<xsl:template match="author">
  <i><xsl:value-of select="." /></i><br />
</xsl:template>

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

</xsl:stylesheet>

Using static files

Lets assume that the examples above can be found in the two files mylibrary.xml and libraryhtml.xsl. To publish the xml in html format is then very straightforward. Remember to give the full path names to the xml and xsl files. (Because of the php-bug Bug #16228 XSLT file path issues\” some might need to prefix the file path with file://)

<?php
   $xp = xslt_create();
   $result = xslt_process($xp, 'mylibrary.xml', 'libraryhtml.xsl');
   echo $result;
   xslt_free($xp);
?>

That is it. Very simple. First we use the xslt_create() function to obtain a resource handle to a xslt processor. This resource handle must be given as the first parameter to all xslt api functions. The function xslt_process() takes the resource handle and the two file names as parameters and stores the result of the transformation in the variable $result. In this case we just echo the result to the client. The last thing we do is to clean up after ourselves by freeing the xslt processor using xslt_free().

If we wanted to, xslt_process could write the result into a file instead. It’s as easy as adding a fourth parameter with a filename.

<?php
   $xp = xslt_create();
   xslt_process($xp, 'mylibrary.xml', 'libraryhtml.xsl', 'library.html');
   xslt_free($xp);
?>

A more dynamic approach

Often the xml is generated dynamically in some way. Whether it comes from a database or some other data source we need to to feed xslt_process() with xml data stored in a variable.

<?php
$xml = get_xml_from_datasource();

$arguments = array(
  '/_xml' => $xml
);

$xp = xslt_create();
$result = xslt_process($xp, 'arg:/_xml', 'libraryhtml.xsl',
                       NULL, $arguments);
echo $result;
xslt_free($xp);
?>

Again not very complicated. We create an associative array for storing arguments to xslt_process(). You will see why an array is needed below. We tell xslt_process() that we want it too read the xml from the arguments by giving it ‘arg:/_xml’ as a second parameter instead of a filename of a xml file. The arguments array is given as a fifth parameter. The fourth, file result parameter, must then be set to NULL indicating that we want the result stored in a variable.

The arguments must be supplied in an array. Apart from the xml we could also supply the xslt as an argument instead of a file. This could be useful if we have a template library stored in database as might be the case in Content Management System.

<?php
$xml = get_xml_from_datasource();
$xsl = get_xsl_from_templatelibrary();

$arguments = array(
  '/_xml' => $xml,
  '/_xsl' => $xsl
);

$xp = xslt_create();
$result = xslt_process($xp, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);
echo $result;
xslt_free($xp);
?>

This is all nice and dandy but hardly enough. In more complex environments you would want proper error handling. When developing and debugging it could be useful to log results of transformations to a log file. Sometimes sending parameters to the xsl template might be needed. Luckily we can do all that.

Error handling and logging

When calling xslt_process() it always returns a result. Whether we want the result of the transformation in a variable or not the return value evaluates to false if something went wrong. If it is false we can use the functions xslt_errno() and xslt_error() to find out what went wrong. If something goes wrong warnings will often be echoed to the client. We should always avoid that by prepending @ to the function call.

In a live system it would not be very nice to just blurt out an error code with a cryptic error message. More proper would be to automatically send an alert via email to an administrator and redirect the user to an error page begging for his forgiveness. We do want our visitors to come back now, don’t we?

<?php
$xp = xslt_create();

$result = @xslt_process($xp, 'library.xml', 'libraryhtml.xsl');
if (!$result)
{
    $msg = "An error occurred on line " .__LINE__;
    $msg .= " in " .$_SERVER['PHP_SELF'] ."\n";
    $msg .= "Error no: " .xslt_errno($xp) ."\n";
    $msg .= "Error   : " .xslt_error($xp) ."\n";
    send_message_to_admin($msg);
    header('Location: http://www.dotvoid.com/error.php');
    exit(0);
}

echo $result;
xslt_free($xp);
?>

Even better would be to wrap this functionality in one function and make all errors go there. Easier to handle and cleaner code. We do that using the function xslt_set_error_handler(). The parameter $fields contain all the information we need.

<?php
function handle_xslt_error($xp, $errorno, $level, $fields)
{
    // Handle errors
    header('Location: http://www.dotvoid.com/error.php');
    exit(0);
}

$xp = xslt_create();
xslt_set_error_handler($xp, "handle_xslt_error");

$result = @xslt_process($xp, 'library.xml', 'libraryhtml.xsl');

echo $result;
xslt_free($xp);
?>

If we want to read the information stored in the $fields parameter we can use the following code.

$msg = '';
if(is_array($fields))
{
   while(list($key, $value) = each($fields))
    {
      $msg .= "$key => $value<br>\n";
    }
   echo $msg;
}

The function xslt_set_error_handler() makes it easy to put a custom error handler in an external library file. It keeps the code clean, robust and easier to maintain. But when we are developing or debugging an application we want to know what is going on all the time. Not only when errors occur. We need logging.

<?php

$xp = xslt_create();
xslt_set_log($xp, true);
xslt_set_log($xp, 'debug.log');

$result = @xslt_process($xp, 'library.xml', 'libraryhtml.xsl');
echo $result;

xslt_free($xp);
?>

The function xslt_set_log either takes a boolean or a string for the filename of a logfile. When we turn the logging on we first call xslt_set_log() with true and then we give it the path to the logfile. So if we would like to we could turn logging off by giving the function false as the second parameter. The logging facility tells us what files (or dynamically generated xml/xsl) are used in the transformation and in how many milliseconds it took.

Sending parameters

Sending parameters to the xsl template is easy. It is often needed in more serious applications. For example when passing the name of the logged in user, what locale is going to be used or just about any dynamica data nuggets not included in the xml. In the example below we send the currentLocale to the xsl template. First we change the libraryhtml.xsl a bit. We add the xsl:param element using en-us as default.

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

<xsl:param name="locale">en-us</xsl:param>

<xsl:template match="/">
  <html>
  <head><title>Booklist</title></head>
  <body>
  <xsl:apply-templates />
  </body>
  </html>
</xsl:template>

<xsl:template match="library">
  <xsl:choose>
    <xsl:when test="$locale = 'sv'">
       You speak Swedish<br />
    </xsl:when>
    <xsl:otherwise>
       You speak American English<br />
    </xsl:otherwise>
  </xsl:choose>

  <xsl:apply-templates />
</xsl:template>

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

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

<xsl:template match="author">
  <i><xsl:value-of select="." /></i><br />
</xsl:template>

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

</xsl:stylesheet>

We add the last parameter to xslt_process() which is an array of key-value pairs stored in an associative array. A small problem is that xslt_process() will not accept a NULL instead of the optional fifth argument. Just give it an empty array and all will be fine.

<?php
$params = array('locale' => 'sv');
$xp = xslt_create();

$result = xslt_process($xp, 'mylibrary.xml', 'libraryhtml.xsl',
                       NULL, array(), $params);
echo $result;

xslt_free($xp);
?>

Conclusion

The concept presented above in all its simplicity is suitable for even the largest projects. The logic to create and/or retrieve the data will be completely separated from the presentation layer. It would be easy to add code in the logic to detect the browser type and apply different xsl templates for wml or html.

PHP mixed with HTML is a bad choice when creating anything more complex than a very small web site. Some prefer to use FastTemplates or another template implementation in PHP, which might be good for some needs. However, both XHTML and WML are based on XML which makes XSLT an even more natural choice for the web. XSLT is rich in features and extremely powerful in transforming XML to other formats. Not to mention that XSLT is a standard.

PHP

If you enjoyed this post, please consider to leave a comment or subscribe to the feed and get future articles delivered to your feed reader.

Leave Comment

(required)

(required)