picture of Justin Grant
In my last article I covered the use of the expat functions available to PHP for parsing XML documents using the SAX method. The sample code was a class that read the XML document and generated the HTML to present the data in a nice table. This example was far from perfect due to the fact that the presentation of the data was not separate from the class code that had to parse the XML document. This meant that a slight modification to the HTML generated by the class could break the actual parsing functionality of the class.
This is not the ideal way of doing things in a production environment, hence the purpose of this article in explaining how to parse XML documents using XSLT (eXtensible Stylesheet Language Transformations). XSLT is a language for transforming XML documents into other XML documents or HTML documents. I will not discuss XML, XSL or XSLT syntax in this article, for a good introduction to these please refer to the links at the end of this article.The focus of this article is on how to transform XML documents with XSLT using the Sablotron XSLT processor.
Sablotron is the result of a project to develop a fast, reliable, compact and portable XSLT processor conforming to the W3C specification. It currently supports Linux, Windows NT and, beginning with version 0.42, Solaris. It requires the expat XML Parser. In addition to these pieces of software the PHP4 sablotron extension wrapper is required to build an extension module that you can load with the PHP4 parser to give you access to the Sablotron API. This may sound a bit complex but it's really not and the result is well worth the power that these functions will provide you with. Some links to instructions on how to do all this are provided at the end of the article.
To demonstrate the use of these functions I have written an XSLT class that takes XML and XSL wether it be in the format of a file, a URL or a String and performs the transform. I use this class to to read an XML document from the web (the Slashdot news!) and transform it into an HTML page using an XSLT document on the local file system.
I'll do my best to explain how I've done this and welcome questions in the followup section, so here goes nothing ! ...
The basic Sablotron extension module functions available to PHP4 are listed and briefly explained below :
PHP4 Sablotron extension module v0.21 functions
 
$processor = xslt_create_processor();
Creates and returns an instance of the Sablotron XSLT Processor
 
xslt_destroy_processor($processor);
Destroys the specified instance of the Sablotron XSLT Processor ($processor)
 
$strResult = xslt_process($processor,$sheetURI, $inputURI, $resultURI, $arrayParams, $arrayArguments, $bResultString,[[$errorcode],[$errormsg]]);
Using an instance of the processor($processor) this function processes an XML document($inputURI) against an XSL document($sheetURI). A resulting document can be specified ($resultURI) or NULL can be substituted if no output document is required to be written. XSL parameters ($arrayParams) and XSL arguments ($arrayArguments) can be passed if required or NULL must be specified. A boolean ($bResultString) indicates wether or not the result must be returned as a string. Optional error code ($errorcode) and error message ($errormsg) variables can be specified.
 
$strResult = xslt_process_strings($processor,$strStyleSheet,$strInput,[[$errorcode],[$errormsg]]);
Wraps the xslt_process() function except that the XSL stylesheet and XML document are passed as strings to the processor.
 
$strResult = xslt_process_files($processor,$StyleSheetfile,$Inputfile,$Outputfile,[[$errorcode],[$errormsg]]);
Wraps the xslt_process() function except that the XSL stylesheet and XML document are passed as file paths to the processor.
 

$strResult = xslt_process_stringswithbase($processor,$strStyleSheet,$strInput,$strHardBase,
[[$errorcode],[$errormsg]]);

The same as xslt_process_strings() except that the base encoding ($strHardBase) can be specified.
 
$strErrorMsg = xslt_get_error_msg($processor);
Returns the error message generated by the XSLT Processor ($processor)
 
$bOK = xslt_openlog($processor,$logfilename,[$loglevel]);
Opens a log file ($logfilename) with the optional parameter for specifiying the log level ($loglevel) to log the progress of the processor ($processor). Returns a boolean ($bOK) indicating if the call was successful.
 
$bOK = xslt_closelog($processor);
Closes the log file in use by the processor ($processor). Returns a boolean ($bOK) indicating if the call was successful.
 

 
This table is provided as a reference so that later the implementation of the example class that wraps these functions can be better understood. You will notice later on that I only use four of these functions in my class, namely xslt_create_processor(), xslt_destroy_processor(), xslt_process_files() and xslt_get_error_msg().

 
Let's look at a sample XML document that Slashdot publishes on their site and that I will transform using XSLT:
<?xml version="1.0"?>
<backslash xmlns:backslash="http://slashdot.org/backslash.dtd">
 <story>
         <title>Free Stripped-Down 3D Studio Max</title>
         <url>http://slashdot.org/article.pl?sid=00/07/30/2056209</url>
         <time>2000-07-30 20:54:56</time>
         <author>timothy</author>
         <department>introduce-me-to-some-models-please</department>
         <topic>games</topic>
         <comments>105</comments>
         <section>articles</section>
         <image>topicgames.jpg</image>
 </story>
 <story>
         <title>Preliminary Ethereal User's Guide</title>
         <url>http://slashdot.org/article.pl?sid=00/07/30/168207</url>
         <time>2000-07-30 17:06:57</time>
         <author>CmdrTaco</author>
         <department>docs-for-cool-software</department>
         <topic>news</topic>
         <comments>157</comments>
         <section>articles</section>
         <image>topicnews.gif</image>
 </story>
