NAME
Petal::Tiny - super light TAL for Perl!
SYNOPSIS
in your Perl code:
use Petal::Tiny;
my $template = new Petal::Tiny ('foo.xhtml');
print $template->process (bar => 'BAZ');
in foo.xhtml
<html xmlns:tal="http://purl.org/petal/1.0/">
<body tal:content="bar">Dummy Content</body>
</html>
and you get something like:
<html>
<body>BAZ</body>
</html>
SUMMARY
Almost 10 years ago now at the time of this writing, I wrote Petal, an XML based templating engine that is able to process any kind of XML, XHTML and HTML. Although I no longer maintain it, I have still used it until today.
Petal is kind of the swiss army knife of the XML templating. It supports pluggable parsers. Pluggable generators. XML to perl compilation. Disk and memory caches. Definable charset encoding and decoding. XML or XHTML entity encoding. I18N. etc. etc.
I wanted something that had most of the really cools feature of Petal, but that was small and didn't have any dependancies.
Hence, after a couple of days of coding, Petal::Tiny was born. It's still Petal, but is weighting around 500 lines of code, is completely self-contained in one .pm file, and doesn't need anything else than Perl.
This POD hence steals a lot of its documentation and explains the differences between the two modules.
NAMESPACE
Although this is not mandatory, Petal templates should include use the namespace http://purl.org/petal/1.0/. Example:
<html xml:lang="en"
lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://purl.org/petal/1.0/">
Blah blah blah...
Content of the file
More blah blah...
</html>
If you do not specify the namespace, Petal will by default try to use the petal:
prefix. However, in all the examples of this POD we'll use the tal:
prefix to avoid too much typing.
KICKSTART
Let's say you have the following Perl code:
use Petal::Tiny;
my $template = Petal::Tiny->new ('/my/templates/foo.xml');
print $template->process ( my_var => some_object() );
some_object() is a subroutine that returns some kind of object, may it be a scalar, object, array referebce or hash reference. Let's see what we can do...
Version 1: WYSIWYG friendly prototype.
Using TAL you can do:
This is the variable 'my_var' :
<span tal:replace="my_var/hello_world">Hola, Mundo!</span>
Now you can open your template in any WYSIWYG tool (mozilla composer, frontpage, dreamweaver, adobe golive...) and work with less risk of damaging your petal commands.
Version 2: Object-oriented version
Let's now say that my_var
is actually an object with a method hello_world() that returns Hello World. To output the same result, your line, which was:
<span tal:replace="my_var/hello_world">Hola, Mundo!</span>
Would need to be... EXACTLY the same. Petal lets you access hashes and objects in an entirely transparent way and tries to automagically do The Right Thing for you.
This high level of polymorphism means that in most cases you can maintain your code, swap hashes for objects, and not change a single line of your template code.
Version 3: Personalizable
Now let's say that your method hello_world() can take an optional argument so that $some_object->hello_world ('Jack')
returns Hello Jack.
You would write:
<span
tal:define="var_jack string:Jack"
tal:replace="my_var/hello_world var_jack">Hola, Mundo!</span>
Optionally, you can directly pass strings (so long as they don't contain spaces) using two dashes, a la GNU command-line option:
<span tal:replace="my_var/hello_world --Jack">Hola, Mundo!</span>
TRAP#1: With Petal, You could write:
<span tal:replace="my_var/hello_world 'Jack'">Hola, Mundo!</span>
This syntax is NOT supported by Petal::Tiny. It's a drag to code, looks ugly in your templates, and I never used this feature. Thus I dropped it.
TRAP#2: Just like with Petal, you can NOT write nested expressions such as:
${my_var/hello_world ${my_var/current_user}}
Version 4: Internationalized
UNSUPPORTED. Either switch to Petal or write a separate module which handles this.
OPTIONS
When you create a Petal template object you can specify plethoras of options controling file pathes, input parsers / output generators, pluggable encoding mechanism, language options, etc. etc. Looking back at it I found it totally over-engineered.
With Petal::Tiny you pass a single argument, which is either a file name or XML data, and that's it. If the stuff which you pass contains < or a new line, it's considered XML data. Otherwise it's treated as a file name.
TAL syntax
Go read http://www.zope.org/Wikis/DevSite/Projects/ZPT/TAL. Petal::Tiny tries to comply with the TAL spec a lot more than Petal did.
Currently it implements all operations, i.e. define, condition, repeat, content, replace, attributes, omit-tag and even on-error (which allows for much nicer error reporting and exception handling than Petal).
But it also tries to remain true to the "Petal Spirit", hence things like directly interpolating variables still work, so instead of having to type things such as:
<!-- fully TAL compliant version -->
<p>Checkout amount: <span petal:content="self/basket/total">TOTAL</span> USD</p>
You can still write:
<!-- BAM! Petal way. Much easier, especially for quick prototyping. -->
<p>Checkout amount: $self/basket/total USD</p>
TRAP: Don't forget that the default prefix is petal:
NOT tal:
, until you set the petal namespace in your HTML or XML document as follows:
<html xmlns:tal="http://purl.org/petal/1.0/">
METAL macros
UNSUPPORTED.
EXPRESSIONS AND MODIFIERS
Just like Petal, Petal::Tiny has the ability to bind template variables to the following Perl datatypes: scalars, lists, hash, arrays and objects. The article describes the syntax which is used to access these from Petal templates.
In the following examples, we'll assume that the template is used as follows:
my $hashref = some_complex_data_structure();
my $template = new Petal::Tiny ('foo.xml');
print $template->process ( $hashref );
Then we will show how the Petal Expression Syntax maps to the Perl way of accessing these values.
accessing scalar values
Perl expression
$hashref->{'some_value'};
Petal expression
some_value
Example
<!--? Replaces Hello, World with the contents of
$hashref->{'some_value'}
-->
<span tal:replace="some_value">Hello, World</span>
accessing hashes & arrays
Perl expression
$hashref->{'some_hash'}->{'a_key'};
Petal expression
some_hash/a_key
Example
<!--? Replaces Hello, World with the contents
of $hashref->{'some_hash'}->{'a_key'}
-->
<span tal:replace="some_hash/a_key">Hello, World</span>
Perl expression
$hashref->{'some_array'}->[12]
Petal expression
some_array/12
Example
<!--? Replaces Hello, World with the contents
of $hashref->{'some_array'}->[12]
-->
<span tal:replace="some_array/12">Hello, World</span>
Note: You're more likely to want to loop through arrays:
<!--? Loops trough the array and displays each values -->
<ul tal:condition="some_array">
<li tal:repeat="value some_array"
tal:content="value">Hello, World</li>
</ul>
accessing object methods
Perl expressions
1. $hashref->{'some_object'}->some_method();
2. $hashref->{'some_object'}->some_method ('foo', 'bar');
3. $hashref->{'some_object'}->some_method ($hashref->{'some_variable'})
1. some_object/some_method
2. some_object/some_method --foo --bar
3. some_object/some_method some_variable
WARNING! The below expressions which work in Petal are UNSUPPORTED by this module!
2a. some_object/some_method 'foo' 'bar'
2b. some_object/some_method "foo" "bar"
composing
Petal lets you traverse any data structure, i.e.
Perl expression
$hashref->{'some_object'}
->some_method()
->{'key2'}
->some_other_method ( 'foo', $hash->{bar} );
Petal expression
some_object/some_method/key2/some_other_method --foo bar
true:EXPRESSION
If EXPRESSION returns an array reference
If this array reference has at least one element
Returns TRUE
Else
Returns FALSE
Else
If EXPRESSION returns a TRUE value (according to Perl 'trueness')
Returns TRUE
Else
Returns FALSE
the true:
modifiers should always be used when doing Petal conditions.
false:EXPRESSION
I'm pretty sure you can work this one out by yourself :-)
set:variable_name EXPRESSION
UNSUPPORTED.
string:STRING_EXPRESSION
The string:
modifier lets you interpolate petal expressions within a string and returns the value.
string:Welcome $user/real_name, it is $date!
Alternatively, you could write:
string:Welcome ${user/real_name}, it is ${date}!
The advantage of using curly brackets is that it lets you interpolate expressions which invoke methods with parameters, i.e.
string:The current CGI 'action' param is: ${cgi/param --action}
And IMHO, they make your interpolated variables stand out a lot more in your templates, so I advise you to use them.
writing your own modifiers
Just go and pollute the Petal::Tiny namespace:
sub Petal::Tiny::modifier_uppercase {
my $class = shift;
my $string = shift;
my $context = shift;
return uc (Petal::Tiny::resolve ($expression, $context));
}
Please remember that you need to prefix your modifier name with 'Petal::Tiny::modifier_', thus if you need to create a modifier "SPONGYBOB:", you define Petal::Tiny::modifier_SPONGYBOB.
Expression keywords
XML encoding / structure keyword
By default Petal will encode &
, <
, > and
"
to &
, <
, >
and "
respectively. However sometimes you might want to display an expression which is already encoded, in which case you can use the structure
keyword.
structure my/encoded/variable
Note that this is a language keyword, not a modifier. It does not use a trailing colon.
Petal::Hash caching and fresh keyword
UNSUPPORTED. Petal::Tiny does no caching.
TOY FUNCTIONS (For debugging or if you're curious)
UNSUPPORTED. Besides, you will find thatL <Petal::Tiny> error reporting and handling is a lot better than Petal's, leading to less debugging requirement. So long as you feed Petal::Tiny with valid XML, you'll be fine.
UGLY SYNTAX
UNSUPPORTED. See Petal::Deprecated.
Performance considerations
The cycle of a Petal template is 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
9. (optional) Petal internationalizes the resulting output.
If you are under a persistent environement a la mod_perl, subsequent calls to the same template will be reduced to step 8 until the source template changes.
The cycle of a Petal::Tiny template is the following:
1. Read the source XML template
2. Tokenize it using a big regex
3. Recursively process the tokens
Benchmarking a simple piece of basic XML shows that Petal is much faster when running its caches, but much slower otherwise:
Benchmark: timing 1000 iterations of Petal (disk cache), Petal (memory cache), Petal (no cache), Petal::Tiny... Petal (disk cache): 3 wallclock secs ( 2.50 usr + 0.10 sys = 2.60 CPU) @ 384.62/s (n=1000) Petal (memory cache): 2 wallclock secs ( 1.76 usr + 0.05 sys = 1.81 CPU) @ 552.49/s (n=1000) Petal (no cache): 18 wallclock secs (17.85 usr + 0.09 sys = 17.94 CPU) @ 55.74/s (n=1000) Petal::Tiny: 6 wallclock secs ( 6.57 usr + 0.04 sys = 6.61 CPU) @ 151.29/s (n=1000)
EXPORTS
None.
BUGS
If you find any, please drop me an email. Patches are always welcome.
SEE ALSO
Jean-Michel Hiver - jhiver (at) gmail (dot) com
This module free software and is distributed under the same license as Perl itself. Use it at your own risk.