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

NAME

HTML::Processor - HTML template processor

SYNOPSIS

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

DESCRIPTION

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:

-perl

-html

-output

Module Defaults

Object Construction

    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 ?>

Syntax

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:

-perl

        $tpl->variable("my~name", $val);

-html

        [TPL variable='my~name']
        [TPL variable="myname"]

Precedence

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.

Including external files

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:

-perl

    # specifying includes from Perl
        $tpl->include("footer", "templates/footer.html");

-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

        $tpl->process("templates/main_template.html")

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.

Debugging

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

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

Syntax:

-perl

        # 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);

-html

        # 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]

-output

        # 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:

-perl

    # A
    $tpl->option("optA", 1);
    # B
    $tpl->option("optB", 0);
    

-html

    [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

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.

Syntax:

-perl

    # Assume the 'hour' is 11:00
        $tpl->variable("hour", (localtime)[2]);

-html

        [TPL IF hour < '12']
           Good Morning
        [TPL ELSIF hour >= '18']
           Good Evening
        [TPL ELSE]
           Good Afternoon
        [TPL ENDIF]

-output

        Good morning

Loops

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:

Syntax:

-perl

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

-html

    My Pets:<br>
    NAME, AGE, TYPE<br>
    [TPL LOOP name='pets']
        [TPL array='name'], [TPL array='age'], [TPL array='type']<br>
    [TPL LOOP END]
    

-output

    My Pets:
    NAME, AGE, TYPE
    Boots, 5, cat
    Rover, 3, dog
    Tweety, 1, budgie

Loop Options

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:

-perl

    # 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";
    }

-html

    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]

-output

    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.

Loop If/Else

Same as normal If/Else but, like the Loop Option, tests values within current loop of loop.

-html

    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]

-output

    My Pets:
    NAME, AGE, TYPE
    Boots, 5, cat 'meeow'
    Rover, 3, dog 'woof'
    Tweety, 1, budgie 'tweet'

Nested Loops

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:

-perl

    %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", $_);
        }
    }

-html

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

Sorting

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)

-perl

    $tpl->sort($cgi->param('sortby'));
    print $tpl->process("templates/countries.html");

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

Processing Variables

simple variable get and set

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" : ""));

-perl

        $varval = "hello world";
        $tpl->variable("greet", $varval);
        -or-
        $tpl->variable("greet", "hello world");

-html

        [TPL variable='greet']

-output

        hello world

variable concatenation

Once a variable is in an object you may want to alter it in some way or add strings to it.

Syntax:

        $tpl->concat("varname", value, invert);

If 'invert' is true ie. not(0|'') value will be pre-pended to 'varname'

-perl

        $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);

-html

        [TPL variable='greet']<br>
        [TPL variable='message']

-output

        hello world its only me
        roses are red, rain is wet

variable math

Basic mathematical operators may be applied to variables in the form:

  • '+' addition

  • '-' subtraction

  • '*' multiplication

  • '/' division

Syntax:

        $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

Processing the Template

$tpl->process("template_path/template_name.html");

This method returns the parsed template data, substituting all template syntax for object data. Typical usage:

-perl

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

AUTHOR

Paul Schnell pschnell@touchpowder.com

Thanks to Alexis Orssich and Tom Robinson for ideas and putting the code through its paces.

COPYRIGHT

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.

SEE ALSO

HTML::Template HTML::Mason Template

3 POD Errors

The following errors were encountered while parsing the POD:

Around line 1347:

Expected '=item *'

Around line 1358:

Expected '=item *'

Around line 1371:

Expected '=item *'