Приглашаем посетить
Биология (bio.niv.ru)

Section 3.2.  Text::Template

Previous
Table of Contents
Next

3.2. Text::Template

Mark-Jason Dominus' Text::Template has established itself as the de facto standard templating system for plain text. Its templating language is very simple indeedanything between { and } is evaluated by Perl; everything else is left alone.

It is an object-oriented moduleyou create a template object from a file, filehandle, or string, and then you fill it in:

    use Text::Template;
    my $template = Text::Template->new(TYPE => "FILE",
                                       SOURCE => "email.tmpl");

    my $output = $template->fill_in(  );

So, let's say we've got the following template:

    Dear {$who},
        Thank you for the {$modulename} Perl module, which has saved me
    {$hours} hours of work this year. This would have left me free to play
    { int($hours*2.4) } games of go, which I would have greatly appreciated
    had I not spent the time goofing off on IRC instead.

    Love,
    Simon

We set up our template object and our variables, and then we process the template:

    use Text::Template;
    my $template = Text::Template->new(TYPE => "FILE",
                                       SOURCE => "email.tmpl");

    $who = "Mark";
    $modulename = "Text::Template";
    $hours = 15;
    print $template->fill_in(  );

And the output would look like:

    Dear Mark,
        Thank you for the Text::Template Perl module, which has saved me
    15 hours of work this year. This would have left me free to play
    36 games of go, which I would have greatly appreciated
    had I not spent the time goofing off on IRC instead.

    Love,
    Simon

Notice that the fill-in variables$who, $modulename, and so onare not my variables. When you think about it, this ought to be obviousthe my variables are not in Text::Template's scope, and therefore it wouldn't be able to see them. This is a bit unpleasant: Text::Template has access to your package variables, and you have to do a bit more work if you want to avoid giving use strict a fit.

Text::Template has two solutions to this. The first is pretty simplejust move the fill-in variables into a completely different package:

    use Text::Template;
    my $template = Text::Template->new(TYPE => "FILE",
                                       SOURCE => "email.tmpl");

    $Temp::who = "Mark";
    $Temp::modulename = "Text::Template";
    $Temp::hours = 15;
    print $template->fill_in(PACKAGE => "Temp");

That's slightly better, but it still doesn't please people for whom global variables are pure evil. If that's you, you can get around the problem by passing in a portable symbol tablethat is, a hash:

    use Text::Template;
    my $template = Text::Template->new(TYPE => "FILE",
                                       SOURCE => "email.tmpl");

    print $template->fill_in(HASH => {
        who => "Mark",
        modulename => "Text::Template",
        hours => 15
    });

3.2.1. Loops, Arrays, and Hashes

So much for simple templates. Because Text::Template evaluates the code in braces as honest-to-goodness Perl code, we can do a whole lot more with templates. Let's suppose we're invoicing for some design work:

    $client = "Acme Motorhomes and Eugenics Ltd.";
    %jobs =
       ("Designing the new logo" => 450.00,
        "Letterheads" => 300.00,
        "Web site redesign"       => 900.00,
        "Miscellaneous Expenses" => 33.75
       );

We can create a template to do the work for usthe invoicing work, that is, not the design work:

    {my $total=0; ''}
    To {$client}:

    Thank you for consulting the services of Fungly Foobar Design
    Associates. Here is our invoice in accordance with the work we have
    carried out for you:

    {
      while (my ($work, $price) = each %jobs) {
          $OUT .= $work . (" " x (50 - length $work)). sprintf("£%6.2f", $price)."\n";
          $total += $price;
      }
    }

    Total                                            {sprintf "£%6.2f",$total}

    Payment terms 30 days.

    Many thanks,
    Fungly Foobar

What's going on here? First, we set up a private variable, $total, in the template and set it to zero. However, since we don't want a 0 appearing at the top of our template, we make sure our code snippet returns '' so it adds nothing to the output. This is a handy trick.

Next we want to loop over the jobs hash. Adding each price to the total is simple enough, but we also want to add a line to the template for each job. What we'd like to say is something like this:

    {
      while (my ($work, $price) = each %jobs) {
    }

    {$work}                                                  £{$price}

    {
          $total += $price;
      }
    }

However, Text::Template doesn't work like that: each snippet of code must be an independent, syntactically correct piece of Perl. So how do we write multiple lines to the template? This is where the magical $OUT variable comes in. If you use $OUT in your template, that's taken as the output from the code snippet. We can append to this variable each time we go through the loop, and it'll all be filled into the template at the end.

3.2.2. Security and Error Checking

One of the advantages of templating is that you can delegate the non-programming bits of your applicationdesign of HTML pages, wording of form letters, and so onto people who aren't necessarily programmers. One of the disadvantages with powerful templating systems like Text::Template is that it only takes one joker to discover { system("rm -rf /") } and one or both of you is out of a job. Clearly there needs to be a way to secure your templates against this sort of abuse.