</backslash>
This is quite a self-explanatory XML document. It obviously contains story data that you might find on the Slashdot web site. The <story> element contains child elements (<title>, <url>, <time>, <author>, <department>, <topic>, <comment>, <section> and <image>) that contain data we can format how ever we like using the power of XSL.
If you've never seen XSL then you may be thinking "what does this XSL document look like ?". It's not very difficult to understand if you're new to it but I recommed some further study to bring yourself up to speed. Please see the links at the end of this article for introductions to XSL. Anyway here's what the XSL document contains that is going to be used to transform our XML into HTML for display in a web browser.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output
method="html"
indent="yes"
encoding="utf-8"
/>
<xsl:template match="/backslash"> 
      <html> 
         <head> 
            <title>XSLT Demo</title> 
            <STYLE type='text/css'>
               <!--
               BODY { font-size: 0em; color: gray; background: black; font-family: arial }
               a { text-decoration:none }
               a { mouse-over:white}
               -->
            </STYLE>
         </head> 
         <body bgcolor="black" text="BBBBCC" link="0077CC" vlink="0077CC" alink="FFFFFF">
            <center> 
            <table bgcolor="222222" border="0" cellpadding="0" cellspacing="0">
            <tr>
               <td bgcolor="white"></td>
               <td bgcolor="white" colspan="2"><br/></td>
            </tr>
            <tr>
               <td bgcolor="white"></td>
               <td bgcolor="white" colspan="2">
                  <a href="http://www.slashdot.org">
                     <img align="right" src="slashdotlg.gif" border="0"/>
                  </a>
               </td>
            </tr>
            <tr bgcolor="777777">
               <td></td><td colspan="2"><br/></td>
            </tr>
            
            <xsl:call-template name="stories"/>

            <tr bgcolor="white"><td></td><td colspan="2"><br/></td></tr>          
            </table>
            </center>
         </body>
      </html>
</xsl:template> 
<xsl:template name="stories">
      <xsl:for-each select="story">
         <tr>
            <td></td>
            <td width="60%">
               <font size="4">
                  <b><a href="{url}" target="_"><xsl:value-of select="title"/></a></b>
               </font><br/>
               <xsl:value-of select="author"/> (<xsl:value-of select="time"/>)<br/>
               from the "<xsl:value-of select="department"/>" dept.<br/>
               <xsl:value-of select="comments"/> comments<br/>
               <xsl:value-of select="section"/> section<br/>
            </td>
            <td bgcolor="white" >
               <center>
                  <img src="http://images.slashdot.org/topics/{image}" ALT="{topic}"/>
               </center>
            </td>
            <td width="30%"></td>
         </tr>
         <tr bgcolor="777777"><td></td><td colspan="2"><br/></td></tr>          
      </xsl:for-each>
</xsl:template>
</xsl:stylesheet> 
You'll notice that there seems to be HTML inside this document, well your right about that !. The XSL document determines how to "read" and "wrap" the data found in the XML document appropriately in HTML so that it can be viewed. A small cascading style sheet has also been included in this document.
Let's see a screenshot of what the result of this transform would look like. The image below is the result of reading a "live" XML document from the web instead of the sample shown above, after all having up- to-date news can be fun while testing out the XSL transforms.

Looks kinda cool, doesn't it ?!, there's so much more you can do with XML/XSLT but I'll leave that for you to research. We could even write an XSL transformation that generates documents viewable by PDA's and Cellular phones.
Let me focus on the code used to do this transform (after all this article is supposed to be about PHP).
I wrote a class to wrap the Sablotron functions in keeping with good OOP practises.
Here is the script that uses the class. By looking at the code it becomes obvious why using PHP Objects is a good idea and can simplify web development.

<?php

// 30.07.2000 by Justin Grant
include("class.XSLTransformer.php");

$xml="http://www.slashdot.org/slashdot.xml";
$xsl="slashdot.xsl";
$transform = new XSLTransformer();
if(
$transform->setXsl($xsl)) {
           if(
$transform->setXml($xml)) {
              
$transform->transform();
              if (
$transform->getError() == 0) {
                 echo 
$transform->getOutput();
              } else {
                 echo 
"<p>Error transforming ",$xml,".</p>\n";
              }
           } else {
              echo 
"<p>",$xml,": ",$transform->getError(),"</p>\n";
           }

$transform->destroy();
}


?>
In the script, the XSLTransformer class file is included.
I define the URL to the XML document and file path to the XSL document.
I then create an instance of the transformer class.
If I can successfully load the XSL document then I try and load the XML document. If these documents are successfully loaded then the transform is performed and the result is written out to the browser.
If loading one or both of these documents is unsuccessful then I just write out the error message property of the object.
Finally in good OOP style I destroy the object because I'm done with it. It's that simple !

<?php

