patTemplate and patXMLRenderer

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 Goals

The patXMLRenderer publishing system was designed around the following goals:

  • Separate content from its presentation

  • Allow for the easy transformation of raw data into any other format

  • Provide an extensible framework for the development of new application modules

It accomplishes these goals using a combination of PHP and XML programming techniques.

Application Components

Assuming an XML data source, a patXMLRenderer publishing system consists of two primary components:

  • A template engine (patTemplate) that handles the creation and management of document templates and performs variable interpolation

  • An XML parser (patXMLRenderer) that parses one or more XML documents, retrieves data from them, and merges this raw data with the templates to produce a composite result.

Usage Examples

As 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 graphics/ccc.gif {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.

graphics/09fig03.gif

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 graphics/ccc.gif name is Bruce Banner. Why don't we get together for dinner sometime?</font>  </body>  </html> 

Checking Out the Competition

If you're interested in developing template-driven web sites, you might want to also look at the FastTemplate and Smarty template engines, which share some semantic similarities with the patTemplate class. Both are available on the web at http://www.thewebmasters.net/ and http:// freshmeat .net/projects/smarty/, respectively.

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 graphics/ccc.gif 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).

Those Amazing Acrobatic Attributes!

The CONTENT placeholder variable is not the only one recognized by patXMLRenderer. The class also allows you to import the values of XML attributes into the template, simply by using template variables corresponding to the attribute names .

Consider Listing 9.16, which has an XML document containing elements and element attributes.

Listing 9.16 A Simple XML Document ( size.xml )
 <?xml version="1.0"?>  <size units="inches">        <height>20</height>        <width>40</width>  </size> 

When patXMLRenderer reads and combines this XML document with the template in Listing 9.17, the attribute values are automatically imported into the template as template variables and used to replace the template placeholders.

Listing 9.17 A Template that Uses XML Attributes as Template Variables
 <!-- this template sets up the main container -->  <patTemplate:tmpl name="size">  <html>  <head></head>  <body>  Dimensions: {CONTENT} {UNITS}  </body>  </html>  </patTemplate:tmpl>  <!-- template for printing height -->  <patTemplate:tmpl name="height">  {CONTENT} x  </patTemplate:tmpl>  <!-- template for printing width -->  <patTemplate:tmpl name="width">  {CONTENT}  </patTemplate:tmpl>  <!-- output is "Dimensions: 20 x 40 inches" --> 

This capability to automatically convert XML attributes into template variables is unique to patXMLRenderer, and is one of the class' most useful features.

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.

graphics/09fig04.gif

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.

Extending Yourself

One of the nice things about patXMLRenderer is its capability to incorporate and use new extensions (remember the third design goal?), thereby allowing developers to add custom functionality to the class.

Each extension has its own namespace; the XML parser uses this unique namespace to store extension-specific variables and execute extension-specific methods .

As an example, consider Listing 9.20, which illustrates usage of a custom time extension. This extension provides a bunch of useful date and time formatting functions for use in a template.

Listing 9.20 Using patXMLRenderer's Time Extension
 <?xml version="1.0"?>  <doc>        <!-- include time extension -->        <randy:addextension namespace="time"  file="time/patXMLRendererTimeExtension.php"  extension="patXMLRendererTimeExtension"/>        <modified>This page was last modified on <time:format graphics/ccc.gif format="D d M Y">2002-06-04</time:format></modified>  </doc>  <!-- outputs "This page was last modified on Tue 04 Jun 2002" graphics/ccc.gif --> 

This capability to add custom extensions to the class is unique to patXMLRenderer, and it is what makes the class different from (and more powerful than) your average XSLT processor. Extensibility makes it a snap to add new functionality to the class on an as-needed basis, while still retaining the feature set and scalability of the base application.

A number of extensions for patXMLRenderer are available on the official web site for the project. Check them out at http://www.php-tools.de/

Implementation Overview

As stated previously, the system consists of two components, which are implemented as PHP classes:

  • The patTemplate class, which handles the creation and management of document templates and performs variable interpolation

  • The patXMLRenderer class, which takes care of parsing XML documents, converting attribute values and character data into template variables, and displaying the rendered content

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 graphics/ccc.gif 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", graphics/ccc.gif 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", graphics/ccc.gif xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser), graphics/ccc.gif $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 Remarks

patTemplate 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


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