I l @ ve RuBoard |
patTemplate is a rendering engine that attempts to simplify web application development by using a template-based framework to separate presentation information from data. It does this by using simple templates to hold presentation semantics ”formatting, layout, alignment, and so on ”and inserting placeholders in these templates to represent data elements. When these templates are rendered by the engine, the placeholders embedded within them are replaced by actual data, which may be dynamically retrieved from a database, a text file, or any other data source. patXMLRenderer builds on the functionality offered by patTemplate, adding an XML parser to the mix. This allows data encoded in XML to be retrieved and used in a template, with the XML parser automatically creating appropriate variables and handing them off to the template engine for inclusion in the final output. Together, patTemplate and patXMLRenderer provide a simple, efficient solution to the problem of generating HTML (or other ASCII) documents from XML-encoded data. Part of a larger suite of PHP-based application tools, they are developed and maintained by Stephan Schmidt, and can be downloaded free of charge from http://www.php-tools.de/. Design GoalsThe patXMLRenderer publishing system was designed around the following goals:
It accomplishes these goals using a combination of PHP and XML programming techniques. Application ComponentsAssuming an XML data source, a patXMLRenderer publishing system consists of two primary components:
Usage ExamplesAs before, let's look at a couple of simple examples before diving into the code that drives the application. Consider Listing 9.11, which contains a simple template. Listing 9.11 A Template for a Simple HTML Document ( superhero.tmpl )<!-- this template sets up the main page --> <patTemplate:tmpl name="superhero"> <html> <head></head> <body> <font face="Arial" size="+1">My friends call me {NICKNAME}, although my real name is {REALNAME}. Why don't we get together for dinner sometime?</font> </body> </html> </patTemplate:tmpl> Each template is enclosed within a pair of <patTemplate:tmpl>...</patTemplate:tmpl> elements and has a unique name (in this case, "superhero" ). As you'll see in Listing 9.12, this name attribute is used when assigning values to different template variables. The uppercase strings you see enclosed within curly braces within the template definition ( REALNAME , NICKNAME ) are template variables; they serve as placeholders for actual data and will be replaced during the rendering phase. Listing 9.12 demonstrates the PHP script that initializes the template engine, assigns values to the template variables, and puts the two together to create a composite result. Listing 9.12 Assigning Data to Template Variables and Displaying the Composite Result<?php // include template engine include("include/patTemplate.php"); // initialize template engine $template = new patTemplate(); // set base path for templates $template->SetBaseDir("templates"); // read template data $template->ReadTemplatesFromFile("superhero.tmpl"); // define values for template variables $template->AddVar("superhero", "NICKNAME", "The Incredible Hulk"); $template->AddVar("superhero", "REALNAME", "Bruce Banner"); // display output $template->DisplayParsedTemplate(); ?> Listing 9.13 contains the resulting HTML document, with Figure 9.3 demonstrating what it looks like in a browser. Figure 9.3. The HTML document generated by Listing 9.12, as it appears in a Web browser.
Listing 9.13 The HTML Document Generated by Listing 9.12<html> <head></head> <body> <font face="Arial" size="+1">My friends call me The Incredible Hulk, although my real name is Bruce Banner. Why don't we get together for dinner sometime?</font> </body> </html>
Let's see what happens when XML is added to the mix. Consider Listing 9.14, an XML shopping list. Listing 9.14 An XML Shopping List ( shoppinglist.xml )<?xml version="1.0"?> <shoppinglist> <item> <name>eye of newt</name> <quantity>3</quantity> </item> <item> <name>tongue of lizard</name> <quantity>2</quantity> </item> <item> <name>scouring powder</name> <quantity>7 boxes</quantity> </item> <item> <name>cauldron</name> <quantity>1</quantity> </item> </shoppinglist> Now, let's convert this XML-encoded list into a web page. The first step is to create a series of templates (compliant to the patTemplate format described earlier) to define the formatting and layout of this web page. Listing 9.15 contains the templates I'd like to use. Listing 9.15 A Series of Templates for an HTML Shopping List<!-- this template sets up the main container --> <patTemplate:tmpl name="shoppinglist"> <html> <head></head> <body> <h2>My Shopping List</h2> <ul> {CONTENT} </ul> </body> </html> </patTemplate:tmpl> <!-- this sets up the list element for each <item> element --> <patTemplate:tmpl name="item"> <li>{CONTENT}</li> </patTemplate:tmpl> <!-- this prints the content of each <name> element within an <item> element --> <patTemplate:tmpl name="name"> {CONTENT} </patTemplate:tmpl> <!-- this prints the content of each <quantity> element within an <item> element, in italics and parentheses --> <patTemplate:tmpl name="quantity"> <i>({CONTENT})</i> </patTemplate:tmpl> As you can see, the desired output is fairly simple ”an HTML document with the items in the shopping list displayed as elements of a bulleted list. In Listing 9.15, this simple HTML document is broken up into individual pieces, with each piece represented as a separate template. The CONTENT marker that appears within each template is a special placeholder variable that is used by patXMLRenderer when it generates the final output. When a template is parsed by the rendering engine, the CONTENT placeholder is automatically replaced with the contents of the corresponding XML element (this may be either character data or other nested XML elements).
Finally, Listing 9.18 has the PHP script that initializes both the template engine and the XML parser, and generates the required output. Listing 9.18 Merging Templates with XML Data and Displaying the Composite Result<?php // include config files include("config/conf.php"); // include template and rendering engines include("include/patTemplate.php"); include("include/patXMLRenderer.php"); // initialize template engine $template = new patTemplate(); // set base path for templates $template->SetBaseDir("templates"); // initialize rendering engine $randy = new patXMLRenderer(); // set base path for XML data $randy->setXMLDir("xml"); // set data file $randy->setXMLFile("shoppinglist.xml"); // connect template to rendering engine $randy->setTemplate($template); // set template file $randy->addTemplateFile("shoppinglist.tmpl"); // load templates, XML $randy->initRenderer(); // parse XML file, merge data into template and display output $randy->displayRenderedContent(); ?> In this case, because the data will be coming directly from an XML file, there's no real need to manually define values for template variables with the addVar() method. Instead, when the patXMLRenderer engine is initialized , it parses the XML document, matches XML elements to templates (on the basis of the name attribute in the template definition), and replaces the placeholders in the template with data from the XML document. If you flip back to Listing 9.14, you'll see the correspondence between the XML elements used there and the name attributes used in Listing 9.15. Listing 9.19 contains the resulting HTML code, and Figure 9.4 demonstrates what it looks like in a web browser. Figure 9.4. The HTML document generated by Listing 9.18, as it appears in a web browser.
Listing 9.19 The HTML Document Generated by Listing 9.18<!-- output reindented for greater readability --> <html> <head></head> <body> <h2>My Shopping List</h2> <ul> <li>eye of newt<i>(3)</i></li> <li>tongue of lizard<i>(2)</i></li> <li>scouring powder<i>(7 boxes)</i></li> <li>cauldron<i>(1)</i></li> </ul> </body> </html> Though these are simple examples, they clearly demonstrate the potential of this system on a content-heavy web site. By separating the presentation of the data from the data itself, this system makes it possible to decouple the graphical interface of a web site from the data that appears within that interface, so web designers and content editors can (finally) work independently of each other.
Implementation OverviewAs stated previously, the system consists of two components, which are implemented as PHP classes:
If you look at the source code for the patTemplate class, you'll see that it's fairly simple. It first reads and parses the available template(s), and then creates PHP arrays to hold the template name(s) and data. Listing 9.21 demonstrates a snippet from the readTempatesFromFile() method of this class. Listing 9.21 A Function to Read and Parse Templates (from patTemplate.php )<?php // some parts of this function // have been deleted for greater readability Function readTemplatesFromFile($file) { // Tag depth $this->depth = -1; // Names, extracted from the Tags $this->template_names = array(); // All HTML code, that is found between the tags $this->template_data = array(); // Attributes, extracted from tags $this->template_types = array(); $this->last_opened = array(); $this->last_keep = array(); $this->whitespace = array(); $this->createParser($file); $open_tag = array_pop($this->last_opened); if($open_tag != NULL) die ("Error in template '".$file."': </".$open_tag."> still open at end of file."); } ?> In case you're wondering, the createParser() method of the class is used to actually parse the template file and obtain information on the templates within it. After all the templates have been parsed, the next step is to obtain a list of template variables and values (remember that these variables must be manually defined by the application using the AddVar() method, as was done in Listing 9.12), and replace the variable placeholders in the template with their corresponding values. This is accomplished via the parseTemplate() method, which internally calls the parseStandardTemplate() method (see Listing 9.22). Listing 9.22 A Function to Replace Variables in a Template with Their Actual Values (from patTemplate.php )<?php // some parts of this function // have been deleted for greater readability function parseStandardTemplate($name, $mode="w") { $name = strtoupper($name); // get a copy of the plain content $temp = $this->getTemplateContent($name); $vars = $this->getVars($name); $vars[$this->tag_start."PAT_ROW_VAR".$this->tag_end] = 1; while(list($tag, $value) = each($vars)) { if(is_array($value)) { $value = $value[0]; } $temp = str_replace($tag, $value, $temp); } // snip } ?> After variable interpolation has taken place (see the call to str_replace() in Listing 9.22), all that's left is to display the resulting output. This is taken care of by a little method called DisplayParsedTemplate() , which merely echoes the result to the output device. So long as your activities are restricted only to patTemplate, the process described in the preceding paragraphs holds good. However, if you have a large amount of data, manually defining the values for a large number of template variables can get tedious . In such situations, it's easier to mark up your data in XML and let patXMLRenderer automatically import this data into your template(s). A close look at the source code for the patXMLRenderer class reveals that most of the activity begins with the initRenderer() method, which sets the name of the XML file to parse, and also creates some global variables for use in the patTemplate class. Listing 9.23 has a snippet from this method. Listing 9.23 A Function to Initialize the XML Parser and Set Some Global Template Variables (from patXMLRenderer.php )<?php // some parts of this function // have been deleted for greater readability function initRenderer() { if(!$this->xmlFile) $this->setXMLFile($this->defaultFile); // add some global variables $file = ($this->xmlDir!="") ? $this->xmlDir."/".$this->xmlFile : $this->xmlFile; // snip // add global variables $this->template->addGlobalVar("XMLFILE", $this->xmlFile); $this->template->addGlobalVar("XMLSOURCE", $file); $this->template->addGlobalVar("PAGE_LAST_UPDATED", date( "Y-m-d H:i:s", filemtime($file))); // snip $this->xmlSource = $file; } ?> Next, the getRenderedContent() method starts the process of parsing the XML file via the parseXMLFile() method. The parseXMLFile() method creates a parser via the createParser() method and processes it in chunks (see Listing 9.24). Listing 9.24 The Functions to Create an XML Parser and Parse the XML Document (from patXMLRenderer.php )<?php // some parts of this function // have been deleted for greater readability function parseXMLFile($file) { $file = ($this->xmlDir!="") ? $this->xmlDir."/".$file : $file; // snip $parser = $this->createParser(); if(!( $fp = fopen( $file, "r" ))) die("patXMLRenderer could not open XML file :".$file ); flock($fp, LOCK_SH ); $counter = 0; while($data = fread($fp, filesize($file))) { if (!xml_parse($parser, $data, feof($fp))) { die(sprintf("XML error: %s at line %d in file %s", xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser), $file)); } } xml_parser_free($parser); $data = $this->finalData[$this->parsers]; flock($fp, LOCK_UN); // snip return $data; } function createParser() { // snip // init XML Parser $parser = xml_parser_create(); xml_set_object($parser, &$this); xml_set_element_handler($parser, "startElement", "endElement"); xml_set_character_data_handler($parser, "characterData"); xml_set_external_entity_ref_handler($parser, "externalEntity"); xml_set_processing_instruction_handler($parser, "processingInstruction"); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); return $parser; } ?> As the parser steps through the document, callback functions are called to handle the different structures found ”you may remember this from Chapter 2, "PHP and the Simple API for XML (SAX)." These callbacks ” specifically the end element handler endElement() ”take care of creating variable-value pairs from the character data in the XML document (see Listing 9.25 for a snippet from this function). Listing 9.25 A Function to Assign Attribute Values and Character Data from the XML Document to Template Variables (from patXMLRenderer.php )<?php function endElement($parser, $name) { // snip if($this->getOption( "transform" ) == "on") { if( $this->getOption("replaceentities") == "on" && is_array($attributes)) { reset($attributes); while(list($key, $value) = each($attributes)) $attributes[$key] = htmlspecialchars($value); } $this->template->addVars($tag, $attributes); $this->template->addVar($tag, "PAT_TAG_NAME", $tag); $this->template->addVar($tag, "PAT_TAG_REP", $tagcounter); $this->template->addVar($tag, "CONTENT", $data); unset( $this->tagCounter[$this->parsers][($tagDepth + 1)]); if($this->template->exists($tag)) { $data = $this->template->getParsedTemplate($tag); $this->template->clearTemplate($tag); } } } ?> After all the template variables have been defined, the patTemplate class' getParsedTemplate() method is called to perform the variable interpolation, and the output is returned to the browser. Isn't it simple when you know how? Concluding RemarkspatTemplate and patXMLRenderer demonstrate how the combination of an XML parser (which parses an XML document and retrieves data from it) with a PHP-based template engine (which separates content from presentation by means of placeholder variables) can substantially streamline the development process of a web application. This separation of presentation semantics from raw data not only increases efficiency by allowing a software development team to work independently of an interface design team, it also makes application code easier to maintain, update, and test. And no matter which way you look at it, this adds up to a Good Thing. |
I l @ ve RuBoard |