HTML::CMTemplate.pm - Generate text-based content from templates.


NAME

HTML::CMTemplate.pm - Generate text-based content from templates.


SYNOPSIS

  use HTML::CMTemplate;
  $t = new HTML::CMTemplate( path => [ '/path1', '/longer/path2' ] );
  $t->import_template(
    filename => 'file.html.ctpl', # in the paths above
    packagename => 'theTemplate',
    importrefs => { myvar => 'hello' },
    importclean => { myclean => 'clean!' },
    );
  theTemplate::cleanup_namespace();
  print "Content-type: text/html\n\n";
  print theTemplate::output();
  # Template syntax is described below -- see that section to get the real
  # details on how to use this sucker.


DESCRIPTION

HTML::CMTemplate 0.4.0

A class for generating text-based content from a simple template language. It was inspired by the (as far as I'm concerned, incomplete) HTML::Template module, and was designed to make template output extremely fast by converting a text/html template into a dynamic perl module and then running code from that module. Since the parsing happens only once and the template is converted into Perl code, the output of the template is very fast.

It was designed to work with mod_perl and FastCGI and has been the basis for all of the dynamic content on the Orangatango site (http://www.orangatango.com).

First release (version 0.1) was February 15, 2001 and was very quiet because it was a proprietary version.

As of version 0.2, it is released under the Artistic License. It's a much more feature-rich version as well as being Open Source! For a copy of the Artistic License, see the files that came with your Perl distribution.

The code was developed during my time at Orangatango. It has been released as open source with the blessing of the controlling entities there.


AUTHOR

Chris Monson, shiblon@yahoo.com

DEBUG OUTPUT

  You can coerce the template engine into spitting out debugging
  information for every step of the parsing process.  This behavior can be
  controlled by the following variables:
  $HTML::CMTemplate::DEBUG = 1;  # Do this, and debugging will be turned on
  $HTML::CMTemplate::DEBUG_FILE_NAME = 'filename'; # Defaults to STDERR
  $HTML::CMTemplate::DEBUG_FUNCTION_REF = $ref;
  The debug function reference is used for every debug step.  It is passed
  three parameters: the name of the function being debugged, a string,
  and an array ref.  If the array is non-empty, it
  contains the arguments to a function that is being debugged.
  If the string is non-empty, it contains a message.
  Note that this is of dubious utility.  The debug functions are mostly for
  my own internal use to see where the template parser goes wrong.  They
  do not detect bad template syntax, and they will be of no use to people
  just making use of the template parser.  Really.  My advice is to leave the
  debug parameters alone.  You don't need them.

TEMPLATE SYNTAX

The template syntax that this parser recognizes has a few tags that look a little like php or xml syntax, except that they are different. The tags all start with <?= and end with ?>. If the next character after ?> is a newline, it is also eaten up with the tag, just like in PHP. This gives you very fine grained control over the actual template output, and is especially important when using loops to generate output.

Note that if you want to actually output those symbols in your code, or you want to access them inside of a tag, I have created two global variables that contain those strings: $START_SYM and $END_SYM. So, to print something like <?=hello?> inside of your template, you would do this:

    <?=echo $START_SYM?>hello<?=echo $END_SYM?>

Or, if you use the shortcut (all explained below), you would do this:

    <?=$START_SYM?>hello<?=$END_SYM?>

An explanation follows of the different constructs that the engine uses.

comment

The comment construct is just a single tag. The whole tag is eaten by the template engine and is never seen again. This simply serves as a comment in the template itself.

    <?=comment  This is a comment ?>

echo

The echo construct supports two tags:

    <?=echo --expression-- ?>
    <?=--expression--?>

The second tag is a handy shortcut for the first, and works like its corresponding PHP tag. Both are replaced with the value of the evaluated expression. This is by far the most used construct, since usually the result of an expression simply needs to be inserted into the appropriate place.

Example:

    ...
    <title><?=$document_title?></title>
    ...

This could also be written as

    ...
    <title><?=echo $document_title?></title>
    ...

Both will replace the tag with the contents of the $document_title variable.

But, where does $document_title come from? Going back to the synopsis, if you do something like this in your perl script:

    $t->import_template(
        filename => 'thetemplate.html.ctpl',
        packagename => 'theTemplate',
        );

Then you can set the variable in the newly-created package (There is no need to do a 'use' or a 'require' or anything. The package is created when you call the import_template function and is thereafter available).

You create the variable thus:

    $theTemplate::document_title = "My Document Title";

Then to output the template, you would do something like this:

    print "Content-type: text/html\n";
    print "\n";
    print theTemplate::output();

Note that the import_template function does not import the template again if it detects that the template or any of its includes (see below) have not changed. This is an optimization to reduce needless parsing, since once the template is in memory, you can use it over and over again with new variables by just changing the variable values in the package namespace.

    NOTE: This behavior can be changed by setting the
    $t->{checkmode} variable to $HTML::CMTemplate::CHECK_NONE.

So, if I wanted to output the template again with a new title, I could simply do the following:

    $theTemplate::document_title = "My NEW Document Title";
    print "Content-type: text/html\n";
    print "\n";
    print theTemplate::output();

Note that an import_template was not necessary again, since the template was converted into code and all we wanted to do was change a variable.

Again, if you do call import_template on the same object ($t in the examples) more than once, it will only actually parse the template once, unless you change it on disk in between import_template calls.

if

The if construct supports several tags, some of which are reused in the for construct:

    <?=if --expression-- :?>
    <?=elif --expression-- :?>
    <?=else :?>
    <?=endif?>

These tags do basically what you would expect them to do. Note that none of the expressions require surrounding parentheses. They do require terminating colons, however. Whitespace is not important except between the tag name ('if') and the expression.

So, as an example of how you might do things:

    <?=if $testvar:?>
    TESTVAR set!
    <?=else:?>
    TESTVAR NOT set!
    <?=endif?>

The elif tag works just like an elsif in Perl.

for

The for contruct supports several tags, as well. It works like Python's for loop construct and has a similar syntax. In fact, all of these tags borrowed some of their syntax from Python.

The supported tags are as follows:

    <?=for --varname-- in --list expression-- :?>
    <?=break?>
    <?=continue?>
    <?=else:?>
    <?=endfor?>

These tags, with the exception of the 'else' tag (since it doesn't exist) do what you would expect them to do in Perl. The 'for' and 'else' tags deserve a little extra explanation since they are not real Perl syntax.

The --varname-- is the name of a variable that will be assigned the value of the current list item. The --list expression-- is an expression that evaluates to a real Perl array (NOT an arrayref). Each item in the array will be assigned to --varname-- in order. Here is an example. Assume for the sake of this example that an array of integers 1 thru 10 named '@list' exists in the package's namespace:

    <table>
    <?=comment
        Note that you can use either 'i' or '$i' here.
        They are equivalent in the for tag.  The echo tag
        MUST use '$i' because it is outputting a perl
        expression and is not specially parsed at all.
    ?>
    <?=for i in @list:?>
    <tr><td>Number <?=echo $i?></td></tr>
    <?=else:?>
    <tr><td>Completed normally</td></tr>
    <?=endfor?>
    </table>

Note that you don't have to output HTML. Any kind of text can be output, but HTML is what this was originally designed for.

This will loop on the elements of @list, which contains the numbers 1 thru 10 in order. It will output the following code:

    <table>
    <tr><td>Number 1</td></tr>
    <tr><td>Number 2</td></tr>
    <tr><td>Number 3</td></tr>
    <tr><td>Number 4</td></tr>
    <tr><td>Number 5</td></tr>
    <tr><td>Number 6</td></tr>
    <tr><td>Number 7</td></tr>
    <tr><td>Number 8</td></tr>
    <tr><td>Number 9</td></tr>
    <tr><td>Number 10</td></tr>
    <tr><td>Completed Normally</td></tr>
    </table>

Note the extra table element at the end that says ``Completed Normally''. This is inserted because of the else tag after the for block. Like in Python, the code in the else tag is executed if the for loop is not terminated with a break tag. If the for loop is terminated with a break tag, then the else block will not execute.

The break and continue tags work as you would expect the corresponding 'last' and 'next' keywords to work in Perl.

There are several functions to ease your way in for loops. They are listed here:

    for_list( $depth )
    for_index( $depth )
    for_count( $depth )
    for_is_first( $depth )
    for_is_last( $depth )

The for_list function gives you access to an arrayref of the list over which the loop (or one of its containing loops, if $depth > 0) is iterating.

The for_index function gives you a number from 0 to len-1, depending on where you are in the actual loop.

The for_count function gives you the number of elements over which you are iterating.

The for_is_first function tells you whether this is the first element, and the for_is_last function tells you whether this is the last one.

Note that all of these functions give you the ability to specify a depth. If you have nested 'for' tags and you want to access the index, count, or list of a containing 'for' loop, you can do that by specifying a depth parameter in the function. No depth parameter or a value of 0 indicates that you want the values for the current loop. A value of 1 would indicate that you want the values for the immediately enclosing loop, etc.

Example: Suppose @xlist = (1, 2, 3) and @ylist = (2, 4, 6):

    <?=for x in @xlist:?>
    <?=for y in @ylist:?>
        <?=$x?>,<?=$y?> :: <?=echo for_index(1)?>,<?=echo for_index()?>
    <?=endfor?>
    <?=endfor?>
    prints:
        1,2 :: 0,0
        1,4 :: 0,1
        1,6 :: 0,2
        2,2 :: 1,0
        2,4 :: 1,1
        2,6 :: 1,2
        3,2 :: 2,0
        3,4 :: 2,1
        3,6 :: 2,2

We can also tell if the current element is the first or last. Rather than give an example for that simple case, it is left as an exercise for the reader. A hint, however, is that you should use for_is_first and for_is_last (functions that can also take a depth argument).

As a side note, here, I should mention that tabbing loop and conditional constructs does not work the way that you think it might inside of a template. Since the only thing that is eaten up in a template is the tag itself, not the preceding whitespace, usually you want the loop constructs and other kinds of block constructs to be located all the way to the left side. This will ensure that your spacing is really what you think it should be.

while

The tags this loop uses are as follows:

    <?=while --expression--:?>
    <?=endwhile?>

The while construct is a very simple loop that works like a standard while loop in Perl. This is useful when you are potentially outputting a large loop and don't want to get the entire contents of it into memory. The expression in the while loop works just as you would expect it to. A true value means to keep going, and a false one means to stop.

As with all other block structures, you can put anything you like in the body of a while construct.

Example (assuming you have created appropriate function references):

    <?=while $items_hanging_around->():?>
    <?=echo $get_next_item->():?>
    <?=endwhile?>

def

The def construct is a very powerful little tool. It corresponds loosely to Python's def in that it defines a sort of ``template function'' which can be ``called''. An example will best illustrate this.

By the way, the tags that are used by this construct are the following:

    <?=def --functionname--( --arglist-- ):?>
    <?=enddef?>
    <?=call --functionname--( --arglist-- )?>

Here is that promised example:

    <?=def tempfunc( a, b, c ): ?>
        a = <?=echo $a?>
        b = <?=echo $b?>
        c = <?=echo $c?>
    <?=enddef?>
    <?=call tempfunc( 1, 2, 3 )?>
    <?=call tempfunc( 4, 5, 6 )?>

This will print the following:

        a = 1
        b = 2
        c = 3
        a = 4
        b = 5
        c = 6

I think it's pretty self-explanatory. Note that you can embed any number of recursive constructs inside of not only the def tags, but also if and for tags, along with their corresponding inner tags.

NOTE: No matter where a template subroutine is defined (def tag), the subroutine ends up in the global package scope. All defs are global. Period. This is by design and actually required a large amount of work to do, so don't you go thinking that it's because I'm lazy ;-).

The reasoning behind this is to keep namespace clashes from happening when one template includes another. If the functions are treated differently from other constructs (since Perl treats them differently anyway), namespace collisions can be detected. Additionally, if one module includes two others, each of which include the same module, the functions from that last module are the same. Functions in the global scope keep these from wrongly stomping on each other.

exec

This one is somewhat dangerous, and should be used with great care. It allows you to execute arbitrary Perl code inside of the tag.

    <?=exec
        $a = 1;
        $b = 2;
        $c = $a + $b;
        print STDERR "Debug this output function!";
    ?>

This will set the variables just as you think it will, but it will do it in a somewhat strange scope and you might get a bit confused. Look at the code that is generated (by calling $t->output_perl()) to see exactly what goes on.

Note that the package that is created from this template explicitly declares no strict 'vars', so the exec tag above will actually create global variables in the package's namespace. You can also create 'my' variables, which is really useful inside of loops.

The best uses I have found for this tag are as follows:

    * Creating temporary variables or aliases to complicated variables.
    * Creating 'my' variables inside of loops to improve efficiency.
    * Outputing debug code using print STDERR "stuff" constructs.

Beyond this, I have serious misgivings about the tag. Just be careful. Your code will be inserted as is into the template code. Don't forget semicolons, etc.

inc

This includes another template where the tag is located. It tests for infinite recursion and does not allow it. Also, note that this does NOT take an arbitrary perl expression as a filename. It only takes strings. The filename can be quoted with either single or double quotes.

This parses the file just like any other template, looking for tags. If you don't want the file parsed, use 'rawinc' instead.

rawinc

Just like 'inc', but it doesn't parse the file. Simple.

IMPORTED UTILITIES

You have access to several functions. Most of the time you will only use a couple of them, but there are several there for the sake of completeness and sanity.

output()

This is used to output the code given the current namespace. Simple. It returns a string.

import_hashref()

This is extremely useful for importing the variables from another namespace. I routinely do the following:

    use CMCONFIG;
     ...
    theTemplate::import_hashref( \%CMCONFIG:: );

You can also set up your own hashref of variables. This is useful for getting form elements from CGI stuff:

    theTemplate::import_hashref( \%FORM );
    theTemplate::import_hashref( { myvar => 'value' } );

That would set all of the FORM data to be global variables (PHP style) in the package, and it would additionally set $myvar to be 'value'.

Note that you can pass an extra parameter (1 or 0) to indicate that you want the variables imported into the 'clean' namespace. More on this later.

cleanup_namespace()

This deletes all variables from the package's namespace except those that are designated 'clean'. Clean variables are the functions that are automatically defined and the globals that are used by the package before anything is done to it. They are also variables that have been imported and marked 'clean'.

This function is really important, especially in cases where the template is being generated with fastcgi or mod_perl, since the modules will have their variables maintained across page loads. That means that the previous user's password (for example) could be available in the page. Bad, bad things happen at that point.

So, it is useful to call cleanup_namespace before using the template. It is also useful to import things like system-wide configuration parameters into the clean namespace, since these aren't sensitive to change and can take a little extra time to import.

add_clean_names()

If you import a ton of variables and want to mark some of them clean, use this function.

FUNCTIONS

new( %args )

Creates an instance of the HTML::CMTemplate class. Potentially takes several parameters.

    parent: Template which immediately "owns" this template.  Should only be
        used internally.
    root: Template at the top of the tree.  Also internal use only.
    NOTE: NEVER use parent or root.  NEVER do it!  Don't!  Jerk.
    path: An array ref of file paths.  These paths will be searched when
        non-absolute template filenames are given.  Note that if a string
        is passed in instead of an arrayref, it will be treated as a single
        file path, not as a ':' or ';' delimited list of paths.  If it has
        illegal characters, the search will simply not work.
        NOTE that you do NOT need to include '.' explicitly.  It will always
        be checked FIRST before the listed directories.
    nocwd: 1 or 0.  Tells the path parser to leave cwd out of it.

get_includes()

Returns an arrayref of included files.

open_file( $filename, $path )

Takes one or two parameters. This function looks for the indicated file and parses it into an internal structure. Once this is done, it is capable of outputting perl code or importing an indicated package with said code in the output function. The file is looked for in the path specified during template creation.

Note that even if a relative filename is passed in (relative to any part of the path, including '.') the filename will be converted to an absolute path internally. This is the way that infinite recursion is detected and templates are never parsed more than once.

import_template( %args )

Open the file, parse it, and import the indicated variables. This will leave the client with an imported package that can be used to generate output. This does not actually call the output function! That would be too confining.

The reason that this function exists is that it is by far the most used operation. The most frequent need when dealing with templates is to open them up and import them into a namespace, including some predefined variables that are well known. This allows one function call to replace several.

The arguments to this function are supposed to be named and are as follows:

import_package( $packagename, $warn )

Once a file has been opened and parsed, the code to generate the template can be imported into a package of the specified name. In order to really make the tempalate useful, the generated code should be imported into a package so that it can have its own namespace. Mind you, the template can actually be imported into the current package, but this is not suggested or encouraged since it is generating code that might do nasty things to your global variables.

The $warn parameter turns warnings on or off in the generated module. Leave it out or set to zero for default behavior (off).

output_perl_code( %args )

Accepts a depth argument. This just outputs the code without any surrounding context and no helper functions, including the functions defined in the template itself. Just the code. Just the code. Remember that: just the code. If you can't figure out what ``just the code'' means, call this function and the output_perl function and do a diff. It will become immediately obvious to you. You may want to consider turning off detection of whitespace in that diff....

output_perl( %args )

This function outputs perl code that will generate the template output. The code that is generated turns off strict 'vars'.

The allowed parameters are:

    packagename (required)
    depth
    warn

If the depth is specified, then the code will indent itself that many times at the top level. The indentation amount is four spaces by default and cannot currently be changed.

If warn is specified, the warn variable ($^W) in the generated module is set to that value. Default is 0 (off).

This always requires a packagename. The packagename is used to generate the surrounding context for the code output. If you don't want the package, the surrounding context, and the function definitions, you are really looking for output_perl_code, which just outputs the code definition for this template without any surrounding context.


FORMAL GRAMMAR DEFINITION

    template :==
        text block template
        | NULL
    text :==
        ANY_CHAR_LITERAL
        | NULL
    block :==
        if_block
        | for_block
        | while_block
        | def_block
        | comment_tag
        | echo_tag
        | call_tag
        | inc_tag
        | rawinc_tag
        | exec_tag
        | break_tag
        | continue_tag
        | NULL
    if_block :==
        if_tag template [ elif_tag template ]* [ else_tag template ]? endif_tag
    for_block :==
        for_tag template [ else_tag template ]? endfor_tag
    while_block :==
        while_tag template endwhile_tag
    def_block :==
        def_tag template enddef_tag
    comment_tag :==
        START_SYMBOL OP_COMMENT WS TEXT WS? end_symbol
    echo_tag :==
        START_SYMBOL OP_ECHO WS simple_expr WS? end_symbol
    if_tag :==
        START_SYMBOL OP_IF WS simple_expr WS? end_symbol_block
    elif_tag :==
        START_SYMBOL OP_ELIF WS simple_expr WS? end_symbol_block
    else_tag :==
        START_SYMBOL OP_ELSE WS? end_symbol_block
    endif_tag :==
        START_SYMBOL OP_ENDIF WS? end_symbol
    for_tag :==
        START_SYMBOL OP_FOR WS var_name WS OP_IN WS simple_expr WS? end_symbol_block
    endfor_tag :==
        START_SYMBOL OP_ENDFOR WS? end_symbol
    while_tag :==
        START_SYMBOL OP_WHILE WS simple_expr WS? end_symbol_block
    endwhile_tag :==
        START_SYMBOL OP_ENDWHILE WS? end_symbol
    break_tag :==
        START_SYMBOL OP_BREAK WS? end_symbol
    continue_tag :==
        START_SYMBOL OP_CONTINUE WS? end_symbol
    def_tag :==
        START_SYMBOL OP_DEF WS def_name def_param_expression WS? end_symbol_block
    enddef_tag :==
        START_SYMBOL OP_ENDDEF WS? end_symbol
    call_tag :==
        START_SYMBOL OP_CALL WS def_name call_param_expression WS? end_symbol
    inc_tag :==
        START_SYMBOL OP_INC WS QUOTE? FILENAME QUOTE? WS? end_symbol
    rawinc_tag :==
        START_SYMBOL OP_INC WS QUOTE? FILENAME QUOTE? WS? end_symbol
    exec_tag :==
        START_SYMBOL OP_EXEC WS expression WS? end_symbol
    var_name :==
        OP_DOLLAR? CHAR_NAME_LITERAL
    def_name :==
        CHAR_NAME_LITERAL
    def_param_expression :==
        OP_OPEN_PAREN WS? def_param_list OP_CLOSE_PAREN
    def_param_list :==
        CHAR_NAME_LITERAL WS? [ OP_LIST_SEP WS? CHAR_NAME_LITERAL WS? ]*
    call_param_expression :==
        OP_OPEN_PAREN WS? call_param_list OP_CLOSE_PAREN
    call_param_list :==
        simple_expr WS? [ OP_LIST_SEP WS? simple_expr WS? ]*
    simple_expr :==
        SINGLE_STATEMENT_EXPR
    expression :==
        MULTI_STATEMENT_EXPR
    end_symbol_block :==
        OP_BLOCK_TERMINAL WS? end_symbol
    end_symbol :==
        END_SYM_TEXT END_SYM_WS?
    WS :== \s+
    END_SYM_WS :== \012\015|\012|\015
    CHAR_NAME_LITERAL :== [a-zA-Z_][a-zA-z0-9_]*
    QUOTE :== ["']
    START_SYMBOL :== '<?='
    END_SYM_TEXT :== '?>'
    OP_BLOCK_TERMINAL :== ':'
    OP_LIST_SEP :== ','
    OP_DOLLAR :== '$'
    OP_OPEN_PAREN :== '('
    OP_CLOSE_PAREN :== ')'
    OP_COMMENT :== 'comment'
    OP_ECHO :== 'echo'
    OP_IF :== 'if'
    OP_ELIF :== 'elif'
    OP_ELSE :== 'else'
    OP_ENDIF :== 'endif'
    OP_FOR :== 'for'
    OP_IN :== 'in'
    OP_ENDFOR :== 'endfor'
    OP_WHILE :== 'while'
    OP_ENDWHILE :== 'endwhile'
    OP_BREAK :== 'break'
    OP_CONTINUE :== 'continue'
    OP_DEF :== 'def'
    OP_ENDDEF :== 'enddef'
    OP_CALL :== 'call'
    OP_INC :== 'inc'
    OP_EXEC :== 'exec'
    SINGLE_STATEMENT_EXPR :==
        Any valid perl expression that is a single statement
        and evaluates to a single return value.  For example, the internals
        of an 'if' statement should evaluate to something akin to a boolean
        and would have the same rules as a normal 'if' statement.
    MULTI_STATEMENT_EXPR :==
        Any valid perl expression that may or may not be multiple expressions.
        This basically leaves the door wide open for a generic eval.
    FILENAME :==
        This is NOT a perl expression, but an actual filename.  The whitespace
        on either end is stripped out.  No quoting is currently allowed, so
        take care to not use filenames with spaces for now.
        Example: <?=inc file.ctpl ?>
    TEXT :==
        This is just text.  No parsing is done.  Just text.

 HTML::CMTemplate.pm - Generate text-based content from templates.