Injecting XML Content Into Page Templates With patXMLRenderer (part 1)

Create a modular Web site with templates, XML and Randy.

Three Degrees Of Separation

If you're a good Web developer, then experience has probably already taught you it's not a great idea to mix HTML markup and PHP function calls when building a PHP-based Web site. It's not wrong to do so, of course - in fact, most examples in the PHP manual adopt this approach - but from the maintenance point of view, the mix of markup and code makes scripts hard to decipher (and even harder to modify).

This problem most commonly rears its ugly head when interface designers need to alter the user interface presented to Web site visitors - since the presentation information is entwined with PHP code, changes to it typically require handholding by a developer with sufficient expertise in the language. Which ultimately means more people, more time and more money.

The alternative approach (and one which most professional developers eventually gravitate to) involves using page templates to hold the presentation information and using a template engine to interpolate variable values or business logic into the templates. This separation between code and markup makes it possible for developers to modify business logic independent of the page design, and designers to alter page layouts without worrying about the effect on the code.

With the advent of XML into the enterprise, things become even more streamlined. With a little bit of thought and creativity, it now becomes possible to keep presentation information in a template, content in an XML container, and business logic in a PHP script, and use a rendering engine to combine all three into a single entity. This third layer of separation means that the content of a Web application can be updated independent of the design, which can be updated independent of the business logic...all of which significantly simplifies maintainability and ease of update.

How do you achieve this Zen-like state of simplicity and purity? With patTemplate and patXMLRenderer, of course!

Hard Sell

patTemplate is a PHP-based template engine designed, in the author's words, to "help you separate program logic or content from layout". Developed by Stephan Schmidt, it is freely available for download and is packaged as a single PHP class which can be easily included in your application.

patTemplate assumes that a single Web application is made up of many smaller pieces - it calls these parts "templates" - and it provides an API to link templates together, and to fill them with data. A "template" is a text file containing both static elements (HTML code, ASCII text et al) and patTemplate variables. When patTemplate parses a template file, it replaces the variables within it with their values (a process referred to as "variable interpolation").

When using patTemplate, these values for template variables are usually defined by the developer at run-time, and may be either local to a particular template, or global across all the templates within an application. If you would instead prefer to produce your content in XML format and have it magically (and automatically) integrated into your template(s), then you need to also become familiar with patXMLRenderer, a rendering engine also developed by Stephan Schmidt which "transforms XML into HTML pages (or any other ASCII Format you like) using the patTemplate class".

patXMLRenderer parses one or more XML files or data streams, matches the data in the XML file to the variables in the template(s), and automatically performs variable interpolation. Very simply, patXMLRenderer works by reading the elements in an XML file and then placing the corresponding content into templates which share the same name. For commonly-accessed files, the rendering engine also includes a caching feature to speed up access, and a logger to track activity and generate access statistics.

Over the course of this article, I'll be showing you how to use both patTemplate and patXMLRenderer in conjunction with each other, to automatically populate a template with content from an XML data source. I'll assume here that you have a working Apache+PHP installation, and that you have downloaded and installed the latest versions of both patTemplate and patXMLRenderer from their home page at http://www.php-tools.de/

If you have prior knowledge of patTemplate (or any other template engine) it will help you grasp the following material faster...but if you don't, don't worry, because the next page contains a crash course on everything you need to know. Let's get going!

You've Got Mail

Let's begin with a simple example that demonstrates how a template engine works. Consider the following template:

<patTemplate:tmpl name="email">
<html>
<head></head>
<body>
<pre>
EMAIL MESSAGE

From: {FROM}
To: {TO}
Subject: {SUBJECT}

{BODY}
</pre>
</body>
</html>
</patTemplate:tmpl>

The block above represents a single template, identified by the opening and closing <patTemplate:tmpl> tags, and by a unique name ("email", in this case).

Within the opening and closing tags comes the actual template body; to all intents and purposes, this is a regular HTML document, except that the markup also contains some special patTemplate variables, written in uppercase and enclosed within curly braces. These special variables will be replaced with actual values by the template engine.

Digging Deeper

Next, it's time to initialize the template engine and have it populate the template created on the previous page with actual data. Here's how:

<?php

// include the class
include("include/patTemplate.php");

// initialize an object of the class
$template = new patTemplate();

// set template location
$template->setBasedir("templates/");

// set name of template file
$template->readTemplatesFromFile("email.tmpl");

// set values for template variables
$template->addVar("email", "FROM", "some@where.com");
$template->addVar("email", "TO", "admin@some.domain.com");
$template->addVar("email", "SUBJECT", "Wedding Invitation");
$template->addVar("email", "BODY", "You are cordially invited to the wedding of John Doe with Jane Noe on 27 March 2005 at Buckley Court, Mumbai 400 056. Wedding ceremony at 3 PM. Cocktails at 9 PM.");

// parse and display the template
$template->DisplayParsedTemplate("email");
?>

What does all this mean? Well, the first step is to include all the relevant files for the template engine and then create an object of the patTemplate class.

<?php
// include the class
include("patTemplate.php");

// initialize an object of the class
$template = new patTemplate();
?>

Next, the object's setBaseDir() and readTemplatesFromFile() methods are used to point the engine in the direction of the templates to be read. The setBaseDir() method sets the default location for all template files; the readTemplatesFromFile() method specifies which template file to process.

<?php
// set template location
$template->setBasedir("templates/");

// set name of template file
$template->readTemplatesFromFile("email.tmpl");
?>

Now, values need to be assigned to the template variables prior to displaying the page. This is accomplished via the addVar() object method, which attaches values to template variables.

<?php
// set values for template variables
$template->addVar("email", "FROM", "some@where.com");
$template->addVar("email", "TO", "admin@some.domain.com");
?>

The final step is to actually display the interpolated template:

<?php
// parse and display the template
$template->DisplayParsedTemplate("message");
?>

The DisplayParsedTemplate() object method parses the specified template, replacing all variables within it with their specified values, and outputs it.

Here's what the resulting output looks like:

EMAIL MESSAGE

From: some@where.com
To: admin@some.domain.com
Subject: Wedding Invitation

You are cordially invited to the wedding of John Doe with Jane Noe on 27 March 2005 at Buckley Court, Mumbai 400 056. Wedding ceremony at 3 PM. Cocktails at 9 PM.

If you peek into the source code for the rendered page, you'll see something like this:

<html>
<head></head>
<body>
<pre>
EMAIL MESSAGE

From: some@where.com
To: admin@some.domain.com
Subject: Wedding Invitation

You are cordially invited to the wedding of John Doe with Jane Noe on 27 March 2005 at Buckley Court, Mumbai 400 056. Wedding ceremony at 3 PM. Cocktails at 9 PM.
</pre>
</body>
</html>

Thus, patTemplate has automatically replaced the variables in the template with the values provided in the PHP script.

The nice thing about this approach? The page interface and page elements are separated from the program code that actually makes the page function - and can therefore be updated independently by Web designers and PHP developers.

X Marks The Spot

So now you've seen how to create a template and populate it by manually setting values for the various template variables. But what about automatically populating it with data from an XML file?

Well, that's where you need to plug in patXMLRenderer aka Randy. To understand this better, consider the following XML file, which contains the data used in the previous example:

<?xml version="1.0"?>
<email>
<from>some@where.com</from>
<to>admin@some.domain.com</to>
<subject>Wedding Invitation</subject>
<body>You are cordially invited to the wedding of John Doe with Jane Noe on 27 March 2005 at Buckley Court, Mumbai 400 056. Wedding ceremony at 3 PM. Cocktails at 9 PM.</body>
</email>

Now, when patXMLRenderer reads a file like this, it maps the element names to the names of the templates it finds in the template file, and automatically populates each template with the character data within the element. This necessitates a change to the structure of the template - here's how:

<!-- main page template -->
<patTemplate:tmpl name="email">
<html>
<head></head>
<body>
<pre>
EMAIL MESSAGE

{CONTENT}
</pre>
</body>
</html>
</patTemplate:tmpl>

<!-- from header -->
<patTemplate:tmpl name="from">
From: {CONTENT}
</patTemplate:tmpl>