/*
    XSLTranformer -- Class to transform XML files using 
    XSL with the Sablotron libraries.Justin Grant (2000-07-30)
    Thanks to Bill Humphries for the original examples on 
    using the Sablotron module.
*/
/* test */
/*
$transformer = new XSLTransformer();
if ($transform->setXsl("http://www.someurl.com/document.xsl") &&
    $transform->setXml("http://www.someurl.com/document.xml")) {
       $transformer->transform();
       echo $transformer->getOutput();
    } else {
       echo $transformer->getError();
    }
*/
class XSLTransformer {
   var 
$xsl$xml$output$error ;
/* Constructor */
   
function XSLTransformer() {
      
$this->processor xslt_create_processor();
   }
 
/* Destructor */
   
function destroy() {
      
xslt_destroy_processor($this->processor);
   }
 
/* output methods */
   
function setOutput($string) {
      
$this->output $string;
   }
   function 
getOutput() {
      return 
$this->output;
   }
 
/* set methods */
   
function setXml($uri) {
      if(
$doc = new docReader($uri)) {
         
$this->xml $doc->getString();
         return 
true;
      } else {
         
$this->setError("Could not open $xml");
         return 
false;
      }
   }
   function 
setXsl($uri) {
      if(
$doc = new docReader($uri)) {
         
$this->xsl $doc->getString();
         return 
true;
      } else {
         
$this->setError("Could not open $uri");
         return 
false;
      }
   }
/* transform method */
   
function transform() {
      
$this->setOutput(
      
xslt_process_strings($this->processor,
      
$this->xsl$this->xml, &$err));
      
$this->setError($err);
   }
/* Error Handling */
   
function setError($string) {
      
$this->error $string;
   }
   function 
getError() {
      return 
$this->error;
   }
}



/* docReader -- read a file or URL as a string */
/* test */
/*
   $docUri = new docReader('http://www.someurl.com/doc.html');
   echo $docUri->getString();
*/
class docReader {
   var 
$string// public string representation of file
   
var $type// private URI type: 'file','url'
   
var $bignum 1000000;
 
/* public constructor */
   
function docReader($uri) { // returns integer      $this->setUri($uri);
      
$this->setType();
      
$fp fopen($this->getUri(),"r");
      if(
$fp) { // get length
         
if ($this->getType() == 'file') {
            
$length filesize($this->getUri());
         } else {
            
$length $this->bignum;
         }
      
$this->setString(fread($fp,$length));
         return 
1;
      } else {
         return 
0;
      }
   }
 
/* determine if a URI is a filename or URL */
   
function isFile($uri) { // returns boolean
      
if (strstr($uri,'http://') == $uri) {
         return 
false;
      } else {
         return 
true;
      }
   }
 
/* set and get methods */
   
function setUri($string) {
      
$this->uri $string;
   }
   function 
getUri() {
      return 
$this->uri;
   }
   function 
setString($string) {
      
$this->string $string;
   }
   function 
getString() {
      return 
$this->string;
   }
   function 
setType() {
      if (
$this->isFile($this->uri)) {
         
$this->type 'file';
      } else {
         
$this->type 'url';
      }
   }
   function 
getType() {
      return 
$this->type;
   }
}

?>
The class definition for XSLTransformer is a little more involved but not much, most PHP'ers should be able to gain a good understanding of how to use Sablotron's power from PHP by going over this code carefully.
In case you were wondering, there actually are two classes defined in this file. The first is XSLTansformer ofcourse and the second is docReader. docReader allows me to read a document from a URL or a file.
The methods meant for public use in the XSLTransformer class can be briefly explained as follows:
XSLTransformer()
This is the PHP constructor and creates an instance of the Sablotron processor when an instance of itself is created.
destroy()
This is the method to call when finished using the processor. It destroys the current instance of the Sablotron processor being used.
getOutput()
Returns the result of a transform.
setXML() and setXSL()
Allows you to load the XML and XSL from a file or URL. If an error occurs they set the class error property.
transform()
Performs the transform on the XML document. Sets the error property if an error occurs.
getError()
Returns in error that may have occured during loading of a document or performing a transform.
The other methods are used by the class internally and cannot be encapsulated because PHP4 does not support this yet.
Hopefully this all helps somewhat in your quest to implement XML and XSL on your PHP platform.
Here are some links that should help you on your way:
Here's some info on obtaining and installing Sablotron and the extension module:
I installed Sablotron and the PHP module on my home brewed Linux box. I know the Sablotron processor is available for Windows and Solaris platforms to.
I'm not sure about the PHP4 Sablotron extension module on other platforms besides Linux but I believe it's possible.
I found it quite difficult to get the Sablotron module loaded by the PHP engine, but eventually got it right. The trick is to set LD_LIBRARY_PATH equal to the directory where all your PHP modules/extensions are situated.
e.g. for my installation I did the following: LD_LIBRARY_PATH = /usr/local/lib
When it is installed you should see something like the following screen if you call phpinfo(); from a script.

Hey look there's also something about Java in this info script , so stay tuned because in my next article we'll look at using Java Beans from PHP. This is really cool too !
Questions on installation, XML, XSL/XSLT and PHP are all welcome in the posts section.
-- Justin