HTML::Processor - HTML template processor
-perl
use HTML::Processor; $tpl = new HTML::Processor; -or with config options- $tpl = new HTML::Processor ({ debug => "Normal", footprint => 1, clean => 0 }); # data %animals = ( mammals => { types => [qw(monkey lion zebra elephant)], count => 120 }, fish => { types => [qw(swordfish shark guppy tuna marlin tunny)], count => 85 }, reptiles => { types => [qw(monitor python crocodile tortoise)], count => 25 }, birds => { types => [qw(eagle pigeon kite crow owl sparrow)], count => 57 } ); # create parent loop object my $animals = $tpl->new_loop("animals"); foreach my $animal_type( keys %animals){ # add data to the parent loop $animals->array("animal_type", $animal_type); $animals->array("count", $animals{$animal_type}{ count }); # create new nested loop object 'keyed' on # the parent via $animal_type my $types = $tpl->new_loop("types", $animal_type); foreach my $type ( @{ $animals{$animal_type}{types} }){ # populate each 'child' loop $types->array("type", $type); } } # set variables $tpl->variable("what", "ANIMALS"); $tpl->variable("count", 2); # process and print parsed template print $tpl->process("templates/animals.html");
-html
<html> <head> <title>Sample</title> </head> <body> [TPL variable='what']:<br> <table width="200"> [TPL LOOP name='animals'] <tr> <td>[TPL array='animal_type'] [[TPL array='count']]</td> </tr> <tr> <td align="right"> [TPL LOOP name='types'] [TPL array='type']<br> [TPL LOOP END] </td> </tr> [TPL LOOP END] </table> <br><br> [TPL IF count == '2'] count is 2 [TPL ELSE] count is not 2 [TPL ENDIF] <br><br> [TPL include='footer.inc']
-output
<!--- TEMPLATE: templates/animals.html ---> <html> <head> <title>Sample</title> </head> <body> ANIMALS:<br> <table width="200"> <tr> <td>mammals [120]</td> </tr> <tr> <td align="right"> monkey<br> lion<br> zebra<br> elephant<br> </td> </tr> <tr> <td>fish [85]</td> </tr> <tr> <td align="right"> swordfish<br> shark<br> guppy<br> tuna<br> marlin<br> tunny<br> </td> </tr> <tr> <td>birds [57]</td> </tr> <tr> <td align="right"> eagle<br> pigeon<br> kite<br> crow<br> owl<br> sparrow<br> </td> </tr> <tr> <td>reptiles [25]</td> </tr> <tr> <td align="right"> monitor<br> python<br> crocodile<br> tortoise<br> </td> </tr> </table> <br><br> count is 2 <br><br> <!--- INCLUDED: templates/footer.inc ---> <br> COMMON FOOTER </body> </html>
The Processor.pm module is designed to remove html from perl scripts without putting too much Perl into the html. The syntax (configurable) is somewhat verbose in order to not scare off html coders, while retaining some Perl logic and functionality. It has a fairly basic set of methods and is not as heavy duty as some of the other Template parsers out there but manages to cover most of the essential html processing operations.
Documentation Syntax
As the module deals with PERL CODE, HTML CODE and OUTPUT the documentation will indicate these as separate blocks:
use HTML::Processor; $tpl = new HTML::Processor; # with defaults -or- $tpl = new HTML::Processor({ debuglevel => 'Verbose', footprint => 0, clean => 0, syntax_pre => '\[% ', syntax_post => ' %\]' });
When creating a new object some of the module defaults may be overridden by passing a hash of options to the constructor, these include:
debuglevel
values: [Off|Fatal|Normal|Verbose] # case sensitive Default: Off Actions: Off => no debug info is displayed Fatal => only fatal errors are displayed Normal => basic processing info is displayed Verbose => verbose info about the processing stage ** Debug info is appended to the output
footprint
Values: [1|0] Default: 0 Actions: 1 => leave an html comment describing the location and name of the primary template at the start of the output: <!-- TEMPLATE: templates/animals.html ---> and for each included file: <!-- TEMPLATE BEGIN INCLUDE templates/footer.html --> -- included file data <!-- TEMPLATE END INCLUDE: templates/footer.html --> 0 => don't
3 clean
Values: [1|0] Default: 1 Actions: 1 => remove blank lines and leading whitespace from template output - reduces html file size for efficiency but source is no longer 'pretty' 0 => don't
3 syntax_pre
Values: pretty much anything BUT escape special chars Default: '\[TPL ' # note the space Actions: Sets the opening syntax for template tags, may conform to a variety of existing styles eg: '<\% ' asp style '<\? ' php style '\[% ' Template-Toolkit style Watch out for spaces and charaters which have meaning within regular expressions
3 syntax_post
Values: as above Default: '\]' Actions: As above
Default Template Syntax examples
[TPL variable='varname'] [TPL LOOP name='loop_name'] [TPL array='loop_item'] [TPL LOOP END]
Alternative syntax examples
[% variable='varname' %] [% LOOP BEGIN name='loop_name' %] [% array='loop_item' %] [% LOOP END %] <? variable='varname' ?> <? LOOP BEGIN name='loop_name' ?> <? array='loop_item' ?> <? LOOP END ?>
When passing variables to object methods and when reflecting the variables in the html, the names must be matchable by: [a-zA-Z0-9_-]
Names must be quoted in single quotes in the HTML/text but double quotes are fine within Perl code.
Bad examples:
$tpl->variable("my~name", $val);
[TPL variable='my~name'] [TPL variable="myname"]
The template data is processed in the following order
(0. SORT - if called) 1. INCLUDE 2. OPTION 3. IF/ELSE 4. VARIABLES 5. LOOPS 5a. LOOP OPTION 5b. LOOP IF/ELSE
Thus, if a LOOP is nested within an OPTION, the OPTION is evaluated and if true, the LOOP is processed.
File fragments or complete html files may be included into any template file. The included files may also contain any Template code blocks or tags. The name and path of the included file may be stored within a template object or interpreted from the include statement in the html.
The root path is relative to the primary template opened in by the
$tpl->process("templates/main_template.html")
method.
adding tempate location paths
Additional paths to locate templates may be added via the:
$tpl->add_path("new/path/relative/to/calling/script");
The effect of adding base paths means that template locations are more flexible and can be moved easily. Also, the paths are used to test alternatives when a template can't be found at a specified location
Syntax:
# specifying includes from Perl $tpl->include("footer", "templates/footer.html");
[TPL include='footer'] # $tpl->include("footer") will be accessed for filename -or- # specifying includes from the template [TPL include='fragments/footer.inc'] # footer.inc is included by means of the direct # reference to its location in the html # the method call: $tpl->include("footer", "templates/footer.html"); # IS NOT REQUIRED IN PERL
To expand on this lets take a script in /cgi-bin which calls
Within 'main_template.html' is the include:
[TPL include='fragments/footer.inc']
Within footer.inc is the include:
[TPL include='../../other_files/foo.htm']
When processed we have:
/cgi-bin/templates/main_template.html # which then includes /cgi-bin/templates/fragments/footer.inc # which then includes /cgi-bin/other_files/foo.htm
The include process keeps track of the locations of all included files relative to the template root.
There are several methods available for debugging the Template object.
1. Debugging Perl HTML::Processor can be used to display program info during runtime to assist in Perl debugging. There are 2 methods for this: A: $tpl->print_die("data_to_print"); "data_to_print" can be anything printable. This method will print an HTML header and then the data and exit the program. B: $tpl->print("data", 'line terminator') this will print an HTML header(once) then data inline as the script is executed. Sometimes usefull for viewing a loop's content eg: while ( @loop_data ) { $tpl->print($_, "<br>"); } This outputs the data with HTML linebreaks for viewing in the browser. Output from both methods will be printed before any template content data. 2. Debugging the Template Object The HTML::Processor object can be debugged in 2 ways: A: Using the Config Option at object creation set 'debuglevel' as describe in 'Object Creation' above. Debug data is appended the the end of object content. eg: $tpl = new HTML::Processor({ debuglevel => 'Verbose' }); B: Viewing the entire content of the object via Data::Dumper $tpl->process("templates/template.html", 1); Pass an additional 'true' parameter to the process method and the object internal data is passed to Data::Dumper and appended to the end of object output content.
Option blocks are chunks of html/text which are displayed only if a condition is true. The internal 'option' hash is tested first and may be set explicitly, if the variable name is not found there, the 'variables' hash is tested.
If both tests return false the block is excluded from temlate output. True is returned for anything but 0 or '' (empty string).
# explicit $hour = (localtime)[2]; $morning = ($hour < 12) ? 1 : 0; $tpl->option("morning", $morning); $tpl->variable("time", scalar localtime) -or- # implicit, re-using an object variable $tpl->variable("morning", scalar localtime) if ((localtime)[2] < 12);
# explicit [TPL OPTION name='morning'] Good morning<br> It is now: [TPL variable='time'] [TPL OPTION END] # implicit [TPL OPTION name='morning'] Good morning<br> It is now: [TPL variable='morning'] [TPL OPTION END]
# implicit and explicit Good morning It is now: Sat Jun 2 09:23:29 2001
The logic may be inverted in an option block eg:
# A $tpl->option("optA", 1); # B $tpl->option("optB", 0);
[TPL OPTION name='optA'] option A data [TPL OPTION END] [TPL OPTION name!='optB'] option B data [TPL OPTION END]
Both of the above evaluate as true and the content will be output in both cases.
If/Else blocks function in a similar way to Perl if(){} elsif(){} else{} constructs and display the contents of the first block for which the expression returns true. Regular object variables are used for the expression. Evaluation operators include:
( == != < <= > >= LIKE NOTLIKE )
Equality and Inequality, for both strings and numerics, is via == and != respectively. HTML::Processor will determine whether the values for comparison are strings or numerics and apply the appropriate operators. Numbers handled are floating points, integers or comma delimited floating points ( eg. 2,999,999.34 ). All other number formats will be treated as strings.
==
!=
The 'LIKE' and 'NOTLIKE' operators use a regular expression for evaluation.
# Assume the 'hour' is 11:00 $tpl->variable("hour", (localtime)[2]);
[TPL IF hour < '12'] Good Morning [TPL ELSIF hour >= '18'] Good Evening [TPL ELSE] Good Afternoon [TPL ENDIF]
Good morning
Loops handle multiple records of a given format. Each loop must be named and can be thought of as similar to a database table with rows and columns. Each 'row' represents a data record and the 'columns' are data fields. Loops may be nested within other loops by means of associating the nested loop to its parent via a 'key' value (one of the data fields). Loops may also contain conditional arguments, If/Else and Option blocks, which evaluate a condition for each loop of a loop. Loop syntax is best explained by example:
# consider this basic data set: my @pets_data = ( "Boots,5,cat", "Rover,3,dog", "Tweety,1,budgie" ); # create a new loop object # and populate the object via the loop # 'array' method $pets = $tpl->new_loop("pets"); foreach my $pet (@pets_data){ my ( $name, $age, $type ) = split (/,/, $pet); $pets->array("name", $name); $pets->array("age", $age); $pets->array("type", $type); }
My Pets:<br> NAME, AGE, TYPE<br> [TPL LOOP name='pets'] [TPL array='name'], [TPL array='age'], [TPL array='type']<br> [TPL LOOP END]
My Pets: NAME, AGE, TYPE Boots, 5, cat Rover, 3, dog Tweety, 1, budgie
Loop options evaluate a data field within the loop, if true the optinal content is displayed. They are the same as normal options but derive their scope from the current loop of the loop which they are in.
Using our above data set:
# consider, again, this basic data set: my @pets_data = ( "Boots,5,cat", "Rover,3,dog", "Tweety,1,budgie" ); # create a new loop object # and populate the object via the loop # 'array' method $pets = $tpl->new_loop("pets"); foreach my $pet (@pets_data){ my ( $name, $age, $type ) = split (/,/, $pet); $pets->array("name", $name); $pets->array("age", $age); $pets->array("type", $type); # set the variable 'cat' as true for testing # optional content $pets->array("cat", 1) if $type eq "cat"; }
My Pets:<br> NAME, AGE, TYPE<br> [TPL LOOP name='pets'] [TPL array='name'], [TPL array='age'], [TPL array='type'] [TPL LOOP OPTION name = 'cat'] 'meeow' [TPL LOOP OPTION END] <br> [TPL LOOP END]
My Pets: NAME, AGE, TYPE Boots, 5, cat 'meeow' Rover, 3, dog Tweety, 1, budgie
Internally, each time the loop is evaluated if variable 'cat' is true the data is displayed.
Same as normal If/Else but, like the Loop Option, tests values within current loop of loop.
My Pets:<br> NAME, AGE, TYPE<br> [TPL LOOP name='pets'] [TPL array='name'], [TPL array='age'], [TPL array='type'] [TPL LOOP IF type = 'cat'] 'meeow' [TPL ELSIF type = 'dog'] 'woof' [TPL ELSIF type = 'bird'] 'tweet' [TPL LOOP ENDIF] <br> [TPL LOOP END]
My Pets: NAME, AGE, TYPE Boots, 5, cat 'meeow' Rover, 3, dog 'woof' Tweety, 1, budgie 'tweet'
The concept of nested loops is similar to a database 'join' where multi-record content for a field is derived from elsewhere based on a 'key'. Nests may be several levels deep provided the syntax is maintained. Consider the following:
%animals = ( mammals => { types => [qw(monkey lion zebra elephant)], count => 120 }, fish => { types => [qw(swordfish shark guppy tuna marlin tunny ray)], count => 85 }, reptiles => { types => [qw(monitor python crocodile tortoise)], count => 25 }, birds => { types => [qw(eagle pigeon owl)], count => 57 } );
Here each type of animal has a variable-length record for the names of its types.
# create parent loop object my $animals = $tpl->new_loop("animals"); foreach my $animal_type( keys %animals){ # add data to the parent loop $animals->array("animal_type", $animal_type); $animals->array("count", $animals{$animal_type}{ count }); # create new nested loop object 'keyed' on # the parent via $animal_type my $types = $tpl->new_loop("types", $animal_type); foreach my $type ( @{ $animals{$animal_type}{types} }){ # populate each 'child' loop $types->array("type", $type); } } # IMPORATNAT SYNTAX # NEW PARENT LOOP OBJECT is created outside the block my $parent = $tpl->new_loop("parent"); foreach ( @parent_data ) { my ( $val1,$val2,$val3,$parent_key ) = @_; $parent->array("val1", $val1); # create a NEW CHILD LOOP OBJECT for each loop # of the parent loop, use a unique value in # the parent data as the 'key' for each # child loop my $child = $tpl->new_loop("child", $parent_key); foreach ( @child_data ) { $child->array("name", $_); } }
<table> [TPL LOOP name='animals'] <tr> <td>[TPL array='animal_type'] [[TPL array='count']]</td> </tr> <tr> <td align="right"> [TPL LOOP name='types'] [TPL array='type']<br> [TPL LOOP END] </td> </tr> [TPL LOOP END] </table>
Sort is a handy method to sort output data by columns. Often output is an HTML table with rows and columns. The data may be derived from several database calls, or other methods, and it becomes difficult to sort the output internally in Perl. This is where the Sort method comes in. Sort is applied to a named LOOP on one of its columns. This is again best illustrated by example (see the supplied perl example file for the full data example - the example makes use of CGI.pm to grab incoming sort instructions)
$tpl->sort($cgi->param('sortby')); print $tpl->process("templates/countries.html");
<table> <tr> <td>name [<a href="scriptname.cgi?sort=countries-name">sort</a>]</td> <td> population [<a href="scriptname.cgi?sort=population-ASC">ASC</a> | <a href="scriptname.cgi?sort=population-DESC">DESC</a>] </td> <td>currency [<a href="scriptname.cgi?sort=currency">sort</a>]</td> <td>capital [<a href="scriptname.cgi?sort=capital">sort</a>]</td> <td>area [<a href="scriptname.cgi?sort=area">sort</a>]</td> </tr> [TPL LOOP name='countries'] <tr> <td>[TPL array='name']</td> <td>[TPL array='population']</td> <td>[TPL array='currency']</td> <td>[TPL array='capital']</td> <td>[TPL array='area'] Km<sup>2</sup></td> </tr> [TPL LOOP END] </table>
The method $tpl->sortby('name_of_column_to_sort') is called just prior the $tpl->process() The value of the column to sort on 'name_of_column_to_sort' is derived from the query string in the HTML link. The HTML link must be of the form:
sort=name_of_array
'name_of_array' is the named array used when populating the loop object.
The direction of the sort will change alternately for each array sorted beginning with Ascending. To specifically start with a descinding sort use:
sort=name_of_array-desc
If the direction is specified IN UPPERCASE as:
sort=name_of_array-ASC -or- sort=name_of_array-DESC
The direction will not alternate for that array during a sort, it will always be in the direction specified.
Sorting where multiple loops exist
If an object contains several named loops and arrays it is advisable to specify both the loop name and array name (in addition to sort direction). In the above example we have 'sort=countries-name' this allows for 2 different loops containing an array of the same name to be sorted accurately. If, for example there were 2 loops with an array called 'name', it is necessary to specify which 'name' array to sort on.
Alternating background colours
It often occurs when outputting tabular data that rows are highlited by alternating HTML background colours. In order to achieve this in conjunction with sorted data, the colours must be arranged after the data sort. The sort method will look for an array within the loop to sort named: 'bgcolor' If it finds this named array, the corresponding colour pair will be applied to the data in alternation.
After a new template object has been created, variables can be added to or returned from the object via:
$tpl->variable("foo", $bar); # set foo = $bar $tpl->variable("foo"); # return the value of foo
Variables may be re-set within the script:
$tpl->variable("value", $value); $tpl->variable("value", sprintf("%.2f", $tpl->variable("value"))); # this is clearly just an illustration - it could have been # done in one go. # another example which comes in handy $tpl->variable("checkbox_a", ($checkbox_val) ? "checked" : ""));
$varval = "hello world"; $tpl->variable("greet", $varval); -or- $tpl->variable("greet", "hello world");
[TPL variable='greet']
hello world
Once a variable is in an object you may want to alter it in some way or add strings to it.
$tpl->concat("varname", value, invert);
If 'invert' is true ie. not(0|'') value will be pre-pended to 'varname'
$tpl->variable("greet", "hello world"); $tpl->concat("greet", " its only me");
You can also invert the concatenation and pre-pend a string
$tpl->variable("message", "rain is wet"); $tpl->concat("message", "roses are red, ", 1);
[TPL variable='greet']<br> [TPL variable='message']
hello world its only me roses are red, rain is wet
Basic mathematical operators may be applied to variables in the form:
'+' addition
'-' subtraction
'*' multiplication
'/' division
$tpl->math("varname", value, operation); # varname = varname operation value -invert the operation $tpl->math("varname", value, operation, invert); # varname = value operation varname
addition and subtraction
$tpl->variable("one", 1); # one = 1 $tpl->variable("two", 2); # two = 2 $tpl->variable("three", 3); # three = 3 $tpl->math("two", 2, "+"); # two = 4 # NOTE - this is the same as: $tpl->variable("two", $tpl->variable("two") + 2); $tpl->math("two", $tpl->variable("one"), "+"); # two = 5 # using originally declared values $tpl->math("two", 2, "-"); # two = 0 # invert the operation $tpl->math("three", 1, "-", 1); # three = -2 # tranlates as: # 1 - $tpl->variable("three") # 1 - 3 # -2
multiplication and division
$tpl->variable("one", 1); # one = 1 $tpl->variable("two", 2); # two = 2 $tpl->variable("three", 3); # three = 3 $tpl->math("two", 3, "*"); # two = 6 $tpl->math("two", 12, "/", 1); # two = 2
$tpl->process("template_path/template_name.html");
This method returns the parsed template data, substituting all template syntax for object data. Typical usage:
sub foo { $tpl->variable("varname", 'varval') ... populate template object with data ... print "Content-type: text/html"; print $tpl->process("template_path/template.html"); # print template and object data print $tpl->process("template_path/template.html", 1); }
Paul Schnell pschnell@touchpowder.com
Thanks to Alexis Orssich and Tom Robinson for ideas and putting the code through its paces.
Copyright (c) 2001 Paul Schnell. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
HTML::Template HTML::Mason Template
3 POD Errors
The following errors were encountered while parsing the POD:
Expected '=item *'
To install HTML::Processor, copy and paste the appropriate command in to your terminal.
cpanm
cpanm HTML::Processor
CPAN shell
perl -MCPAN -e shell install HTML::Processor
For more information on module installation, please visit the detailed CPAN module installation guide.