<!-- to header -->
<patTemplate:tmpl name="to">
To: {CONTENT}
</patTemplate:tmpl>

<!-- subject header -->
<patTemplate:tmpl name="subject">
Subject: {CONTENT}
</patTemplate:tmpl>

<!-- body of email message -->
<patTemplate:tmpl name="body">
{CONTENT}
</patTemplate:tmpl>

As you can see, the template names here match the element names in the XML file. Note the special {CONTENT} template variable - when patXMLRenderer sees this variable, it automatically replaces it with the character data from the corresponding XML element.

Ties That Bind

All that's left to do now is write the PHP glue that binds the template engine and the XML rendering engine together. Here's the code:

<?php

// include config file
require_once("config/patXMLRenderer.php");

// include patTemplate class
require_once("include/patTemplate.php");

// include patXMLRenderer clas
require_once("include/patXMLRenderer.php");

// initialize XML rendering class
$randy = new patXMLRenderer;

// set base directory for XML content
$randy->setXMLDir("xml/");

// set XML file to use
$randy->setXMLFile("email.xml");

// initialize template engine
$template = new patTemplate();

// set base directory for templates
$template->setBaseDir("templates/");

// connect template object to renderer object
$randy->setTemplate($template);

// set template file to use
$randy->addTemplateFile("email.tmpl");

// set list of skins
$randy->setSkins($skins);

// set up renderer
$randy->initRenderer();

// parse XML file
// interpolate variables
// display output
$randy->displayRenderedContent();

?>

Notice that this script does not contain any manually-inserted values for the template variables (as was done previously) - this time, the values are all supposed to be read in automatically from the specified XML file.

Now, when you run this script in your browser, you should see something like this:

EMAIL MESSAGE

From: some@where.com

To: admin@some.domain.com

Subject: Wedding Invitation

You are cordially invited to the wedding of John Doe with Jane Noe on 27 March 2005 at Buckley Court, Mumbai 400 056. Wedding ceremony at 3 PM. Cocktails at 9 PM.

And a quick peek at the source reveals that this time around, the template has been populated from the XML data source - you can verify this by making changes to the content in the XML file and seeing the changes appear in your rendered Web page.

<html>
<head></head>
<body>
<pre>
EMAIL MESSAGE

From: some@where.com

To: admin@some.domain.com

Subject: Wedding Invitation

You are cordially invited to the wedding of John Doe with Jane Noe on 27 March 2005 at Buckley Court, Mumbai 400 056. Wedding ceremony at 3 PM. Cocktails at 9 PM.
</pre>
</body>
</html>

This might seem very long and complex, but it's actually nothing more than a well-defined sequence of steps. As before, the first step in the sequence involves include()-ing all the required object and configuration files, and instantiating objects for the template engine and XML rendering engine.

<?php
// include patTemplate class
require_once("include/patTemplate.php");

// include patXMLRenderer clas
require_once("include/patXMLRenderer.php");

// initialize XML rendering class
$randy = new patXMLRenderer;

// initialize template engine
$template = new patTemplate();
?>

Locations for both the XML content files and the patTemplate-compliant template files are also set in this first phase, via the setXMLDir(),setXMLFile(), setBaseDir() and setTemplate() methods.

<?php
// set base directory for XML content
$randy->setXMLDir("xml/");

// set XML file to use
$randy->setXMLFile("email.xml");

// set base directory for templates
$template->setBaseDir("templates/");

// set template file to use
$randy->addTemplateFile("email.tmpl");
?>

Now, tell the rendering engine about the template engine so that the two of them can sniff each other and make friends,

<?php
// connect template object to renderer object
$randy->setTemplate($template);
?>

initialize the rendering engine

<?php
// set up renderer
$randy->initRenderer();
?>

and call the displayRenderedContent() method, which takes care of parsing the XML and putting its content into the appropriate template for display.

<?php
// parse XML file
// interpolate variables
// display output
$randy->displayRenderedContent();
?>

Miles To Go...

Here's another example, this one demonstrating building an unordered list from the content of an XML file. Here's the XML data,

<?xml version="1.0"?>
<countries>
    <item>India</item>
    <item>England</item>
    <item>France</item>
    <item>Singapore</item>
