XSLT in PHP
Like XML, PHP 4 provides the XSLT extension, while PHP 5 provides the W3C compliment XSL extension. We will focus here on PHP 5's implementation of the XSL extension. Before using XSL, it must be enabled and the required libraries must also be installed on the host operating system.

Installation

Windows
To check whether or not XSL is already enabled on a server, create a file which calls the phpinfo() function and check for the XSL section.
On Windows, XSL support requires two dll files.

    * libxslt.dll, which should be in the root PHP installation directory. This file can be obtained from zlatkovic.com
    * php_xsl.dll, which should be inside the extension directory as set in the extension_dir directive in php.ini

To enable XSL support, add or uncomment the following line in the php.ini file:

extension=php_xsl.dll

Unix
On Unix platforms, the XSL extension needs to be enabled at compile time and requires the libxslt libraries from xmlsoft to be installed. After obtaining the source code from xmlsoft, extract it and follow the familiar configure, make, make install, routine to install them:

# tar xvf libxslt-1.1.2.tar.bz2 -–bzip2
# cd libxslt-1.1.2
# ./configure
# make
# su -c "make install"

Finally, we need to recompile PHP enabling the XSL extension:

# cd php-5.1.2
# ./configure –-with-xsl=/path/to/libxslt-1.1.2
# make
# su -c "make install"

Transforming XML with PHP
Transforming an XML document generally requires the following steps:

   1. load the XSL stylesheet into a DOMDocument object.
   2. create an instance of an XSLTProcessor object
   3. import the DOMDocument containing the style sheet into the XSLTProcessor
   4. load the XML document to be transformed into a DOMDocument object
   5. transform the XML



PHP - book_summary.php:
<?php
// 1) load the XSL style sheet into a DOMDocument object.
$xsl = new DOMDocument;
$xsl->load('xml/book_summary.xsl');

// 2) create an instance of an XSLTProcessor object
$processor = new XSLTProcessor;

$processor->setParameter('', 'script-path', $_SERVER['PHP_SELF']);
$processor->setParameter('', 'select-isbn', @$_GET['isbn']);

// 3) import the DOMDocument containing the style sheet into the XSLTProcessor
$processor->importStyleSheet($xsl);

// 4) load the XML document to be transformed into a DOMDocument object
$xml = new DOMDocument('1.0');
$xml->load('xml/library.xml');

// 5) transform the XML
$transformedXml = $processor->transformToXml($xml);

echo($transformedXml);
?>

The transformToXml method transforms the XML using the imported stylesheet, returning the string representation of the transformed XML. Similarly, the transformToDoc function can be used if you require a DOMDocument representation of the transformed XML.

Using XSLT to transform XML makes the code cleaner and allows for clear separation between processing logic and display logic. It also serves to provide a robust way of describing the transformation in a multi-platform, multi-language environment. As we will discover later, we can apply the same XSL stylesheet to the XML using Javascript too.
Passing Variables to an XSL Stylesheet
One of the most powerful features of XSL is its ability to receive variables from external entities which can be used anywhere in the stylesheet. In XSL, an external variable is declared using the xsl:param element as follows:

    <xsl:param name="select-isbn" />

The current XSL stylesheet lists all books with details accompanying each one. By adding an external variable and making a slight change to the template we can display information on only one specific book, based on a variable passed in the query string containing the ISBN number. A second external variable called script-path will also be added, which contains the URI of the current script.

We need only change the root template. Notice how we now use xsl:for-each to iterate the book list and display the detailed book information at the bottom using the book template:

XSL:    
<xsl:param name="select-isbn" />
    <xsl:param name="script-path" />
        <!-- start with matching the root of the XML -->
    <xsl:template match="/library">
        <html>
            <head>
                <title>XML Library</title>
            </head>
            <body>
                <ul id="bookList”>   
                <xsl:for-each select="books/book">
                    <xsl:sort select="title" />
                    <xsl:variable name="isbn" select="@isbn" />
                    <li>
                    <a href="{$script-path}?isbn={$isbn}">
                        <xsl:value-of select="title"/>
                    </a>
                    </li>
                </xsl:for-each>
                </ul>
                <!-- the apply-templates attribute applies all other XSL templates to direct descendants of the currently
                     selected node. Some XSL processors spit out text if no template exists for a
                     given descendant, therefore an xPath expression is used -->
                  <xsl:apply-templates select="books/book[@isbn=$select-isbn]" />
            </body>  
        </html>
</xsl:template>   

The variable's values can be passed to the XSL stylesheet using the setParameter function. The next time an XML document is transformed, these variables will be available inside the stylesheet:

$processor->setParameter('', 'script-path', $_SERVER['PHP_SELF']);
$processor->setParameter('', 'select-isbn', htmlentities(@$_GET['isbn']));

