The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Petal - Perl Template Attribute Language

SYNOPSIS

  use Petal;
  my $template = new Petal ( base_dir => '.', file => 'test.html' );
  print $template->process (
    some_hash   => $hashref,
    some_array  => $arrayref,
    some_object => $object,
  );

SUMMARY

Hopefully, Petal is a bit more than "yet another template engine".

First of all, Petal uses XML::Parser to process XML templates and makes it easy to produce well-formed XML from these template pages. If your template is not valid XML, then Petal Petal will issue a warning and attempt to parse the file using HTML::TreeBuilder and generate the XML events using the HTML::TreeBuilder parsed tree.

So stricly speaking, your templates won't *need* to be valid XML, but it would be good practice if they were.

Besides, Petal borrows a lot from Zope Page Templates TAL specification in order to make template files very dreamweaver / frontpage / etc. friendly.

The idea is to enforce even further the separation of logic and presentation. With Petal, graphic designers who use their favorite WYSIWYG editor can easily edit templates without having to worry about the loops and ifs which happen behind the scenes.

Besides, you can safely send the result of their work through HTML tidy to make sure that you always output neat, standard compliant, valid XML pages.

GLOBAL VARIABLES

$Petal::PARSER - Currently acceptable values are

  'XML'  - Petal will use XML::Parser to parse the template
  'HTML' - Petal will use HTML::TreeBuilder to parse the template
  'ANY'  - Petal will try with XML::Parser first, then with
           HTML::TreeBuilder

This variable defaults to 'ANY'

$Petal::BASE_DIR - Default value for the 'base_dir' option which can be passed to the constructor (see below). Defaults to undef.

$Petal::DISK_CACHE - Default value for the 'disk_cache' option which can be passed to the constructor (see below). Defaults to TRUE.

$Petal::MEMORY_CACHE - Default value for the 'memory_cache' option which can be passed to the constructor (see below). Defaults to TRUE.

$Petal::VERSION - Module version

METHODS

$class->new ( file => $file, %options );

Instanciates a new Petal object. %options can include:

* base_dir - Base directory to find templates. Defaults to $Petal::BASE_DIR or '/' if $Petal::BASE_DIR is not defined.

* disk_cache - Disables the use of the Petal::Cache::Disk module. Defaults to $Petal::DISK_CACHE.

* memory_cache - Disables the use of the Petal::Cache::Memory module. Defaults to $Petal::MEMORY_CACHE.

Example:

  my $template = new Petal (
      file       => 'foo.html',
      base_dir   => '.',
      disk_cache => 0
  );

$self->process (%hash);

Processes the current template object with the information contained in %hash. This information can be scalars, hash references, array references or objects.

Example:

  my $data_out = $template->process (
    user   => $user,
    page   => $page,
    basket => $shopping_basket,    
  );

  print "Content-Type: text/html\n\n";
  print $data_out;

Petal Syntax Summary

Petal features a flexible, XML-compliant syntax which can be summarized as follows:

Template comments

  <!--? This will not be in the output -->

"Simple" syntax (Variable Interpolation)

  $my_variable
  $my_object.my_method
  $my_array.2
  $my_hash.some_key

"TAL-like" syntax, i.e.

  <li petal:repeat="element list"
      petal:content="element">Some dummy element</li>

Which if 'list' was ('foo', 'bar', 'baz') would output:

  <li>foo</li>
  <li>bar</li>
  <li>baz</li>

"Usual" syntax (like HTML::Template, Template::Toolkit, etc), i.e

  <?petal:condition="list"?>
    <?petal:repeat="element list"?>
      <li><?petal:var name="element"?></li>
    <?end?>
  <?end?>

Variable expressions and modifiers

Petal has the ability to bind template variables to the following Perl datatypes: scalars, lists, hash, arrays and object.

How it works

  <?petal:var name="user.login"?>

Is *EXACTLY* the same as writing

  <?petal:var name=":var user.login"?>

Which internally is turned into

  push @out, $hash->{':var user.login'};

$hash is an highly magical hash which is tied to the Petal::Hash class, and uses the ':var' information to pass the expression 'user.login' to the Petal::Hash::VAR module.

The Petal::Hash::VAR module has access to $hash, and has the responsibility to resolve the user.login expression. So if $hash->{'user'} is an object and 'login' is a method on this object, 'user.login' will do the 'Right Thing' and return $hash->{user}->login();

Expression evaluation

Using a uniform, simple syntax you can access:

  * scalars: <?petal var="my_scalar"?>
  * hashes: <?petal var="my_hash.key"?>
  * arrays: <?petal var="my_array.12"?>
  * objects methods: <?petal var="my_object.my_method" ?>

Note that you can also pass arguments to object methods. Let's say that you have an object 'math', you could do:

  2+2 = <?petal:var name="math.add 2 2"?>

Even more powerful, let's say that you have:

  $hash = { math => $math_object, number => 3 }

You could write the following:

  $number+$number = <?petal:var name="math.add $number $number"?>

Which would output:

  3+3 = 6

If you wonder how it all works, I suggest that you take a look at the Petal::Hash and Petal::Hash::VAR modules.

Expression modifiers

We have seen that :var maps to Petal::Hash::VAR, which evaluates expressions.

There are other modifiers, which map to the following modules:

  :xml    => Petal::Hash::Encode_XML
  :encode => (alias for :xml)
  :true   => Petal::Hash::TRUE
  :false  => Petal::Hash::FALSE
  :not    => (alias for :false)
  :set    => Petal::Hash::SET

You can write your own modifiers easily by just subclassing Petal::Hash::VAR. Look at the Petal::Hash POD for more information on how to do this.

:xml / :encode

These will let you output a variable, but encodes the XML entities. Let us say that:

  $user.name

