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

NAME

Template::Replace - PurePerl Push-Style Templating Module

VERSION

Version 0.04

SYNOPSIS

Template::Replace is a rather basic, zero dependency "push style" templating module with a simple API:

    use Template::Replace;

    my $tmpl = Template::Replace->new({
        path      => '/shared/httpd/tmpl', # templates path (required)
        filename  => 'test_template.html', # load template (optional)
        filter    => { default => 'xml' }, # XML escape data on default (opt.)
    });

    $tmpl->parse($str);                    # load template from string
    $tmpl->load($filename);                # load template from file
    print $tmpl->replace($data);           # replace placeholders by data

Example template file (standard delimiters are suitable for HTML; this example assumes that the default filter for variables is set to 'xml'):

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>($ html_title_var $)</title>
            <!--{ head.tmpl }-->     <!--# Include head template           #-->
        </head>
        <body>
            <!--#
                This is excluded from output (a "template comment").
                Could also be used to temporarily exclude portions
                of the template.
            #-->
            <!--{ header.tmpl }-->   <!--# Include header template         #-->
            <div class="Content">

            <h1>($ content_title_var $)</h1>

            ($ content_var | none $) <!--# Variable has HTML, don't filter #-->

            <!--? Comments ?-->      <!--# Test for section data           #-->
            <h5>Comments</h5>
            <!--( Comments )-->      <!--# Start of section 'Comments'     #-->
            <div class="comment">
                <h6><a href="($ url|url $)">($ name $):</a></h6>
                ($ comment $)
            </div>
            <!--( /Comments )-->   <!--# End of section                   #-->
            <!--? /Comments ?-->   <!--# End of test                      #-->
            <!--? !Comments ?-->   <!--# Test for missing section data    #-->
            <p>No comments yet!</p>
            <!--? /!Comments ?-->  <!--# End of test                      #-->

            </div>
            <!--{ footer.tmpl }--> <!--# Include footer template          #-->
        </body>
    </html>

Data example:

    my $data = {
        html_title_var    => 'Template::Replace: An Example',
        content_title_var => 'An Example',
        content_var       => $html_content,
        Comments          => [
            {
                url       => $author[0]->{url},
                name      => $author[0]->{name},
                comment   => $author[0]->{comment},
            },
            {
                url       => $author[1]->{url},
                name      => $author[1]->{name},
                comment   => $author[1]->{comment},
            },
        ],
        NotRepeating      => { content => 'This is simple content.' },
    };

EXPORT

Nothing is exported. This module provides an object oriented interface.

DEPENDENCIES

Requires Perl 5.8 (best served above 5.8.2), Carp, Encode and File::Spec::Functions (Perl 5.8 core modules).

This is a single file module that can be run without prior installation.

DESCRIPTION

# # TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #

Beware: This module's code is neither elegant nor ingenious! Au contraire - it's ugly, it's a mess ... and it is doing what I wanted it to do. (Okay, not as bad as stated, but don't complain when looking at it ;-)

METHODS

new()

    my $tmpl = Template::Replace->new({
        path      => [ 'path1', 'path2' ],
        filename  => 'template_filename',
        delimiter => {
            include => [ '<!--{', '}-->' ],
            section => [ '<!--(', ')-->' ],
            var     => [ '($'   ,   '$)' ],
            test    => [ '<!--?', '?-->' ],
            comment => [ '<!--#', '#-->' ],
        },
        filter => {
            default => 'xml',
            special => \&my_special_filter_function,
        },
    });
 

Path can be a single string or an array reference of multiple strings; given paths have to exist, and template files (and includes) can only be loaded from these paths (for security reasons)!

Filename is an optional string; the template is loaded on object creation if given.

Single delimiter pairs can be given (default delimiters shown), but they have to be array references with exactly 2 strings; the delimiters are fixed after object creation, so this is the only chance to change them!

Filters can be re-declared and custom filters attached; the default filter is a pass-through filter; filters can be changed anytime before invoking $tmpl->replace().

All options are optional, but at least one existing path has to be given to load a template from file (either with filename on object creation or later with the load method).

parse()

    my $template_ref = $tmpl->parse($str);

Parses a template from $str. Stores the template structure reference in the $tmpl object and returns it. No includes, because they are handled only on reading from file (use $tmpl->load() instead)!

load()

    my $template_ref = $tmpl->load($filename);

Loads a template from file $filename and parses it. Stores the template structure reference in the $tmpl object and returns it.

replace()

    my $txt = $tmpl->replace($data);

Replaces $data in $tmpl and returns the resulting string (text).