Text::Template offers two ways to protect yourself from this kind of coworker, um, I mean abuse. The first is through Perl's ordinary tainting mechanism. In taint mode, Perl will refuse to run templates from external files. This protects you from people meddling with the template files, but only because you can't use template files at all any more; you must specify templates as strings instead.

If you can actually trust the files in the filesystem, then you'll need to tell Text::Template to untaint the file data; this is done with the UNTAINT option:

    my $template = new Text::Template (TYPE => "FILE",
                                       UNTAINT => 1,
                                       SOURCE => $filename);

Now you will be able to use the template in $filename, if $filename itself has passed taint checks.

The second mechanism is much more fine-grained; the SAFE option allows you to specify a Safe compartment in which to run the code snippets:

    my $compartment = new Safe; # Default set of operations is pretty safe
    $text = $template->fill_in(SAFE => $compartment);

If you're really concerned about security, you'll want to do more tweaking than just using the default set of restricted operations.

What if things go wrong in other ways? You don't want your application to die if the code snippets contain invalid Perl, or throw a divide-by-zero error. While Text::Template traps eval errors by default, you may find yourself wanting more control of error handling. This is where the BROKEN option comes in.

The BROKEN option allows you to supply a subroutine reference to execute when a code snippet produces a syntax error or fails in any other way. Without BROKEN, you get a default error message inserted into your output:

    Dear Program fragment delivered error ''syntax error at template line 1'',

By specifying a BROKEN subroutine, you get more control over what is inserted into the output. In many cases, the only sensible thing to do if your template is broken would be to abort processing of the template altogether. You can do this by returning undef from your BROKEN routine, and Text::Template will return as much output as it was able to build up.

Of course, you now need to be able to tell whether the template completed successfully or whether it was aborted by a BROKEN routine. The way to do this is to use the callback argument BROKEN_ARG. If you pass a BROKEN_ARG to your template constructor, it will be passed into your BROKEN callback.[*] This allows us to do something like this:

[*] Allowing a user-defined argument is a great way to make a callback extremely extensible.

    my $succeeded = 1;

    $template->fill_in(BROKEN => \&broken_sub, BROKEN_ARG => \$succeeded);

    if (!$suceeded) {
        die "Template failed to fill in...";
    }

    sub broken_sub {
        my %params = @_;
        ${$params{arg}} = 0;
        undef;
    }

As you can see, the callback is called with a hash; the argument specified by BROKEN_ARG is the arg element of the hash. In this case, that's a reference to the $succeeded flag; we dereference the reference and set the flag to zero, indicating an error, before returning undef to abort processing.

In case you feel you can make use of the broken template, Text::Template supplies the code snippet as the text element of the hash; I haven't been able to think of anything sensible to do with this yet. To assist with error reporting, the other entries in the hash are line, the line number in the template where the error occurred, and error, the value of $@ indicating the error.

3.2.3. Text::Template Tricks

Using { and } to delimit code is fine for most uses of Text::Templatewhen you're generating form letters or emails, for instance. But what if you're generating text that makes heavy use of { and }HTML pages including JavaScript, for example, or TEX code for typesetting?

One solution is to escape the braces that you don't want to be processed as Perl snippets with backslashes:

    if (browser =  = "Opera") \{
     ...
    \}

However, as one user pointed out, if you're generating TeX, which attaches meaning to backslashes and braces, you're entering a world of pain:

    \\textit\{ {$title} \} \\dotfill \\textbf\{ \\${$cost} \}

A much nicer solution would be to specify alternate delimiters, and get rid of the backslash escaping:

    \textit{ [[[ $title ]]] } \dotfill \textbf{ [[[ $cost ]]] }

Much clearer!

To do this with Text::Template, use the DELIMITERS option on either the constructor or the fill_in method:

    print $template->fill_in(DELIMITERS => [ '[[[', ']]]' ]);

This actually runs faster than the default because it doesn't do any special backslash processing, but needless to say, you have to ensure that your delimiters do not appear in the literal text of your template.

Mark suggests a different trick if this isn't appropriate: use Perl's built-in quoting operators to escape the braces. If we have a program fragment { q{ Hello } }, this returns the string "Hello" and inserts it into the template output. So another way to get literal text without escaping the braces is simply to add more braces!

    { q{

     if (browser =  = "Opera") { ... }

    } }

Another problem is that your fingers fall off from typing:

    my $template = new Text::Template(...);
    $template->fill_in(  );

all the time. The object-oriented style is perfect when you have a template that you need to fill in hundreds of timesa form letter, for instancebut not so great if you're just filling it in once. For these cases, Text::Template can export a subroutine, fill_in_file. This does the preparation and filling in all in one go:

    use Text::Template qw(fill_in_file);

    print fill_in_file("email.tmpl", PACKAGE => "Q", ...);

Note that you do have to import this function specifically.

    Previous
    Table of Contents
    Next