Produces:

  Smith & Co.

Which is invalid XML. You could write:

  $encode:user.name

Or

  <?petal:var name=":encode user.name"?>

Or

  <span petal:replace=":encode user.name">User Name Here</span>

:true

Mainly to be used with expressions such as

  <?petal:if name=":true user.has_access"?>

:false

I'm pretty sure you can work it out by yourself:-)

:set

This one is the wierdest modifier. It will return __NOTHING__ no matter what, but will set the result into the hash. For instance:

  <?petal:var name="foo.bar"?>

Could be rewritten:

  <?petal:var name=":set newVariableNameForBar foo.bar"?>
  <?petal:var name="newVariableNameForBar"?>

This is mainly intended so that if you have a.very.very.long.expression, you can alias it to something like 'vLongExpr' and save some typing (as well as providing a slight performance boost if you're using the expression inside a loop).

Petal TAL-like syntax

This functionality is inspired from TAL, the specification of which is there: http://www.zope.org/Wikis/DevSite/Projects/ZPT/TAL. In order to save some typing I'll just point out the differences and important points:

* The prefix is not tal:, but petal:. It's two extra characters to write but although Petal is partly inspired from TAL, it does _NOT_ implement the TAL specification which justifies the fact that a different namespace is used.

define

  <!-- sets document.title to 'title' -->
  <span petal:define="title document.title">

condition (ifs)

  <span petal:condition="user.is_authenticated">
    Yo, authenticated!
  </span>

repeat (loops)

  <li petal:repeat="user system.user_list">$xml:user.real_name</span>

attributes

  <a href="http://www.gianthard.com"
     lang="en-gb"
     petal:attributes="href document.href_relative; lang document.lang">

interpolation

  <span petal:content=":xml title">Dummy Title</span>
  <span petal:replace=":xml title">Dummy Title</span>

'petal:content' and 'petal:replace' are *NOT* aliases. The former will replace the contents of the span tag, while the latter will replace the whole span tag.

Composite constructs

You can do things like:

  <p petal:define="children document.children"
     petal:condition="children"
     petal:repeat="child children"
     petal:attributes="lang child.lang; xml:lang child.lang"
     petal:content="child.data">Some Dummy Content</p>

Given the fact that XML attributes are not ordered, withing the same tag statements will be executed in the following order: define, condition, repeat, (attributes, content) or (replace).

Aliases

On top of all that, for people who are lazy at typing the following aliases are provided (although I would recommend sticking to the defaults):

  * petal:define     - petal:def, petal:set
  * petal:condition  - petal:if
  * petal:repeat     - petal:for, petal:loop, petal:foreach
  * petal:attributes - petal:att, petal:attr, petal:atts
  * petal:content    - petal:inner
  * petal:replace    - petal:outer

Simple Variable Interpolation

It's the simplest way to insert values in the template:

  Hello, $user.login
  Your real name is $xml:user.real_name!

If $user is a hash reference, then the engine will fetch the value matching the 'login' key. If it's an object, it will try to see if there is a $user->login method, otherwise it will try to fetch $user->{login}.

The xml:user.real_name tells the template engine to XML encode the fetched value, i.e. 'John Smith & Son' will be converted to 'John Smith &amp; Son'.

Alternatively, you could have used $encode:user.real_name to get the same behavior. This is it, you cannot do anything more complex than variable interpolation with that syntax, so that's half a syntax ;-)

XML Declaration Syntax

Includes

  <?petal:include file="path/relative/to/base_dir/template.html"?>

Note that there is no protection against recursive includes. The include is done at runtime rather than compile time, which enables you to construct templates that have a recursive behavior (useful for hierarchical sitemaps for example)...

Variables and Modifiers

  <?petal:var name="document.title"?>

If / Else constructs

Usual stuff:

  <?petal:if name="user.is_birthay"?>
    Happy Birthday, $xml:user.real_name!
  <?else?>
    What?! It's not your birthday?
    Maybe tomorrow...
  <?end?>

You can use petal:condition instead of petal:if, and indeed you can use modifiers:

  <?petal:condition name=":false user.is_birthay"?>
    What?! It's not your birthday?
    Maybe tomorrow...
  <?else?>
    Happy Birthday, $xml:user.real_name!
  <?end?>

Not much else to say!

Loops

Use either petal:for, petal:foreach, petal:loop or petal:repeat. They're all the same thing, which one you use is a matter of taste. Again no surprise:

  <h1>Listing of user logins</h1>
  <ul>
    <?petal:repeat name="system.list_users" as="user"?>
      <li>$user.login : $user.real_name</li>
    <?end?>
  </ul>
  

Variables are scoped inside loops so you don't risk to erase an existing 'user' variable which would be outside the loop. The template engine also provides the following variables for you inside the loop:

  <?petal:repeat blah blah blah...?>
    $__count__    - iteration number, starting at 1
    $__is_first__ - is it the first iteration
    $__is_last__  - is it the last iteration
    $__is_inner__ - is it not the first and not the last iteration
    $__even__     - is the count even
    $__odd__      - is the count odd
  <?end?>

Again these variables are scoped, you can safely nest loops, ifs and includes as much as you like and everything should be fine. And if it's not, it's a bug :-)

EXPORT

None.

BUGS

Probably plenty at the time of this writing. Mail them to me and I'll squash 'em all! Begon jaune a l'attaque!

AUTHOR

Jean-Michel Hiver <jhiver@mkdoc.com>

This module free software and has the same license as Perl itself.

SEE ALSO

  Petal::Hash
  Petal::Hash::Var
  Petal::Parser::XMLWrapper
  Petal::Parser::HTMLWrapper
  Petal::Canonicalizer
  Petal::CodeGenerator
  Petal::Cache::Disk
  Petal::Cache::Memory