XML and PHP [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

XML and PHP [Electronic resources] - نسخه متنی

Vikram Vaswani

نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
افزودن یادداشت
افزودن یادداشت جدید

A Few Examples

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 
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) . 
")"; 
} 
// 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 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.

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 
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 
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 
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), 
format and display --> 
<td><xsl:value-of
 select="format-number(sum(//record[culture=$c and 
temperature=$t]/reading) div count
(//record[culture=$c and temperature=$t]/reading), '0', 
'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 
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.

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 
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 
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.

/ 84