</countries>

and here are the two templates which will be used to build the page layout.

<!-- main page -->
<patTemplate:tmpl name="countries">
<html>
<head></head>
<body>
Places I must visit:
<ul>
{CONTENT}
</ul>
</body>
</html>
</patTemplate:tmpl>

<!-- list item -->
<patTemplate:tmpl name="item">
<li>{CONTENT}</li>
</patTemplate:tmpl>

Finally, here's the PHP script that brings it all together:

<?php

// include config file
require_once("config/patXMLRenderer.php");

// include patTemplate class
require_once("include/patTemplate.php");

// include patXMLRenderer clas
require_once("include/patXMLRenderer.php");

// initialize XML rendering class
$randy = new patXMLRenderer;

// set base directory for XML content
$randy->setXMLDir("xml/");

// set XML file to use
$randy->setXMLFile("map.xml");

// initialize template engine
$template = new patTemplate();

// set base directory for templates
$template->setBaseDir("templates/");

// connect template object to renderer object
$randy->setTemplate($template);

// set template file to use
$randy->addTemplateFile("map.tmpl");

// set list of skins
$randy->setSkins($skins);

// set up renderer
$randy->initRenderer();

// parse XML file
// interpolate variables
// display output
$randy->displayRenderedContent();
?>

And here's the output:

Places I must visit:
* India
* England
* France
* Singapore

Note that the template named "item" is repeated for each and every <item> element in the XML source file. This capability comes in handy when you need to repeat a single layout element for multiple content fragments.

The Perfect Margarita

Now that you have some familiarity with how the rendering engine works, let's try it out in a live environment. Let's assume here that I have a series of recipes for my favourite cocktails, all in XML format, and I now want to put together a Web site to showcase them to the world. It's way too much trouble to create HTML pages for each recipe (plus I know that XML is the format of the future), so instead I'm going to create a single layout template for the recipe display and use patXMLRenderer to read in the XML data fragments and insert them into the correct positions in the template. A PHP script will take care of selecting the appropriate XML file for display (the XML file name can be passed to the script via the URL GET method).

Here's my template - a two-column layout with the recipe ingredients and method on the left, and a navigation bar on the right.

And here's what one of my XML files looks like:

<?xml version="1.0"?>
<!-- margarita.xml -->
<content>
    <title>Margarita Madness</title>
    <!-- set up a two column page -->
    <columns>
        <!-- left column has ingredients, method and notes -->
        <column>
            <!-- ingredients section -->
            <section>
                <head>Ingredients</head>
                <body>
                    <ulist>
                        <item>Tequila</item>
                        <item>Triple sec</item>
                        <item>Lime juice</item>
                        <item>Crushed ice</item>
                        <item>Salt to taste</item>
                    </ulist>
                </body>
            </section>
            <!-- method section -->
            <section>
                <head>Preparation</head>
                <body>
                    <olist>
                        <item>Pour 2 portion of fresh lime juice into cocktail shaker</item>
                        <item>Add 3 portions of triple sec</item>
                        <item>Add 1 portion of tequila</item>
                        <item>Add crushed ice and shake</item>
                        <item>Rim glass with salt and pour contents from shaker into glass</item>
                    </olist>
                </body>
            </section>
            <!-- notes section -->
            <!-- note that this section does not need a bulleted list -->
            <section>
                <head>Notes</head>
                <body>Rose's Lime Juice is a good alternative if you can't find fresh juice</body>
            </section>
        </column>
        <!-- right column has links to similar recipes -->
        <column>
            <section>
                <head>Other recipes of interest</head>
                <body>
                    <linklist>
                        <link>
                            <target>martini.xml</target>
                            <label>Martini</label>
                        </link>
                        <link>
                            <target>cannonball.xml</target>
                            <label>Cannonball</label>
                        </link>
                        <link>
                            <target>sunrise.xml</target>
                            <label>Tequila Sunrise</label>
                        </link>
                    </linklist>
                </body>
            </section>
        </column>
    </columns>
</content>

I can now map this into my layout template above by creating HTML elements corresponding to the XML elements above. Here's the template:

<!-- main page -->
<patTemplate:tmpl name="content">
<html>
<head><basefont face="Tahoma"></head>
<body bgcolor="black" text="white">
{CONTENT}
</body>
</html>
</patTemplate:tmpl>

<!-- page title -->
<patTemplate:tmpl name="title">
<h1><center>{CONTENT}</center></h1>
</patTemplate:tmpl>

<!-- allow for multiple columns on page -->
<patTemplate:tmpl name="columns">
<table border="1" cellspacing="5" cellpadding="5"><tr>{CONTENT}</tr></table>
</patTemplate:tmpl>

<!-- each column is a table cell -->
<patTemplate:tmpl name="column">
<td valign="top">{CONTENT}</td>
</patTemplate:tmpl>

<!-- section headings -->
<patTemplate:tmpl name="head">
<h3>{CONTENT}</h3>
</patTemplate:tmpl>

<!-- numbered list -->
<patTemplate:tmpl name="olist">
<ol>{CONTENT}</ol>
</patTemplate:tmpl>

<!-- bulleted list -->
<patTemplate:tmpl name="ulist">
<ul>{CONTENT}</ul>
</patTemplate:tmpl>

<!-- bulleted list -->
<patTemplate:tmpl name="linklist">
<ul>{CONTENT}</ul>
</patTemplate:tmpl>

<!-- individual URLs -->
<patTemplate:tmpl name="link">
<li>{CONTENT}</li>
</patTemplate:tmpl>

<!-- URL target -->
<patTemplate:tmpl name="target">
<a href=[RANDY_SELF]&file={CONTENT}>
</patTemplate:tmpl>

<!-- URL label -->
<patTemplate:tmpl name="label">
{CONTENT}</a>
</patTemplate:tmpl>

<!-- items in list -->
<patTemplate:tmpl name="item">
<li>{CONTENT}</li>
</patTemplate:tmpl>

Fairly self-explanatory, this - all it does is set up a table with two columns and appropriate formatting for each XML element. Pay attention to my use of the special [RANDY_SELF] patXMLRenderer variable in the template - this variable always holds the name of the currently executing script, and it's useful when linking to other recipe files, as the recipe filename can simply be appended to it as a GET parameter.

With the template and XML out of the way, the final step is, as always, the PHP script that initializes the rendering engine. Here, I've modified the standard script you've seen so far to accept the name of the XML file from the URL itself. Take a look:

<?php

// include config file
require_once("config/patXMLRenderer.php");

// include patTemplate class
require_once("include/patTemplate.php");

// include patXMLRenderer clas
require_once("include/patXMLRenderer.php");

// initialize XML rendering class
$randy = new patXMLRenderer;

// set base directory for XML content
$randy->setXMLDir("xml/");

// set XML file to use
// get from URL
$randy->setXMLFile($_GET['file']);

// initialize template engine
$template = new patTemplate();

// set base directory for templates
$template->setBaseDir("templates/");

// connect template object to renderer object
$randy->setTemplate($template);

// set template file to use
$randy->addTemplateFile("recipe.tmpl");

// set list of skins
$randy->setSkins($skins);

// set up renderer
$randy->initRenderer();

// parse XML file
// interpolate variables
// display output
$randy->displayRenderedContent();
?>

And when you call "recipe.php" with the argument "?file=margarita.xml", here's what you'll see:

Run your mouse over the links on the right - you'll see that the [RANDY_SELF] variable has been replaced with the name of the currently executing script, with the name of the target XML file being passed to it as an argument.

And that's about all I have time for in this first segment. Over the last few pages, I taught you how to build simple templates and populate them manually, then create XML files and link them to your template with patXMLRenderer. I showed you a couple of examples of how patXMLRenderer could be used to map templates to XML elements, and wrapped up this introductory section with a look at how a single PHP script could be used with a single template to drive a site off XML content.

That's just the tip of the iceberg, though - in the second part of this series, I'll be looking at some of the more advanced things you can do with patXMLRenderer, including using XML attribute values in your templates, caching commonly-used pages, logging user activity, generating reports and creating a static offline version of your dynamic site. Make sure you come back for that!

This article was first published on15 Mar 2004.