The first function parameter sets the namespace URI for the parameter. This can be left as a zero length string when using the default XSL namespace.

XSLT in Javascript
At the time of writing only Internet Explorer and Firefox have support for XSL transformations. Where possible we should take advantage of this and allow the transformations to be carried out on the client side. This not only saves processing time on the server; the XML which is to be transformed is often smaller in size than its HTML counterpart meaning bandwidth savings too. Both Firefox and Internet Explorer have quite different implementations of XSLT.

Firefox
The Firefox XSLT implementation is very similar to PHP's, as, it too is W3C compliment. In Javascript, we must code more defensively, as we will be acquiring the XML to be transformed and the XML stylesheet via an HTTP connection.

The first step is to load the XSL stylesheet into a DOMDocument. This is achieved with the createDocument function:

var oXsl = document.implementation.createDocument('', '', null);
oXsl.async = false;
oXsl.load('book_summary.xsl');

Notice how we deliberately set async to false here. If you intend to load the stylesheet while the page is loading, it is best to use a synchronous request. If you wish to use an asynchronous request, the DOMDocument fires the onload event handler once the XML document has loaded completely.

Before continuing, it is wise to check that the XSL stylesheet has been loaded properly. We can do this by testing the root element of the XML document. If an error occurred while loading the XML document, this will be replaced with a <parsererror> tag:

if ((!oXsl .documentElement) || (oXsl .documentElement.tagName == 'parsererror')) {
    /* an error occurred while loading the document */       
}

Once we have confirmed that the stylesheet has been loaded correctly, we can import it as a stylesheet and transform it in exactly the same way as PHP:

var oXslt = new XSLTProcessor();
oXslt.importStylesheet(oXslt);


var oTransformedDom = bookInfoXsl.transformToDoc(oXmlDom);

Internet Explorer
In Internet Explorer, the DOMElement object supports the transformNode method. However, each time this method is called, the XSL stylesheet must be recompiled, making it very sluggish.

Microsoft does however provide the MSXML2.XslTemplate Active X control which is similar to the XSLTProcessor. The compiled stylesheet is cached by the browser, therefore transformations are a lot quicker. Like the XSLTProcessor object in Firefox and PHP, the stylesheet is imported as DomDocument object.

The XSLTemplate object requires a FreeThreadedDomDocument, which implements an identical interface to the standard DomDocument object, with the difference of allowing simultaneous access to the object from multiple threads.

var oXsl = new ActiveXObject('Msxml2.FreeThreadedDOMDocument.3.0');
oXsl.async = false;
oXsl.load('book_summary.xsl');

If an error occurred while parsing the XML, the parseError, property of the DOMDocument will be available and the errorCode will be non zero:

if ((oXsl.parseError) && (oXsl.parseError.errorCode != 0)) {
    /* an error occurred while loading the document */            
}

We now need to load the style sheet into the processor and we are ready to make the transformation:

var oXslt = new ActiveXObject("Msxml2.XSLTemplate.3.0");
oXslt.stylesheet= oXsl;
       
oXsltProcessor = oXslt.createProcessor();
oXsltProcessor.input = oXmlDom;
oXsltProcessor.transform();

var transformedXml = oXsltProcessor.output;
An XSLT and Ajax Example
This is where the technologies of XML, XSLT and xPath all come together. Through the correct utilisation of these technologies, where available, you can reward your visitors with a feature-rich user interface.

Extending our original XSLT application by adding an Ajax module, we will display the picture and the book synopsis when the users mouse pointer hovers over a link to a specific book. If the web browser supports XSLT, the PHP script will return the XML for the book requested and allow the browser to transform it. If not, the XML is transformed by the PHP script and returned as HTML.

Book Summary Display

The XSL Stylesheet
We first start by making a small change to the book_summary.xsl stylesheet, adding a small CSS stylesheet and a link to the external Javascript containing the Ajax engine to the root template.
   
XSL:
<xsl:template match="/library">
    <html>
        <head>
        <title>XML Library</title>
        <style type="text/css">
                .bookcover {
                    float: left;
                    margin: 5px 15px 20px 0px;
                }
                       
                /* the book summary won't be displayed unless Aajx is supported */              
                #bookSummary {
                    position: absolute;
                    border: 3px solid black;
                    background-color: gray;
                    display: none;
                    padding: 5px;
                    margin-right: 100px;
                }
        </style>
        </head>
        <body>
           <ul id="bookList">   
                <xsl:for-each select="books/book">
                      <xsl:sort select="title"/>
                        <xsl:variable name="isbn" select="@isbn" />
                      <li>
                        <a href="{$script-path}?isbn={$isbn}">
                            <xsl:value-of select="title"/>
                        </a>
                    </li>
                </xsl:for-each    >
            </ul>          
            <xsl:apply-templates select="books/book[@isbn=$select-isbn]" />
            <div id="bookSummary"></div>
            <script src="xml_helper_functions.js" type="text/javascript"></script>
            <script src="book.js" type="text/javascript"></script>
            </body>  
    </html>
