If you've been following past issues of this column, you're probably already aware of the numerous things you can do with XML and PHP. You also probably already know the basics - that there are two ways XML can be parsed, either via the Simple API for XML (SAX) or the Document Object Model (DOM); that each method has pros and cons, and that the selection of a parsing technique depends largely on the application; and that PHP today supports both parsing methods, making it a versatile and flexible tool for XML application development.
Now, by default, all newer versions of PHP come with the XML SAX parser enabled; however, the DOM module needs to be explicitly turned on at compile time. If you're working in a development environment that is entirely under your control, this is not a problem - all you need to do is log in as root, recompile PHP and get cracking.
However, if you're working in a shared environment - for example, on your Web hosting provider's server - then it's quite likely that you won't have super-user access to the system. And since Web hosting companies are
(understandably!) picky about compiling new software on their production servers, if your default PHP environment doesn't already have DOM support compiled in, it's unlikely that you're going to get it any time soon.
So, a creative solution is needed. Which is where this article comes in.
Over the next few pages, I'm going to be introducing you to a free PHP class named XMLTree, which allows you to manipulate XML document trees in a manner similar (though *not* completely identical) to that available in the standard PHP DOM extension...without requiring you to first recompile your PHP build. As you might imagine, this can come in handy in certain situations - for example, if you need a quick and dirty way to build an XML document tree from an external data source, like a MySQL database or a structured text file. So keep reading - you might find the rest of the show interesting!
A Hero Is Born
The XMLTree class comes courtesy of PEAR, the PHP Extension and Application Repository (http://pear.php.net). In case you didn't know, PEAR is an online repository of free PHP software, including classes and modules for everything from data archiving to XML parsing. When you install PHP, a whole bunch of PEAR modules get installed as well; the XMLTree class is one of them.
In case your PHP distribution didn't include XMLTree, you can get yourself a copy from the official PEAR Web site, at http://pear.php.net - simply unzip the distribution archive into your PEAR directory and you're ready to roll!
Let's begin with something simple - dynamically constructing an XML document using XMLTree methods. Here's the code:
<?php
// include class
include("Tree.php");
// instantiate object
$tree = new XML_Tree();
// add the root element
$root =& $tree->addRoot("superhero");
As you can see, the output of the script is a correctly-formatted, well-formed XML document - all created using PHP code!
Anatomy Class
Let's take a closer look at how I accomplished this.
1. The first step is, obviously, to include the XMLTree class
<?php
// include class
include("Tree.php");
?>
You can either provide an absolute path to this file, or do what most lazy programmers do - include the path to your PEAR installation in PHP's "include_path" variable, so that you can access any of the PEAR classes without needing to type in long, convoluted file paths.
2. Next, an object of the XMLTree class needs to be initialized, and assigned to a PHP variable.
<?php
// instantiate object
$tree = new XML_Tree();
?>
This variable serves as the control point for future tree manipulation.
3. Every XML document must have a root element - which is where the addRoot() method comes in.
<?php
// add the root element
$root =& $tree->addRoot("superhero");
?>
The addRoot() method is the starting point for your XML tree - it allows you to specify the name and content of your document's root element, and returns a Node object that can be used to graft further branches on to the document tree.
4. The Node object returned by the addRoot() method comes with methods of its own - and one of the more useful ones is the addChild() method, which allows you to add new children under that node. This method is fairly simple to use - all it needs is the name of the child element to be added, and the content and attributes (if any).
Each call to addChild() returns another Node object, which in turn exposes an addChild() method, thereby allowing you to add branches to the tree ad infinitum. In this case, I've stopped at a two-level tree - but feel free to go as deep as you like (the example on the next page demonstrates a more complex tree).
5. Once the tree is complete, you can do something useful with it - write it to a file, pass it through a SAX parser or - as I've done here - simply output it to the screen for all to admire.
<?php
// print tree
$tree->dump();
?>
The dump() method prints the entire XML document tree as is, and serves a very useful purpose in debugging long or complex trees - I'll be using it fairly frequently in the examples in this chapter.
A La Carte
Here's another example, this one building a slightly more complicated document tree,
<?php
// include class
include("Tree.php");
// instantiate object
$tree = new XML_Tree();
// add the root element
$root =& $tree->addRoot("recipe");
// create array of steps to execute recipe $stepsArray = array(
"Cut chicken into cubes, wash and apply lime juice and salt",
"Add ginger, garlic, chili and lime juice in a separate bowl",
"Mix well, and add chicken to marinate for 3-4 hours",
"Place chicken pieces on skewers and barbeque",
"Remove, apply butter, and barbeque again until meat is tender",
"Garnish with lemon and chopped onions"
);
// add ingredient list to tree
foreach ($ingredientsArray as $i)
{
$item = $ingredients->addChild("item", $i); }
// add execution steps to tree
foreach ($stepsArray as $s)
{
$step = $process->addChild("step", $s); }
<process>
<step>Cut chicken into cubes, wash and apply lime juice and salt</step>
<step>Add ginger, garlic, chili and lime juice in a separate bowl</step>
<step>Mix well, and add chicken to marinate for 3-4 hours</step>
<step>Place chicken pieces on skewers and barbeque</step>
<step>Remove, apply butter, and barbeque again until meat is tender</step>
<step>Garnish with lemon and chopped onions</step>
</process>
</recipe>
Slice And Dice
You can retrieve any segment of a dynamically-created document tree via the get() method, as in the following variant of the previous example:
<?php
// include class
include("Tree.php");
// instantiate object
$tree = new XML_Tree();
// add the root element
$root =& $tree->addRoot("recipe");
// create array of steps to execute recipe $stepsArray = array(
"Cut chicken into cubes, wash and apply lime juice and salt",
"Add ginger, garlic, chili and lime juice in a separate bowl",
"Mix well, and add chicken to marinate for 3-4 hours",
"Place chicken pieces on skewers and barbeque",
"Remove, apply butter, and barbeque again until meat is tender",
"Garnish with lemon and chopped onions"
);
// add ingredient list to tree
foreach ($ingredientsArray as $i)
{
$item = $ingredients->addChild("item", $i);
}
// add execution steps to tree
foreach ($stepsArray as $s)
{
$step = $process->addChild("step", $s); }
// print ingredients only
echo $ingredients->get();
?>
In this case, the output of the script is the <ingredients> node only:
You've already seen that you can also add elements to an existing tree via the addChild() method. But XMLTree also allows you to prune an existing document tree, deleting elements from the hierarchy with the removeChild() method.
In order to illustrate this, consider the following example, which dynamically constructs a simple document tree:
<?php
// include class
include("Tree.php");
// instantiate object
$tree = new XML_Tree();
// add the root element
$root =& $tree->addRoot("superhero");
// remove second child node (age)
$root->removeChild(1);
// print tree
$tree->dump();
?>
The removeChild() method takes, as argument, the index number of the child to be removed under the specified Node object (remember that node numbering begins at 0, not 1). In this case, since I want to remove the <age> element, which is the second child of the root element, I can use
In addition to dynamically creating an XML document tree, XMLTree also allows you to read an existing tree into memory, via its getTreeFromFile() method. Consider the following XML file, named "me.xml":
In this case, the getTreeFromFile() method has been used to read the contents of the XML file specified in the object constructor, and convert it into an XMLTree object. The tree can then be viewed via a call to dump().
Take a quick peek at the object created by getTreeFromFile() with the print_r() function, and here's what you'll see:
// read string into tree
$root =& $tree->getTreeFromString($str);
// print tree
$tree->dump();
?>
Spider, Spider On The Wall...
XMLTree doesn't just restrict you to XML elements - you can easily add attributes to any element as well, simply by specifying them as an associative array of name-value pairs in your calls to the addRoot() or
addChild() methods. Here's an example,
<?php
// include class
include("Tree.php");
// instantiate object
$tree = new XML_Tree();
// add the root element
$root =& $tree->addRoot("superhero", NULL, array("owner" => "Marvel Comics"));
Now, while all this is fine and dandy, how about using all this new-found knowledge for something practical?
This next example does just that, demonstrating how the XMLTree class can be used to convert data stored in a MySQL database into an XML document, and write it to a file for later use. Here's the MySQL table I'll be using,
+----+------------------+-----------------------------------+-----------
+----+------------------+-----------------------------------+----
--+----------------+
| id | name | address | tel
|fax |
+----+------------------+-----------------------------------+-----------
+----+------------------+-----------------------------------+----
--+----------------+
| 1 | Sam Spade | 134, Dark Road, New York, NY |
1-800-TOUGH-GUY |1-234-587-3636 |
| 2 | The White Rabbit | Down The Rabbit Hole, Wonderland | 56-78-5467
|56-78-2537 |
| 3 | Jack Liliput | The Little House, Lilliput Island | None
|None |
+----+------------------+-----------------------------------+-----------
+----+------------------+-----------------------------------+----
--+----------------+
and here's what I want my target XML document to look like:
<?xml version="1.0"?>
<addressbook>
<item id="1">
<name>Sam Spade</name>
<address>134, Dark Road, New York, NY</address>
<tel>1-800-TOUGH-GUY</tel>
<fax>1-234-587-3636</fax>
</item>
<item id="2">
<name>The White Rabbit</name>
<address>Down The Rabbit Hole, Wonderland</address>
<tel>56-78-5467</tel>
<fax>56-78-2537</fax>
</item>
// close database connection
mysql_close($connection);
// open file
if (!$handle = fopen($filename, 'w'))
{
print "Cannot open file ($filename)";
exit;
}
// write XML to file
if (!fwrite($handle, $tree->get()))
{
print "Cannot write to file ($filename)";
exit;
}
// close file
fclose($handle);
?>
Pretty simple, once you know how it works. First, I've opened up a connection to the database and retrieved all the records from the table.
Then I've instantiated a new document tree and iterated over the result set, adding a new set of nodes to the tree at each iteration. Finally, once all the rows have been processed, the dynamically generated tree is written to a file for later use.
Doing The Chameleon
Why stop there? It's also possible to pair the dynamically-built XML document in the previous example with an XSLT stylesheet to produce HTML output. Here's the stylesheet,
and here's a variant of the previous example which, instead of writing the XML to a file, passes it through PHP's XSLT processor to produce an HTML page containing the data from the MySQL database.
<?php
// include class
include("Tree.php");
// set XSLT file
$xslt_file = "addressbook.xsl";
// instantiate object
$tree = new XML_Tree();
// add the root element
$root =& $tree->addRoot("addressbook");
// open connection to database
$connection = mysql_connect("localhost", "joe", "secret") or die ("Unable to connect!");
// select database
mysql_select_db("db567") or die ("Unable to select database!");
// execute query
$query = "SELECT * FROM addressbook";
$result = mysql_query($query) or die ("Error in query: $query. " .
mysql_error());
// iterate through rows and print column data while ($row = mysql_fetch_array($result)) {
// close database connection
mysql_close($connection);
// read XML into string
$xml = $tree->get();
// create the XSLT processor
$xslt_processor = xslt_create();
// read in the data
$xslt = join("", file($xslt_file));
// set up buffers
$arg_buffer = array("/xml" => $xml, "/xslt" => $xslt);
// 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);
?>
Here's what the output looks like:
Simple when you know how!
Linking Out
And that's about it for this article. Over the last few pages, I showed you how you to build an XML document tree even if your PHP build doesn't support the XML DOM, via the free add-on XMLTree class from PEAR. I showed you how to programmatically create an XML document, how to construct root and child nodes and attributes, how to remove nodes from an existing document tree, and how to convert your existing XML files into XMLTree-compliant format Finally, I wrapped things up with a composite example that demonstrated a practical, real-world use for all this code - converting the data in a MySQL database into XML, and transforming it for use on a Web site with PHP and XSLT.
All this is, of course, only the tip of the iceberg - there are an infinite number of possibilities with power like this at your disposal. To find out what else you can do with XML and PHP, I'd encourage you to visit the following links:
Note: Examples are illustrative only, and are not meant for a production environment. Melonfire provides no warranties or support for the source code described in this article. YMMV!
This article copyright Melonfire,2005. All rights reserved.