A Few Examples

I l @ ve RuBoard

Now that you understand the theory, let's look at a few examples that put it all in context. This section illustrates some common examples of XSLT usage, including using it to generate documents in multiple formats and transforming a dynamically generated XML document.

Transforming a Dynamically Generated XML Document

The standard transformation approach I've been using through this chapter involves reading XML and XSLT data from static files, converting this data into a single string, and processing it all at once with xslt_process() . Most of the time, this process works well; however, as discussed previously, there is an alternative approach that makes it possible to use dynamically generated XML (that is, XML built using data stored in a database, rather than a static file) in the transformation.

In order to illustrate this alternative approach, consider Listing 4.10, an XML file.

Listing 4.10 An XML Bookmark List ( bookmarks.xml )
 <?xml version="1.0"?>  <bookmarks>        <category name="News">              <record>                    <name>CNN.com</name>                    <url>http://www.cnn.com/</url>              </record>              <record>                    <name>Slashdot</name>                    <url>http://www.slashdot.org/</url>              </record>        </category>        <category name="Shopping">              <record>                    <name>Amazon.com</name>                    <url>http://www.amazon.com/</url>              </record>        </category>        <category name="Technical Articles">              <record>                    <name>Melonfire</name>                    <url>http://www.melonfire.com/</url>              </record>        </category>  </bookmarks> 

Now, let's suppose that I want to generate this XML document dynamically from a database (in a manner similar to that used in Listing 3.15) and transform it into an HTML page using the stylesheet in Listing 4.11.

Listing 4.11 An XLST Stylesheet to Convert the Bookmark List into HTML ( bookmarks.xsl )
 <?xml version="1.0"?>  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">  <!-- set up the main page container -->  <xsl:template match="/">        <html>        <head>        </head>        <body>        <h3><font face="Arial">My Bookmarks</font></h3>        <xsl:apply-templates />        </body>        </html>  </xsl:template>  <!-- look for the category node -->  <xsl:template match="//category">        <font face="Arial"><b><xsl:value-of select="@name" /></b></font>        <!-- iterate through the record nodes under it -->        <ul>        <xsl:for-each select="record">              <!-- print each record as a list item with a label and hyperlink -->              <li>                    <a>                      <xsl:attribute name="href"><xsl:value-of select="url"/></xsl:attribute>                    <font face="Arial"><xsl:value-of select="name" /></font>                    </a>                    <p />              </li>        </xsl:for-each>        </ul>  </xsl:template>  </xsl:stylesheet> 

Listing 4.12 contains the PHP script to accomplish this.

Listing 4.12 Performing an XSLT Transformation on a Dynamically Generated XML Document with PHP
 <?php  $xslt_file = "bookmarks.xsl";  // create the XSLT processor  $xslt_processor = xslt_create();  // read in the data  $xslt_string = join("", file($xslt_file));  // create DomDocument object  $doc = new_xmldoc("1.0");  // add root node  $root = $doc->add_root("bookmarks");  // query database for records  $connection = mysql_connect("localhost", "us7584", "secret") or die ("Unable to graphics/ccc.gif connect!");  mysql_select_db("bm") or die ("Unable to select database!");  $query = "SELECT DISTINCT category FROM bookmarks";  $result = mysql_query($query) or die ("Error in query: $query. " . mysql_error());  // iterate through resultset  while(list($category) = mysql_fetch_row($result))  {      $c = $root->new_child("category", "");       $c->set_attribute("name", $category);       $query2 = "SELECT name, url FROM bookmarks WHERE category = '$category'";       $result2 = mysql_query($query2) or die ("Error in query: $query2. " .mysql_error());      while(list($name, $url) = mysql_fetch_row($result2))      {           $record = $c->new_child("record", "");            $record->new_child("name", $name);            $record->new_child("url", $url);      }  }  // close connection  mysql_close($connection);  // dump the tree as a string  $xml_string = $doc->dumpmem();  // set up buffers  $arg_buffer = array("/xml" => $xml_string, "/xslt" => $xslt_string);  // create the XSLT processor  $xp = xslt_create() or die("Could not create XSLT processor");  // process the two strings to get the desired output  if($result = xslt_process($xp, "arg:/xml", "arg:/xslt", NULL, $arg_buffer))  {       echo $result;  }  else  {       echo "An error occurred: " . xslt_error($xp) . "(error code " . xslt_errno($xp) . graphics/ccc.gif ")";  }  // free the resources occupied by the handler  xslt_free($xp);  ?> 

