This tutorial is for developers who wish to know how to generate HTML documents using a combination of XML data and XSL stylesheets. It also includes examples of how to use XSL parameters and included stylesheets.
The process of combining the content of an XML file and the formatting instructions of an XSL file to produce another document is known as an XSL Transformation (XSLT for short). This tutorial shows how PHP's inbuilt XSL module can be used to perform XSL transformations. It also shows how to pass optional parameters to the XSL stylesheet during the transformation process, and how you can use include files to contain shared stylesheet definitions.
While XSL files are static in nature XML files can be created 'on the fly' to contain whatever data has just been selected from the database. An example of the PHP code which can generate an XML document can be found at Using PHP 5's DOM functions to create XML files from SQL data. It is possible therefore to create complete web applications where the PHP scripts do not contain any HTML tags at all. All they do is create XML files from database queries, then use XSL transformations to generate the actual HTML documents.
This method completely splits the presentation layer (i.e. the generation of HTML documents) from the business layer (the application of business rules using a language such as PHP) so that any one of these layers can be modified without affecting the other.
All the code described in this document is contained within my downloadable sample application.
It is assumed that you have a version of PHP which has been compiled with XML and XSL support.
A previous version of this document, Using PHP 4's Sablotron extension to perform XSL Transformations was written for PHP 4 and used the XSLT extension. Please note that this extension has been removed from PHP 5 and moved to the PECL repository.
It is also assumed that you have some knowledge of XML and XSL.
Here is a sample XML document. It can either be created by an external process, or it can be created by a PHP script such as by the procedure documented in Using PHP 5's DOM functions to create XML files from SQL data.
<?xml version="1.0"?> <xample.person> <person> <person_id>PA</person_id> <first_name>Pamela</first_name> <last_name>Anderson</last_name> <initials>pa</initials> <star_sign>Virgo</star_sign> </person> <person> <person_id>KB</person_id> <first_name>Kim</first_name> <last_name>Basinger</last_name> <initials>kb</initials> <star_sign>Sagittarius</star_sign> </person> <person> ..... </person> <actbar> <button id="person_search.php">SEARCH</button> <button id="reset">RESET</button> <button id="finish">FINISH</button> </actbar> </xample.person>
An XML file contains a collection of nodes which must have an opening tag (<node>
) and a corresponding closing tag (</node>
). A node can contain either text or any number of child nodes. A node's opening tag may also contain any number of attributes in the format id="value"
.
Here is an explanation of the contents of the sample XML file:
<?xml version="1.0"> | This is the XML declaration which identifies the XML version. |
<xample.person> | This is the root node. It can contain any text, even 'root', but in this case it is constructed from the database name and the table name. There must be 1, and only 1, root node in a document. |
<person> | This identifies a node which was constructed from the contents of a database table. In this example the node name is the same as the table name. Note that there are several occurrences of this node in the XML file. |
<person_id> <first_name> |
This identifies a node which was named after a column from a row in the database table. The text between the opening and closing tags is the value from the database. |
<actbar> | This node is the parent of a series of button nodes. |
<button> | This identifies a child of the <actbar> node. Each button has a text node and an attribute with the name "id". |
Here is a sample XSL stylesheet document. These files exist outside of PHP and may even be under the control of a separate team of developers.
<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method='html'/> <!-- param values may be changed during the XSL Transformation --> <xsl:param name="title">List PERSON</xsl:param> <xsl:param name="script">person_list.php</xsl:param> <xsl:param name="numrows">0</xsl:param> <xsl:param name="curpage">1</xsl:param> <xsl:param name="lastpage">1</xsl:param> <xsl:param name="script_time">0.2744</xsl:param> <!-- include common templates --> <xsl:include href="std.pagination.xsl"/> <xsl:include href="std.actionbar.xsl"/> <xsl:template match="/"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title><xsl:value-of select="$title"/></title> <style type="text/css"> <![CDATA[ <!-- caption { font-weight: bold; } th { background: #cceeff; } tr.odd { background: #eeeeee; } tr.even { background: #dddddd; } .center { text-align: center; } --> ]]> </style> </head> <body> <form method="post" action="{$script}"> <div class="center"> <table border="0"> <caption><xsl:value-of select="$title"/></caption> <thead> <tr> <th>Person ID</th> <th>First Name</th> <th>Last Name</th> <th>Star Sign</th> </tr> </thead> <tbody> <xsl:apply-templates select="//person" /> </tbody> </table> <!-- insert the page navigation links --> <xsl:call-template name="pagination" /> <!-- create standard action buttons --> <xsl:call-template name="actbar"/> </div> </form> </body> </html> </xsl:template> <xsl:template match="person"> <tr> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="position()mod 2">odd</xsl:when> <xsl:otherwise>even</xsl:otherwise> </xsl:choose> </xsl:attribute> <td><xsl:value-of select="person_id"/></td> <td><xsl:value-of select="first_name"/></td> <td><xsl:value-of select="last_name"/></td> <td><xsl:value-of select="star_sign"/></td> </tr> </xsl:template> </xsl:stylesheet>
As you can see the XSL file contains a mixture of standard HTML tags and a series of other tags which have the <xsl:
prefix. This is similar to a PHP script which can contain a mixture of HTML tags and PHP instructions. Anything which is within an XSL tag will be processed by the XSLT engine in some way, while everything else will be output 'as is'.
Note that this example has its CSS definitions defined internally. In a proper production environment I would expect these to be held in an external file.
Here is an explanation of the various XSL tags:
<xsl:stylesheet> | Everything within this tag is a stylesheet. Note that the first stylesheet declaration identifies the official W3C XSL recommendation namespace and the version number. |
<xsl:output /> | This controls the format of the stylesheet output. The default is 'html' with other options being 'xml' and 'text'. Note that the Sablotron extension allows a method of 'xhtml' which ensures that the output conforms to the XHTML rather than the HTML standard. |
<xsl:param> | This identifies a parameter that can be referenced from anywhere within the stylesheet by its name (but with a '$' prefix). A default value may be declared within the stylesheet, although a different value may be supplied during the transformation process. |
<xsl:include /> | This is similar to PHP's include function. It allows common stylesheets to be defined once then included as and when necessary. Note that you will need to use the xsl_set_base() command within your PHP script in order to define the base directory which contains the include files. |
<![CDATA[......]]> | Identifies a piece of text to be ignored by the XML parser. Without this the '<' and '>' would cause errors as the parser would treat them as if they were part of start or end tags. |
<xsl:template match="/"> | This defines the start of a template. The match="/" attribute associates (matches) the template to the root (/) of the XML source document. |
<xsl:apply-templates> />; | This defines a set of nodes to be processed. The select="//person" attribute identifies those nodes with the name 'person'. The '//' is used to signify 'child of the root node'. This will look for a template which matches that rule, in this case <xsl:template match="person" > . |
<xsl:call-template /> | This is used to invoke a named template, as identified by the name attribute. This is analogous to calling a function within PHP. This will look for a template which matches that rule, in this case <xsl:template name="..." > |
<xsl:value-of /> | This outputs the string value of the expression. The expression can be one of the following:
{$script} . This is similar to the use of curly braces within PHP.
|
position() | This equates to the position of the node being tested among its siblings of the same name (i.e. its row number). Note that this number starts from 1, not 0 as in PHP. |
mod | This produces the modulus of the specified expression. For example, position()mod 2 tells us whether the row number of the current node is odd or even so that we can set the class attribute accordingly. |
<xsl:attribute> | Inserts an attribute into the current HTML tag. |
<xsl:choose >
<xsl:when > <xsl:otherwise > |
This is equivalent to IF .... ELSEIF .... ELSE |
It is possible to store common templates in separate files and incorporate them into your stylesheet at runtime by means of the <xsl:include>
command. This is similar to calling a subroutine in other programming languages. Here are listings and explanations of the include files used in this example.
<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <!-- //***************************************************************************** // Copyright 2003-2004 by A J Marston <tony@tonymarston.net> // Distributed under the GNU General Public Licence //***************************************************************************** --> <xsl:template name="pagination"> <table border="0" class="pagination"> <tr class="pagination"> <xsl:choose> <xsl:when test="$curpage<=1"> <!-- we are on page 1, so there is no navigation backwards --> <td class="pagination">FIRST</td> <td class="pagination">PREV</td> </xsl:when> <xsl:otherwise> <!-- insert links for first/previous page --> <td class="pagination"><a href="{$script}?page=1"><b>FIRST</b></a></td> <td class="pagination"><a href="{$script}?page={$curpage -1}"><b>PREV</b></a></td> </xsl:otherwise> </xsl:choose> <!-- insert "page x of y" --> <td class="pagination"> <xsl:text>Page </xsl:text> <xsl:value-of select="$curpage"/> <xsl:text> of </xsl:text> <xsl:value-of select="$lastpage"/> </td> <xsl:choose> <xsl:when test="$curpage=$lastpage"> <!-- we are on the last page, so there is no navigation forwards --> <td class="pagination">NEXT</td> <td class="pagination">LAST</td> </xsl:when> <xsl:otherwise> <!-- insert links for last/next page --> <td class="pagination"><a href="{$script}?page={$curpage +1}"><b>NEXT</b></a></td> <td class="pagination"><a href="{$script}?page={$lastpage}"><b>LAST</b></a></td> </xsl:otherwise> </xsl:choose> </tr> </table> </xsl:template> </xsl:stylesheet>
$curpage
, $lastpage
and $script
are parameters defined with the <xsl:param>
tag, and which can have values supplied at runtime during the XSL transformation process. Access to these values allows this template to output the following:
<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <!-- //***************************************************************************** // Copyright 2003-2004 by A J Marston <tony@tonymarston.net> // Distributed under the GNU General Public Licence //***************************************************************************** --> <xsl:template name="actbar"> <table border="0" class="actionbar"> <tr class="actionbar"> <xsl:for-each select="//actbar/*"> <!-- create a button for each element within actionbar --> <td class="actionbar"> <input type="submit" name="{@id}" value="{node()}" /> </td> </xsl:for-each> </tr> </table> <p class="script_time">page loaded in <xsl:value-of select="$script_time"/> seconds</p> </xsl:template> </xsl:stylesheet>
<xsl:for-each> | This is similar to PHP's foreach function. The "//actbar/* " directive will select all available children of the actbar node. |
(@id) | This will output the contents of the attribute with the name 'id'. |
{node()} | This will output the value from the current node. |
$script_time | This is one of the parameters that were specified during the transformation process. |
This method means that the buttons on the action bar are not hard coded within the XSL script but specified within the XML data. Thus the process which generates the XML data can vary the action buttons depending on its own internal logic.
You will find that there are numerous options available when you come to perform the XSL transformation. Below are some of these options with an explantion of their use.
This requires a single simple instruction.
$xp = new XsltProcessor();
This obtains the XSL stylesheet from an external file and loads it into the XSLT resource. It will also load in any external templates specified with the <xsl:include>
command.
// create a DOM document and load the XSL stylesheet $xsl = new DomDocument; $xsl->load('something.xsl'); // import the XSL styelsheet into the XSLT process $xp->importStylesheet($xsl);
This obtains the XML data from an external file and loads it into a separate document instance.
// create a DOM document and load the XML datat $xml_doc = new DomDocument; $xml_doc->load('something.xml');
Alternatively the XML document may be created dynamically using the procedure outlined in Using PHP 5's DOM functions to create XML files from SQL data.
Use this method to load in any optional parameters which you want to pass to the XSL transformation process. Note that with PHP version 5.0.0 you must load the parameters one at a time:
$xp->setParameter($namespace, 'id1', 'value1'); $xp->setParameter($namespace, 'id2', 'value2');
With PHP version 5.1.0 you will be able to create an array of parameters and load them in one go:
$params['id1'] = 'value1'; $params['id2'] = 'value2'; .... $xp->setParameter($namespace, $params);
This involves using the transformation method of the XSLT resource created ealier with a specified XML document:
// transform the XML into HTML using the XSL file if ($html = $xp->transformToXML($xml_doc)) { echo $html; } else { trigger_error('XSL transformation failed.', E_USER_ERROR); } // if
If you perform the XSL transformation using the sample XML and XSL files provided in this article the results should look like the following:
If the appearance is not quite to your taste then you can alter it, not by modifying any PHP code, but by altering the stylesheet definitions contained within either your .CSS file or your .XSL file.