</xsl:template>

We also need an additional XSL stylesheet which will transform the XML for a single book, to HTML.

XSL - single_book_html.xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="UTF-8"
        />
    <xsl:template match="book">
        <div>
            <xsl:variable name="isbn" select="@isbn" />
            <xsl:if test="hascover = 'yes'">
                <img class="bookcover" src="book_covers/{$isbn}.jpg" />
            </xsl:if>
            <div>
                <h2><xsl:value-of select="title" /></h2>
                <p><xsl:value-of select="synopsis" /></p>
            </div>
        </div>
    </xsl:template>
</xsl:stylesheet>

Server Side PHP
The PHP script single_book.php, is the server side portion of the Ajax application. It receives two variables in the query string: the isbn number and a boolean variable indicating whether the browser sending the request supports XSLT. The script either outputs XML or HTML depending upon the value of the xslt variable.
PHP - single_book.php:
<?php
if (@$_GET['source']) {
    highlight_file(__FILE__);
    exit;
}
    /* load external variables */
    $isbn = htmlentities(@$_GET['isbn']);
    $xslt = (boolean) @$_GET['xslt'];

    /* this DOMDocument will contain the XML for a single book */
    $returnDom = new DOMDocument;

    /* load the complete libarary XML */
    $xml = new DOMDocument('1.0');
    $xml->load('xml/library.xml');

    /* use an xPath expression to extract the book containing the isbn number we want */
    $xPath = new DOMXpath($xml);
    $book = $xPath->evaluate("/library/books/book[@isbn='$isbn']")->item(0);

    /* if the book was found, append it to our return DOM object */
    if ($book) {
        $book = $returnDom->importNode($book, true);
        $returnDom->appendChild($book);
    }

    if ($xslt) { // browser supports XSLT, let it do the transformation
        header('Content-Type: application/xml'); // send the correct MIME type
        echo($returnDom->saveXml());
    } else {// borwser does not support XSLT, therefore we do the transformation on its behalf
        /* load the stylesheet */
        $xsl = new DOMDocument;
        $xsl->load('xml/single_book_html.xsl');
        
        /* import the stylesheet */
        $processor = new XSLTProcessor;
        $processor->importStyleSheet($xsl);
            
        /* transform it to HTML and output the result */
        echo($processor->transformToXml($returnDom));
    }
?>

Javascript Helper Functions
Due to the different implementations of XSLT, the Ajax engine is the most complex part of the application. Several helper functions have been defined, which are as follows. Each function chooses the browser-dependent method to complete the task.

    * createDomFromUri(uri, freeThreaded) – loads and returns an instance of a DOMDocument from a specified location on the Internet. n.b: only documents from the same domain can be loaded

      freeThreaded - (Internet Explorer Only) If set to true a FreeThreadedDomDocument is returned.
    * getDom(freeThreaded) – returns an instance of  DOMDocument object.

      freeThreaded - (Internet Explorer Only) If set to true a FreeThreadedDomDocument is returned.
    * getXMLHTTP() - returns an instance of an XMLHTTPRequest object. Returns false if it is unavailable.
    * loadXmlFromString(sXml) - loads an XML DOMDocument object from a valid string representation of XML.

      sXML - the string representation of the XML to be parsed
    * loadXslStylesheet(uri) – loads an XSLT stylesheet returning an XSLTProcessor object

       uri – the location of the XSLT stylesheet
    * parseXmlDom(oXmlDom) – returns true if the Xml document is valid and well formed and false if not

      oXmlDom – the DOMDocument to check
    * transformXml(oXmlDom, oXsltProcessor) – transforms an XML DOMDocument. W3c compliant browsers return a DOMDocument object representing the transformed XML. Internet Explorer returns a string representation of the transformed XML.

      oXmlDom – the XML DOMDocument object to be transformed
      oXsltProcessor – the XSLT processor object to use when making the transformation.

The code from these functions is declared in the xml_helper_functions.js file contained in the ZIP file accompanying this article.

The Ajax Engine
The init function initialises the Ajax environment and assigns all the required event handlers. If Ajax is not available via the XMLHTTPRequest object, the function exits and no event handlers are assigned.

Javascript:
var xmlHTTP = false; // holds a reference to an XMLHTTPRequest object
var bookInfoXsl = false; // holds a reference to the XSL transformer object

var bookSummary; // holds a reference to the book summary div


init(); // initialise the Ajax application