As you can see, I started off by reading the XSLT file into a string variable. (I'll use this string a little later.) Next , I proceeded to dynamically construct an XML document, in the format described in Listing 4.10, querying a MySQL database for records and inserting them into the XML source tree using the DOM functions discussed in Chapter 3, "PHP and the Document Object Model (DOM)." After the document was fully constructed , I dumped it with the dumpmem() function and created an associative array to hold both the XML and XSLT strings in named buffers.

Finally, I instantiated an XSLT processor and passed the named buffers to the xslt_process() function for processing. At this stage, the XSLT engine took over and transformed the XML data per the template rules in the stylesheet.

Figure 4.2 shows what the output looks like.

Figure 4.2. Transforming a dynamically generated XML document into a web page with XSLT.

graphics/04fig02.gif

Averaging and Tabulating Data with XSLT

In Chapter 3, I demonstrated how the DOM's XPath classes could be used to build a 2x2 table of experiment readings (refer to Listing 3.12). That listing used the DOM API to traverse the document tree; this one uses a stylesheet to demonstrate how much simpler the process is with XSLT.

Listing 4.13 is the XML document containing the sample readings.

Listing 4.13 A Compilation of Experiment Readings ( data.xml )
 <?xml version="1.0"?>  <project id="49">       <!-- data for 3 cultures: Alpha, Beta and Gamma, tested at temperatures ranging from graphics/ccc.gif 10C to 50C -->       <!-- readings indicate cell counts 4 hours after start of experiment -->       <record>            <culture>Alpha</culture>            <temperature>10</temperature>             reading>25000</reading>       </record>       <record>            <culture>Beta</culture>            <temperature>10</temperature>            <reading>4000</reading>       </record>       <record>            <culture>Alpha</culture>            <temperature>10</temperature>            <reading>23494</reading>       </record>       <record>            <culture>Alpha</culture>            <temperature>20</temperature>            <reading>21099</reading>       </record>       <record>            <culture>Gamma</culture>            <temperature>40</temperature>            <reading>768</reading>       </record>       <record>            <culture>Gamma</culture>            <temperature>10</temperature>            <reading>900</reading>       </record>       <!-- snip -->  </project> 

Listing 4.14 has the XSLT stylesheet I plan to use.

Listing 4.14 An XSLT Stylesheet to Group and Average Readings ( data.xsl )
 <?xml version="1.0"?>  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">  <!-- define a custom number format so that non-numbers are displayed as 0 -->  <xsl:decimal-format name="NaNFixFormat" NaN="0" zero-digit="0"/>  <!-- start -->  <xsl:template match="/">        <html>        <head>        </head>        <body>        <table border="1" cellspacing="5" cellpadding="5">        <!-- first row -->        <tr>        <td> </td>        <!-- this returns a list of unique culture names, which are printed in the first graphics/ccc.gif row -->                      <xsl:for-each select="//culture[not(.=preceding::culture)]">                    <td><xsl:value-of select="."/></td>                    </xsl:for-each>                    </tr>                    <!-- next, we need a list of available temperatures, printed as the graphics/ccc.gif first column (so put into a loop) -->                    <xsl:for-each select="//temperature[not(.=preceding::temperature)]">                    <tr>                    <td><xsl:value-of select="."/></td>                    <!-- assign the current temperature value to $t-->                    <xsl:variable name="t" select="." />                    <!-- iterate through the culture list for this temperature value -->                    <xsl:for-each select="//culture[not(.=preceding::culture)]">                    <!-- assign the current culture name to $c-->                    <xsl:variable name="c" select="." />                    <!-- average all readings corresponding to the intersection ($t, $c), graphics/ccc.gif format and display -->                    <td><xsl:value-of select="format-number(sum(//record[culture=$c and graphics/ccc.gif temperature=$t]/reading) div count(//record[culture=$c and temperature=$t]/reading), '0', graphics/ccc.gif 'NaNFixFormat')"/></td>                    </xsl:for-each>        </tr>        <!-- iterate to next row-->        </xsl:for-each>        </table>        </body>        </html>  </xsl:template>  </xsl:stylesheet> 

In case you're wondering, I used XPath expressions to obtain a list of unique culture names and temperature points from the XML document; these are then used to construct the first row and column of the grid. Next, I used standard XSLT sum() and count() expressions, in combination with a loop, to add and average all the readings belonging to a particular (culture, temperature) combination.

Combinations for which no readings exist are normally represented by the string NaN (Not a Number); I used the <xsl: decimal-format> instruction and the format-number() function to replace this unsightly acronym with a zero.

Version Control

Note that some of the XSLT functions used in Listing 4.14 are supported only in Sablotron 0.71 and higher. If you encounter errors while running this script, you should upgrade to the latest version of Sablotron.

Listing 4.15 transforms the XML document in Listing 4.13 using the XSLT stylesheet in Listing 4.14.

Listing 4.15 PHP Script to Perform XSL Transformation ( data.php )
 <?php  // set the filenames  $xml_file = "data.xml";  $xslt_file = "data.xsl";  // create the XSLT processor  $xp = xslt_create() or die("Could not create XSLT processor");  // define the error handler  xslt_set_error_handler($xp, "errHandler");  // process the two files to get the desired output  $result = xslt_process($xp, $xml_file, $xslt_file);  // print output  echo $result;  // free the resources occupied by the handler  xslt_free($xp);  // custom error handler  function errHandler($processor, $level, $ecode, $einfo)  {       echo "<html><head></head><body>Something bad just happened. Here's some more graphics/ccc.gif information: <br>";        // iterate through error array        while(list($key, $value) = each($einfo))        {             echo "$key --> $value <br>";        }  echo "</body></html>";  }  ?> 

Figure 4.3 shows the output.

Figure 4.3. Tabulating and averaging experiment readings with XSLT.

graphics/04fig03.gif

Using XSLT to Generate Output in Different Formats from a Single XML Source

Although XSLT is certainly exciting, there's an important caveat: Using PHP to perform XSLT can degrade performance on a web site quite substantially. So, it's a very bad idea to use this technique on a high-traffic web server. It's preferable, therefore, to use transformation simply as a one-time publishing mechanism to generate static HTML or XML files on the server, which can be parsed and returned to the client faster than code- intensive PHP scripts.Yes, you need to regenerate, or republish, the static documents each time your stylesheet or XML source changes, but the extra effort pays dividends in terms of better web server performance and faster response times.

With this in mind, my final example demonstrates an extremely primitive publishing system. I use a single XML document and multiple XSLT stylesheets to output data in three different formats: HTML, WML, and comma-separated ASCII text (CSV). Listing 4.16 is the sample XML document, which lists elements from the periodic table.

Listing 4.16 The Periodic Table in XML ( ptable.xml )
 <?xml version="1.0"?>  <ptable>        <element>              <name>Hydrogen</name>              <symbol>H</symbol>              <number>1</number>        </element>        <element>              <name>Lithium</name>              <symbol>Li</symbol>              <number>3</number>        </element>        <element>              <name>Sodium</name>              <symbol>Na</symbol>              <number>11</number>        </element>        <!-- snip -->  </ptable> 

Next, I need three different stylesheets, one for each format (see Listings 4.17, 4.18, and 4.19).

Listing 4.17 XSLT Stylesheet to Generate HTML Output ( ptable_html.xsl )
 <?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>        <h3>Periodic table of elements</h3>        <table border="1" cellspacing="5" cellpadding="5">        <tr>        <td align="center">Element name</td>        <td align="center">Symbol</td>        <td align="center">Atomic number</td>        </tr>        <xsl:apply-templates />        </table>        </body>          </html>  </xsl:template>  <xsl:template match="//element">        <tr>        <td align="center"><xsl:value-of select="name" /></td>        <td align="center"><xsl:value-of select="symbol" /></td>        <td align="center"><xsl:value-of select="number" /></td>        </tr>  </xsl:template>  </xsl:stylesheet> 
Listing 4.18 XSLT Stylesheet to Generate CSV Output ( ptable_csv.xsl )
 <?xml version="1.0"?>  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">  <xsl:output method="text" omit-xml-declaration="yes" />  <xsl:template match="//element">  <!-- separate the values with commas, put a carriage return at the end of the line -->  <xsl:value-of select="concat(name, ', ', symbol, ',', number, '&#xD;')" />  </xsl:template>  </xsl:stylesheet> 
Listing 4.19 XSLT Stylesheet to Generate WML Output ( ptable_wml.xsl )
 <?xml version="1.0"?>  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">  <xsl:template match="/">        <wml>        <card id="ptable" title="Periodic table">        <p align="center">        Periodic table        </p>        <xsl:apply-templates />        </card>        </wml>  </xsl:template>  <xsl:template match="//element">   * <xsl:value-of select="name" /> (<xsl:value-of select="symbol" />) - <xsl:value-of graphics/ccc.gif select="number" />   <br />  </xsl:template>  </xsl:stylesheet> 

Finally, I need a PHP script that accepts the file format as argument, picks up the correct stylesheet, and combines it with the XML document to generate appropriate output (see Listing 4.20).

Listing 4.20 Generating Output in Different Formats Using XSLT and PHP
 <?php  // alter this to see other formats  $format = "csv";  // set the filenames  $xml_file = "ptable.xml";  // pick the XSLT sheet based on the $format variable  $xslt_file = "ptable_" . $format . ".xsl";  // set the name for the output file  $out_file = "/www/ptable." . $format;  // create the XSLT processor  $xp = xslt_create() or die("Could not create XSLT processor");  // log messages  xslt_set_log($xp, "/tmp/xslt.log");  // define the error handler  xslt_set_error_handler($xp, "errHandler");  // process the files and write the output to $out_file  if(xslt_process($xp, $xml_file, $xslt_file, $out_file))  {       echo "Success!";  }  // free the resources occupied by the handler  xslt_free($xp);  // custom error handler  function errHandler($processor, $level, $ecode, $einfo)    {       echo "<html><head></head><body>Something bad just happened. Here's some more graphics/ccc.gif information: <br>";        // iterate through error array        while(list($key, $value) = each($einfo))        {             echo "$key --> $value <br>";        }  echo "</body></html>";  }  ?> 

Nothing too complicated here. The $format variable specifies the file format required, and may be passed to the script either on the command line or as a form variable. Depending on the value of this variable, the appropriate stylesheet is used to generate corresponding output. This output may be displayed or saved to disk for later use (which is what Listing 4.20 does).

In this case, I specified a filename as the fourth argument to xslt_process() ”this is the file to which the results of the transformation are written. If I'd omitted this file, the output would have been returned as a string.

I l @ ve RuBoard


XML and PHP
XML and PHP
ISBN: 0735712271
EAN: 2147483647
Year: 2002
Pages: 84

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net