NAME
Petal::Tiny - super light TAL for Perl!
SYNOPSIS
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>
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 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/">
Modifications to TAL
'+' in attributes
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.
Nested loops in repeat
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&
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 = 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.
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>
Petal expression
some_hash a_variable
Example
<!--? 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>
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>
Petal expression
some_array a_variable
Example
<!--? 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" />
calling anonymous functions
If $hashref->{'some_function'} = sub { ... }.
Perl expressions
1. $hashref->{'some_function'}->();
2. $hashref->{'some_function'}->('foo', 'bar');
3. $hashref->{'some_function'}->($hashref->{'some_variable'});
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.
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_function'}->()
->some_other_method ( 'foo', $hash->{bar} );
Petal expression
some_object/some_method/key2/some_function/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 $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.
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 or pull request on github. Patches are always welcome.
SOURCE AVAILABILITY
This source is on Github:
https://github.com/lbalker/petal-tiny
AUTHOR
Current maintainer 1.05+: Lars Balker lars@balker.dk
Original author: Jean-Michel Hiver - jhiver (at) gmail (dot) com
SEE ALSO
Petal, Template::TAL, Mojolicious::Plugin::PetalTinyRenderer
LICENSE
This module free software and is distributed under the same license as Perl itself. Use it at your own risk.