The Perl Journal October 2003
There are a lot of templating modules on the Perl scene: HTML::Template, Text::Template, CGI::FastTemplate, and of course, the well known and formidable Template Toolkit. Why create yet another one?
All of these systems suffer from serious drawbacks, not least of which is loop management: Graphic designers have to know about the programmatic structure of the loops, and make sure the end statements are correctly placed.
The Zope Corporation came up with a very elegant solution the Template Attribute Language (TAL). TAL is an open specification that has been designed to make templates more compliant with WYSIWYG tools such as Adobe GoLive or Dreamweaver. There are many advantages to TAL, as described in Revuen Lerner's "At the Forge" article (http://www.lerner.co.il/atf/atf_98/).
In a nutshell, TAL lets you use extra XML attributes in a clever way so that you can control your templates. While TAL statements might look a bit alien at first, they integrate so nicely with the XML/XHTML syntax that template designers can simply ignore themeven for loop and conditional statements.
Clearly, TAL is a good idea. There was no Perl module for it. Stealing a good idea is often also a good idea. So I started hacking around with XML::Parser and HTML::TreeBuilder.
Eventually, I released something on CPAN and created a mailing list. A small yet dedicated community of Perl hackers joined in, submitting bug reports, patches, benchmarks, and tests. Very quickly, Petal (PErl TAL) became a full-featured, robust module.
Say you need a screen for an application that says hello to a logged-in user. The template looks like this:
<html> <body> <p>Hello, World!</p> </body> </html>
Since Petal is an XML processing tool, the first thing you want to do on a template this simplistic is to send it through HTML tidy to make it valid XHTML as follows (the template just shown lives in file example1_01.html; all code from this article is available at http://www.tpj.com/source/):
$ tidy -asxhtml -asutf8 <example1_01.html
Which outputs:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> </head> <body> <p>Hello, World!</p> </body> </html>
Assuming that you are using HTTP authentication, the user login should be contained in the variable $ENV{REMOTE_USER}. In TAL expression syntax, this corresponds to ENV/REMOTE_USER.
Your template then becomes:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://purl.org/petal/1.0/"> <head> <title></title> </head> <body> <p> Hello, <spantal:replace="ENV/REMOTE_USER">World</span>! </p> </body> </html>
(This template is example1_03.html in the source code for this article.) As you can see, a few things were added. The attribute xmlns:tal="http://purl.org/petal/1.0/" means "all the stuff that is prefixed with tal: is in the Petal namespace." If you don't specify this, Petal will default to using the petal: prefix by default rather than tal:.
<span tal:replace="ENV/REMOTE_USER">World</span> means "replace this tag and its contents with the value returned by the expression ENV/REMOTE_USER."
Now, open up your template in your favorite browser: It still looks like the original mockup!
Example 1 shows a sample Perl script to test our template. (This is file example1.pl in the source code.) As you can see, using Petal is pretty similar to other templating systems: You create a template object pointing to the template file you want to process, and then you execute the process(%args) method, passing a hash of arguments.
One problem with this template is that if $ENV{REMOTE_USER} is undefined, you will get the following output:
<html> <body> <p>Hello, !</p> </body> </html>
Which doesn't exactly look good.
You can add a condition as follows:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://purl.org/petal/1.0/"> <head> <title></title> </head> <body> <p tal:condition="true: ENV/REMOTE_USER"> Hello, <span tal:replace="ENV/REMOTE_USER">World</span>! </p> </body> </html>
In the event that ENV/REMOTE_USER is false, tal:condition will remove the <p> tag altogether. Now, open this new template in your favorite browser or HTML editor. Despite the added condition, it still looks the same!
As you can see, this template is perfectly well-formed XML. There are many useful things you can do with it. You can edit it in Dreamweaver or GoLive, send it through HTML tidy, or check it for well-formed XML with xmllint.
But now, let's move on to a more advanced, and more interesting example.
Let's say that you want to write a script that takes the URI of an RSS file and turns it into beautifully crafted XHTML. You could try to figure out an XSL stylesheet, which would transform the RSS into the XHTML page you've been given, and then use an XSL processor such as sabolotron to do the job. Eventually.
Or you could use LWP::Simple to fetch the file, XML::RSS to parse it, and Petal to template it, and leave work much earlier.
Let's look at the script in Example 2. We are going to want Petal to access the following Perl values:
· $rss->channel ('title'), the title of the channel
· $rss->channel ('link'), the URI of the channel
· each element of the array @{$rss->{items}}, for example: $rss ->{item}->[$index]->{title} and $rss->{item}->[$index]->{link}
Now, let's look at this HTML mockup:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1- strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> </head> <body> <h1> <a href="#">This is the RSS title</a> </h1> <ul> <li> <a href="#">RSS item</a> </li> </ul> </body> </html>
The very first thing we want to do is to add the Petal namespace:
<html xmlns:tal="http://purl.org/petal/1.0/">
Then, using tal:define, we'll define a few aliases to save some typing in the rest of the template:
<html xmlns:tal="http://purl.org/petal/1.0/" tal:define="rss_title rss/channel --title; rss_link rss/channel --link; rss_desc rss/channel --description; rss_items rss/items" >
As you can see:
rss/channel -title maps to $rss->channel ('title')
rss/channel -link maps to $rss->channel ('link')
rss/channel -description maps to $rss->channel ('description')
rss/items maps to $rss->{items}
You can use some/stuff and not worry about stuff being the key of a hash, an object attribute, or an object method. Petal will just Do The Right Thing.
Now, we want to replace the content of the <a> element within the <h1> element with the value of the rss_title variable. In order to do that, we use a tal:content directive.
<a href="#" title="some_title" tal:content="rss_title">
We also want to replace the href attribute with the value of rss_link, and the title attribute with the value of rss_desc. Both are replaced with the tal:attributes statement. Our <a> element becomes:
<a href="#" title="some_title" tal:content="rss_title" tal:attributes="href rss_link; title rss_desc">
Now, we need to iterate through each element of the rss_items array. In order to do that, we'll use tal:repeat on the <li> element so that we get one <li> per iteration.
<li tal:repeat="item rss_items">
This statement creates a new variable called item for each iteration of the loop. As previously, we use tal:content and tal:attributes on the <a> element, respectively.
<a href="#" tal:attributes="href item/link" tal:content="item/title">
Our complete template is shown in Figure 1. You can try it with the script in Example 2 (example2.pl in the source code).
So, to summarize the Petal attributes:
And that's pretty much what TAL is all about. Using these basic building blocks, you can template almost any kind of XML, while keeping your XML template well formed and compatible with whatever tool you use to edit, fix, or validate it.
Let's examine what happens behind the scenes. Whenever you process an XML template using Petal, the library does (roughly) the following:
1. Read the source XML template.
2. $INPUT (XML or HTML) throws XML events from the source file.
3. $OUTPUT (XML or HTML) uses these XML events to canonicalize the template.
4. Petal::CodeGenerator turns the canonical template into Perl code.
5. Petal::Cache::Disk caches the Perl code on disk.
6. Petal turns the Perl code into a subroutine.
7. Petal::Cache::Memory caches the subroutine in memory.
8. Petal executes the subroutine.
This big, complicated procedure is really done only once. Subsequent calls to the same template will always resume at step 5 until the template file changes. If you are running a persistent environment a la mod_perl, then you get an extra bonus because subsequent calls to the same template in the same process will only involve step 8.
All this makes Petal pretty speedy. If you need even more speed, you can use Fergal Daly's most excellent Petal::CodePerl. Petal::CodePerl is a subclass of Petal that compiles your templates to highly optimized Perl code. This module is still experimental, yet it seems very sound.
Petal supports multiple parsing mechanisms. By default, Petal can parse your templates using the rather broken XML::Parser (I wish I had known that when I started to write Petal).
However, if you find XML::Parser too picky for your source templates (because your templates are not well formed), you can also use HTML::TreeBuilder, which will do a reasonable job at parsing somewhat broken XHTML.
You can enable this behavior as follows:
# use a local variable local $Petal::INPUT = 'XHTML'; # or pass it as an argument to the constructor my $template = Petal->new ( file => â"gerbils.htmlâ", input => â"HTMLâ", );
Whichever parser you use will fire XML::Parser look-alike XML events, which will be used by a canonicalizer.
At this point, I have to confess: Petal started as just a big hack. It is built on top of a previous twist in which I used a syntax made of XML processing instructions as follows:
<?if name="something"?> ... do something <?var name="foo"?> etc... <?end?>
Instead of building a module that directly converts XML events into Perl code, I've built a module that converts XML events into the just described syntax, because this system was already working. I've also made this module turn an $inline_variable into a <?var name="inline_variable"?>.
The Petal canonicalizer was born. It turns alternate syntaxes (TAL, inline) into the original canonical syntax, which unfortunately happens to look very ugly. A neat effect is that you can use all three syntaxes at the same time, so this is legal:
<?if name="true:something"?> <p tal:repeat="item something">$item</p> <?end?>
However, you should really stick with the standard TAL syntax as much as possible:
<p tal:condition="true:something" tal:repeat="item something" tal:content="item">Exempli Gratia</p>
If you want more information on the canonical syntax, see the perldoc Petal section "UGLY SYNTAX."
There are now two canonicalizer modules. The default canonicalizer produces generic XML. The XHTML canonicalizer has extra logic to deal with XHTML-specific syntaxic annoyances, such as: <br></br> needs to be <br />. If you want to output HTML, you want to change the output canonicalizer to XHTML:
# use a local variable local $Petal::OUTPUT = 'XHTML'; # or pass it as an argument to the constructor my $template = Petal->new ( file => â"camels.htmlâ", output => â"HTMLâ", );
So your poor template now looks like a terrible mix of XML processing instructions and XML tags. Although this syntax is hard to read for a human, Perl-wise it is much easier to tokenize and parse. As its name implies, the code generator returns a string that can be evaled. When the string is evaled, it returns a CODE reference ready to be executed.
You can even substitute Petal's code generator with another one using the $Petal::CodeGenerator variable, but you probably will never need to do that unless you're into serious Petal hacking.
Imagine that you want to replace an attribute of a tag with more than a simple variable. For the sake of the example, let's call the attribute bar and its tag foo. You want to replace it with the value "Hello, $user".
<foo bar="dummy value" />
Of course, you could directly replace "dummy value" with "Hello, $user" as follows:
<foo bar="Hello, $user" />
but this is not compliant with the TAL specification. If you want to keep your dummy value intact, you can use the tal:attributes statement combined with the string: modifier. So, instead of writing:
<!-- doesn't do the right thing --> <foo bar="dummy value" tal:attributes="bar user"/>
You write:
<!-- does the right thing -->
<foo bar="dummy value" tal:attributes="bar string:Hello, ${user}!"/>
Modifiers are implemented using either code references or modules. Petal features the following default modifiers:
The exciting thing is that it's possibleand dead easyto write your own modifiers. For example, let's write an uppercase: modifier:
$Petal::Hash::MODIFIERS->{'uppercase:'} = sub {
my $hash = shift;
my $value = shift;
return uc ($value);
};
We can then write the following template:
<span tal:replace="uppercase:string:Hello, World!">hello, world</span>
which will output:
HELLO, WORLD!
or we could write a decrement: modifier, which would decrement a variable:
$Petal::Hash::MODIFIERS->{'decrement:'} = sub {
my $hash = shift;
my $value = shift;
$hash->{$value}--;
};
And try it on:
<spantal:define="num 5" tal:replace="decrement:num" />
If the modifier you're planning to write is too big to be a single subroutine, you can write it as a module. The uppercase example becomes:
package Petal::Hash::UpperCase;
use warnings;
use strict;
sub process
{
my $class = shift;
my $hash = shift;
my $expr = shift;
return uc ($hash->get ($expr));
}
1;
__END__
And that's it: Petal will automagically pick up any file in @ISA that looks like "Petal/Hash/SomeWeirdPlugin.pm" and install it as "someweirdplugin:".
Fortunately, Petal is not "yet another templating module." It is a stable Perl implementation of an open specification from the Zope Corporation team. So far, TAL has been implemented in Python, PHP, and Perl.
Petal has all the bells and whistles you would expect from a good templating module: a modular architecture, proper Unicode support, decent speed, mod_perl friendliness, POD documentation, and a test suite. Moreover, the TAL specification lets you amend XML templates in a way that stays compatible with existing XML tools. With TAL, error-prone <%end-loop%>-style tags are a nonissue. Petal makes use of TAL to help you produce industry-standard-compliant data.
Finally, and more important, the main strength of Petal has been and will be its mailing list and its fantastic community of friendly and competent users.
So type "perl -MCPAN -e 'install Petal"' and join us!
Text::Template: http://search.cpan.org/author/MJD/Text- Template-1.44/lib/Text/Template.pm
HTML::Template:http://search.cpan.org/author/ SAMTREGAR/HTML-Template-2.6/Template.pm
CGI::FastTemplate: http://search.cpan.org/author/JMOORE/CGI-FastTemplate-1.09/FastTemplate.pm
Template Toolkit: http://template-toolkit.org/
TAL of ZPT: http://www.zope.org/Wikis/DevSite/Projects/ZPT/TAL
TAL specification: http://zope.org/Wikis/DevSite/Projects/ZPT/TAL%20Specification%201.4
Petal: http://search.cpan.org/author/JHIVER/Petal-1.03/
Petal mailing list: http://lists.webarch.co.uk/mailman/ listinfo/petal/
"At the Forge:" http://www.lerner.co.il/atf/atf_98
TPJ