has()

    my $result = $tmpl->has($access_str);

Tries to access a template element and returns the usage count of a section (0 or 1 for now) or of a variable in the template structure. The access string has the following form:

    'RootSection' (or '/RootSection')
    'RootSection/Subsection'
    'RootVariable'
    'RootSection/SectionVariable'
    'RootSection/Subsection/SubsectionVariable'
    etc.

DIAGNOSTICS

# # TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #

FUNCTIONAL DESCRIPTION

To better understand some of the peculiarities of Template::Replace, here is how it works:

  1. When using load() (implicitly by providing a filename with new() or explicitly), the private method _read_file() is called, which does some path and filename cleanup (for security reasons) and slurps the template file.

    File inclusion is only done in this step! There is a mechanism to prevent recursive file inclusion. The inclusion in _read_file() is done because it makes the other steps much simpler.

    This step results in a single string containing the complete template with all inclusions.

  2. Then parse() (implicitely called by load()) is used to process the template string. First the private method _slice_str() creates a linear list of slices (using regular expressions based on the given delimiters) that is then processed by _parse_slices() to build the template structure of sections (and subsections), tests, variables and text fragments ("strings").

    Custom delimiters can only be defined on object creation, because they are used beforehand when slicing (and parsing) the template.

  3. The rendering of the final output is done with replace() (which calls _replace()), replacing the various template parts by the contents of a corresponding data structure. Output filters can be applied to the replacement of variables. This is a dynamic process, so that output filters can be changed after parsing a template. (Oh, and you can do this over and over again with the loaded template and changing data ...)

RATIONALE

Yet another template module ... oh no ... why? For the fun of it ;-)

No, not really. There were other considerations that lead me to write Yet Another Perl Template Module (I won't do it again, I promise). I had the following requirements when I started searching CPAN for template modules:

  • no programming in the template (no DSL, no Perl)

  • replacement oriented

  • implicit looping

  • nested sections

  • scoped variables (with access to other scopes)

  • output filters for variables

  • file includes

  • strict include path(s)

  • template defines overall structure of output

  • template testing in the script (what is defined in the template?)

  • data testing in the template (what data is defined?)

  • configurable delimiters

  • template items should not interfere with target syntax (i.e. HTML)

  • independent of target syntax/language

  • no installation/compilation required

  • only Perl 5.8 core dependencies

Okay, with data testing in the template the line to programming or "business logic" is slightly blurred, but it is necessary to define alternate parts for a template according to data availability (i.e. comments/no comments).

With the ability to query the template (and the structure of its replacement parts) there is a greater chance to de-couple the structures of template and program (the program can prepare the data structures used to fill the template according to the specific template used). And programming can be more efficient (avoiding expensive processing if the result isn't used in the template).

The requirement to have no DSL or Perl in the template has the side effect that the templating "syntax" can be easily re-implemented in other programming languages and that the whole system can be switched without effecting the templates.

None of those properties are new or unseen, but I found no module that would satisfy all of my requirements (and then there's a potential problem with UTF-8 and taint mode that bit me again and again before, so I wanted to have full control over the source so that I can intervene when necessary). And at least many APIs where much too complicated or bloated for my liking.

Other programmer's brainchilds ...

If you want to use some really cool template engines, or if you think you are creating The Next Big Thing, and if you can afford module installation or compilation, and if you are not afraid of module dependencies, then look out for Template::Toolkit or HTML::Mason and all the other great (or big - depends on your point of view :-)) template modules on CPAN. Or stay with simpler modules like Template::Tiny etc.

Here is some reading for you:

Have fun ...

AUTHOR

Christian Augustin, <mail at caugustin.de>

BUGS

Please report any bugs or feature requests to bug-template-replace at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Template-Replace. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Template::Replace

You can also look for information at:

ACKNOWLEDGEMENTS

Some years ago I stumbled over some ingeniously simple Perl templating code, consisting of only two rather short and clever functions, that could do some of the things I used as requirements for Template::Replace (it was replace oriented, had some sort of nested sections and did implicit looping). But the code had some annoying properties and quirks, the data structures for replacement were all but intuitive, and the lack of "global" variables or an access mechanism made the use a slight pain in the ass (and no documentation). What I learned at least was: Don't be too clever ...

But the basic idea resonated and inspired the creation of Template::Replace. As did many other modules I found while I searched the CPAN. I can't give specific achnowledgements, but if you find some ideas in here you saw in one of the other modules, they probably came from them.

LICENSE AND COPYRIGHT

Copyright 2012 Christian Augustin (caugustin.de).

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

See http://dev.perl.org/licenses/ for more information.