Petal::Tiny - super light TAL for Perl!
in your Perl code:
use Petal::Tiny; my $template = Petal::Tiny->new('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>
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.
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.
petal:
tal:
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...
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.
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:
my_var
<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.
Now let's say that your method hello_world() can take an optional argument so that $some_object->hello_world ('Jack') returns Hello Jack.
$some_object->hello_world ('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}}
UNSUPPORTED. Either switch to Petal or write a separate module which handles this.
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.
Go read https://github.com/zopefoundation/zpt-docs (http://www.zope.org/Wikis/DevSite/Projects/ZPT/TAL> is dead). 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/">
tal:attributes always overrides the content of an attribute, but occasionally you want to concatenate the new string to the existing string. Prefixing the attribute name with '+' allows you do to this:
<div class="foo " tal:attributes="+class bar"/>
outputs
<div class="foo bar"/>
With +, if the expression returns undef the exisiting attribute is left unchanged. Without +, it's still deleted.
tal:repeat understands semicolon-separated loop-variables to nest loops within same tag, e.g.:
some_keys => [ "foo", "bar" ], some_hash => { foo => [ "fooval1", "fooval2" ], bar => "barval", } <span tal:repeat="key some_keys; val some_hash key" tal:replace="structure string:${key}=${val}&"/>
will evaluate to
foo=fooval1&foo=fooval2&bar=barval&
UNSUPPORTED.
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 = Petal::Tiny->new('foo.xml'); print $template->process ( $hashref );
Then we will show how the Petal Expression Syntax maps to the Perl way of accessing these 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>
$hashref->{'some_hash'}->{'a_key'};
some_hash/a_key
<!--? Replaces Hello, World with the contents of $hashref->{'some_hash'}->{'a_key'} --> <span tal:replace="some_hash/a_key">Hello, World</span>
some_hash a_variable
<!--? Replaces Hello, World with the contents of $hashref->{'some_hash'}->{'a_key'} --> <span tal:define="a_variable --a_key" tal:replace="some_hash a_variable">Hello, World</span>
$hashref->{'some_array'}->[12]
some_array/12
<!--? Replaces Hello, World with the contents of $hashref->{'some_array'}->[12] --> <span tal:replace="some_array/12">Hello, World</span>
some_array a_variable
<!--? Replaces Hello, World with the contents of $hashref->{'some_array'}->[12] --> <span tal:define="a_variable 12" tal:replace="some_array a_variable">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>
If you want to loop through a hash, supply both the hash, as well as its relevant keys in $hashref, e.g.:
some_keys => [ "foo", "bar" ], some_hash => { foo => "fooval", bar => "barval", } <input type="text" tal:repeat="a_key some_keys" tal:attributes="name a_key; value some_hash a_key" />
which will generate the HTML
<input type="text" name="foo" value="fooval" /> <input type="text" name="bar" value="barval" />
If $hashref->{'some_function'} = sub { ... }.
Perl expressions
1. $hashref->{'some_function'}->(); 2. $hashref->{'some_function'}->('foo', 'bar'); 3. $hashref->{'some_function'}->($hashref->{'some_variable'});
"Petal::Tiny expressions"
1. some_object/some_function 2. some_object/some_function --foo --bar 3. some_object/some_function some_variable
TRAP: If the last item in the path is a function or method which returns a function, it is the path-member who gets the argument-list; there's no way to predict the future and giving the argument-list to the function.
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"
Petal lets you traverse any data structure, i.e.
$hashref->{'some_object'} ->some_method() ->{'key2'} ->{'some_function'}->() ->some_other_method ( 'foo', $hash->{bar} );
some_object/some_method/key2/some_function/some_other_method --foo bar
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.
true:
I'm pretty sure you can work this one out by yourself :-)
The string: modifier lets you interpolate petal expressions within a string and returns the value.
string:
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.
Just go and pollute the Petal::Tiny namespace:
sub Petal::Tiny::modifier_uppercase { my $self = shift; my $string = shift; my $context = shift; return uc ($self->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.
Alternatively add your modifiers to a subclass of Petal::Tiny, and instantiate that class instead of Petal::Tiny.
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
structure my/encoded/variable
Note that this is a language keyword, not a modifier. It does not use a trailing colon.
UNSUPPORTED. Petal::Tiny does no caching.
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.
UNSUPPORTED. See Petal::Deprecated.
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)
None.
If you find any, please drop me an email or pull request on github. Patches are always welcome.
This source is on Github:
https://github.com/lbalker/petal-tiny
Current maintainer 1.05+: Lars Balker lars@balker.dk
Original author: Jean-Michel Hiver - jhiver (at) gmail (dot) com
Petal, Template::TAL, Mojolicious::Plugin::PetalTinyRenderer
This module free software and is distributed under the same license as Perl itself. Use it at your own risk.
To install Petal::Tiny, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Petal::Tiny
CPAN shell
perl -MCPAN -e shell install Petal::Tiny
For more information on module installation, please visit the detailed CPAN module installation guide.