The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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'});

"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.

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'});

"Petal::Tiny expressions"

  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 &amp;, &lt;, &gt and &quot; 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.