function init()
{
    if (!(xmlHTTP = getXMLHTTP())) {
        return; // don't continue if XMLHttpRequest isn't available
    }

    bookSummary = document.getElementById('bookSummary');

    /* these event handlers ensure that if the mouse if over the book sumamry but not the link
           it does not disappear */
    bookSummary.onmouseover = function(e){  bookSummary.style.display = 'block';};
    bookSummary.onmouseout = function(e){   bookSummary.style.display = 'none'; };

    /* attempt to load the XSL stylesheet */
    bookInfoXsl = loadXslStylesheet('xml/single_book_html.xsl');

    var bookLinks = document.getElementById('bookList').getElementsByTagName('a');

    /* add event handlers to links inside the book list - the summary will be displayed while the mouse
       hovers over the link */
    for(var x = 0; x < bookLinks.length; x++)
    {   
        var oCurrent = bookLinks[x];

        oCurrent.onmouseover = displayBookSummary;
        oCurrent.onmouseout = hideBookSummary;

    }
}

The displayBookSummary function is executed when the user moves the mouse pointer over any of the book title links. On execution it makes an HTTP request to the single_book.php script. The isbn number is extracted from the href attribute of the link and appended to the query sting along with the xslt variable that indicates whether or not the browser supports XSLT.

Javascript:
function displayBookSummary(event)
{
    if (! (xmlHTTP)) { // do not continue if XMLHTTPRequest is not available
        return;
    }

    if (! event) { // In Internet Explorer the event object is not passed by default
        event = window.event;
        var target = event.srcElement;
    } else {
        var target = event.target;
    }

    /* find the co-ordinates of the mouse - this is where we will display the book sumamry DIV */
    var x = event.clientX;
    var y = event.clientY;

    bookSummary.style.top = y + 'px';
    bookSummary.style.left = x + 'px';

    /* extract the book ISBN */
    var href = target.getAttribute('href');
    var isbn = encodeURIComponent(href.substring(href.indexOf('isbn=') + 5, href.length));

    /* tell the receiving script whether we can do the transformation ourselves */
    xslt = bookInfoXsl?1:0;
   
    /* abort any past requests and send the new request */   
    xmlHTTP.abort();
    xmlHTTP.open('get', 'single_book.php?xslt=' + xslt + '&' + 'isbn=' + isbn, true);
    xmlHTTP.onreadystatechange = displayBookSummaryCallback;
   
    xmlHTTP.send(null);
}

The displayBookSummary callback is executed when a response has been received from the XMLHTTPRequest object. The type of response received is dependant on whether or not the browser supports xslt. If XSLT is supported, the response is an XML fragment, this is then parsed, transformed and the result of the transformation appended to the bookSummary div.

Javascript:
function displayBookSummaryCallback()
{
    if (xmlHTTP.readyState != 4) { // not fully loaded
        return;
    }

    if (! xmlHTTP.responseText) { // this happens if abort is used in Firefox and Netscape
        return;
    }

    if (bookInfoXsl) { // XSLT is avaiable so the reponse should be an XML response
        /* load the response into a DOMDocument */
        var oXml = loadXmlFromString(xmlHTTP.responseText);

        if (! parseXmlDom(oXml)) {
            /* xml cannot be parsed */
            return;
        }

        /* transform the XML */
        var transformed = transformXml(oXml, bookInfoXsl);

        if ((typeof transformed) == "object") { // w3c compliant browsers
            /* append the new DOM to the HTMl document */
            if (! bookSummary.firstChild) {
                bookSummary.appendChild(transformed.documentElement);   
            } else {
                bookSummary.replaceChild(transformed.documentElement, bookSummary.firstChild);
            }
        } else { // Internet Explorer
            bookSummary.innerHTML = transformed;
        }
    } else { // XSLT is not supported, therefore we have an HTML response which we just insert into the book summary DIV
        bookSummary.innerHTML = xmlHTTP.responseText;
    }

    bookSummary.style.display = 'block';       
}

The hideBookSummary function is executed when the mouse pointer moves off the book link and hides the book summary div:

Javascript:
function hideBookSummary()
{
    bookSummary.style.display = 'none';
}

Conclusion
In this article we have demonstrated how XSLT can be used in conjunction with PHP and Javascript to create an Ajax application. We have, however, only touched the tip of the Ajax iceberg. It is easy to become overwhelmed by the seemingly countless number of possible applications which come from marrying these technologies together. When developing Ajax applications in particular, take the following points into consideration:

    * Ensure your application is still fully functional in the worst case scenario and still accessible to all its users.
    * Use Ajax technologies only where required (i.e: to help save bandwidth, enhance the users experience) and not just for the sake of using it.
    * Test...test and test all Javascript code thoroughly in all mainstream web browsers and ensure that if a script fails, it fails gracefully without breaking the user interface.