NAME
Piffle::Template - Perlish templating language
SYNOPSIS
use Piffle::Template;
use Piffle::Template qw{template_to_perl expand_template};
# OO syntax, with output stored and returned:
print Piffle::Template->expand(source_file => 'foo/fish.xml',
include_path => ['foo/inc','foo']);
# Immediate: OO syntax: output goes directly to STDOUT:
Piffle::Template->expand(source_file => 'foo/fish.xml',
output_file => \*STDOUT);
# Procedural syntax, data from a string
$string = <<'__END__';
<?perl for $world (1 .. 10) { ?>
Hello world number {$world}.
<?perl print random_greeting(); ?>
<?perl } ?>
<?include std_disclaimer.txt?>
__END__
expand_template(source => $string,
output_file => \*FILE);
DESCRIPTION
This is a simple Perl-embedding syntax for template code which is geared towards allowing authors to validate their templates directly against schemas or DTDs. The embedded language is Perl itself, which allows great flexibility at the expense of having to be disciplined about the barrier between template code and module code.
In operation, the source template is transformed to an in-memory Perl script which is then run using Perl's eval
operator. Errors can be redirected to files or subroutines, and the output can be either caught in a variable or written to a file or open filehandle.
expand_template
# Generic syntax
expand_template(%OPT);
# -either-
my $result = expand_template(source_file => '...');
# -or-
expand_template(include_path => [qw{/tmp/foo /usr/local/lib/foo}],
source_file => '/usr/local/lib/foo/foo.xml',
output_file => \*FH,
errors_to => sub {},
# -or-
expand_template(source => $long_string);
Expands a template stored somewhere, and writes the output somewhere else. The generated script is executed in a uniquely-named package, without any enforced use strict
preceding it. This call normally stores the printed output from the template-script in a temporary scalar variable, and returns this scalar. You can redirect the output somewhere else though.
With versions of Perl prior to 5.8, you must redirect output using the output_file
option. Your program will fail ungracefully if you don't. This is because the internal store-and-return mechanism relies on the ability to open a filehandle to a scalar ref, which was introduced with Perl 5.8.
The options are:
- source
-
A string containing the template to convert to Perl code and run. Either this or
source_file
must be specified. - source_file
-
A source file containing the template to execute. This can be either a string containing the name of a file, or an open filehandle passed by reference.
- output_file
-
Redirects output to a specific file. Like
source_file
, this may be either a filename or a filehandle. - errors_to
-
Defines how errors raised by the template code are handled. This can be either a subroutine (which will be called with
$@
as its single argument), or a filehandle (to which the error message will be printed). The default is to propagate them back viadie
. - include_path
-
An array containing a number of directories to search when processing the
<?include...?>
directive. - reported_filename
-
Overrides the filename associated with error messages.
expand
Piffle::Template->expand(%opt);
Identical arguments, and return values to expand_template()
, but with object-oriented syntax.
template_to_perl
my $perl = template_to_perl($template_txt, $filename, @inc);
This is what expand()
and expand_template()
use to generate the Perl script that gets eval()
ed. Useful for debugging. By the way, <?include?>
is processed in this pass.
The Templating Language
There are only three constructs to worry about, the rest is just Perl, and the language you're embedding it in.
<?perl...?>
<p>
<?perl print $polite_greeting; ?>
</p>
<!-- You'll have to do your own escaping here. -->
<ul>
<?perl for (1 .. 10) { ?>
<li>Hello</li>
<?perl } ?>
</ul>
The tokens <?perl
and the immediately following ?>
denote a fragment of embedded Perl. Any calls to print
go to the currently select
ed output. The syntax is supposed to look like an XML processing instruction. If you use it where an XML PI could be used, your document should validate.
<?include...?>
<?include sidebars/easter_bunny.pl.html ?>
This is a simple textual inclusion. The named file is searched for in the include paths passed to template_to_perl()
, and is opened, transformed to a Perl script, and included into the Perl script being generated.
If the file doesn't exist, you get a warning about the problem; your template code doesn't fail before compilation if an inclusion can't be found. This is the right behaviour for template code; if you want to import vital constants and subroutines, you should be using use
or require
instead, and writing a proper Perl module.
Textual inclusions happen before compilation, i.e. when template_to_perl()
is called.
{$varname} etc.
<p>{$polite_greeting}<p>
<p>{@arrayvar}</p>
<a title="{$title}" href="foo.cgi?t={$title,uri}&p=1">
{$title}</a>
Variables with simple names (/^[\$\@\%]\w+$/
)can be interpolated into plain text by writing their names inside curly brackets. The resulting code contains a print
statement wrapped around something which escapes the contents of the variable.
You can optionally use a nonstandard character escaping style by appending a comma after the variable name, followed by the name of an escaping style. the escaping styles are:
- xml
-
{$varname} ;; typical {$varname,xml} ;; canonical
This is the default, and is also what you'll get if the escaping style you use isn't known. Characters in the set [
&<>"'
] are turned into decimal XML character references suitable for all XML dialects and hopefully all parsers. - uri
-
{%varname,uri}
Bytes (not characters) which aren't safe for use as an unparsed token in a URI are escaped into their hexadecimal representation. It's pretty much what
CGI::escape()
does. - raw
-
{@varname,raw}
Interpolates a variable without doing any escaping on its value whatsoever. Make sure you know what you're doing before trying this.
The syntax for variable interpolation is intended to look something like the way Expressions can be interpolated into attributes in XSLT. The default escape style was chosen because in most template CGI template work, I've tended to use this style most often.
Character escaping styles are intended to be extensible by module users. They just aren't yet.
Order of Execution
When your wrapper script calls:
Piffle::Template->expand(source_file => 'foo/bar.pl.html',
output_file => \*STDOUT);
The file foo/bar.pl.html
is slurped in, and transformed to a Perl script using template_to_perl()
. The resulting script is studded with #line
directives, so errors will be reported from the right place. Any textual inclusions happen at this point too.
The script is then run using Perl's eval-string operator. Despite the fact that the data came from an external file, this operation is considered taint-safe due to the way the template language parse works. Do not use this module if there are doubts about whether the Perl code in your template files is trustworthy.
Any print
statement in the template code, or in library code called from it, is sent to STDOUT.
Worked Examples, Tricks and Tips
Database Table Splats
This charming term comes from my workplace, and describes dumping the results of a SQL query into an HTML table for display and viewing. A common approach to doing this in a Perlish templating language is:
<table>
<?perl
while (my ($id,$name,$colour,$partnum) = each_part($manuf))
{
?>
<tr>
<td>{$id}</td>
<td>{$name}</td>
<td>{$colour}</td>
<td>{$partnum}</td>
</tr>
<?perl
} #each_part
?>
</table>
The corresponding definition of each_part()
follows. It has to do a little state management so that it can behave a little like Perl's builtin each
operator.
use DBI;
our $dbh = DBI->connect('some_dsn', 'user', 'secret') or die;
our $each_part_sth;
sub each_part
{
my $manuf = shift;
if (! defined $each_part_sth)
{
$each_part_sth = $dbh->prepare(q{
SELECT p.id,p.name,s.colour,s.partnum
FROM Parts p, Styles s
WHERE p.id = s.part
AND p.manufacturer = ?
});
$each_part_sth->execute($manuf);
}
my @ret = $each_part_sth->fetchrow_array;
unless (@ret)
{
$each_part_sth->finish;
undef $each_part_sth;
}
}
Optional Attributes
Consider the HTML select
box:
<select name="colour" multiple="multiple">
<option value="r" selected="selected">Red</option>
<option value="g">Green</option>
<option value="b">Blue</option>
</select>
The option
elements may or may not be selected. Sometimes you want to make template code that prints the above multiselect using information about the selection state from a database. Unfortunately, you can't just write
XXX Incorrect Code XXX
<option value="{$col_s}" {$is_sel}>{$col_l}</option>
XXX Incorrect Code XXX
because your template wouldn't validate. The fix is to use the following messy workaround:
<select name="colour" multiple="multiple">
<?perl
my $sel_hack_on = '" selected="selected';
while (my ($col_s, $is_sel, $col_l) = each_col())
{
my $sel_hack = $is_sel ? $sel_hack_on : '';
?>
<option value="{$col_s}{$sel_hack}">{$col_l}</option>
<?perl
} #each_col
?>
</select>
It's ugly, but it works.
Messing with Execution Order
Special blocks such as BEGIN
and END
can be used to change the order in which output is generated, which is useful for generating HTTP headers in your code while retaining good XML syntax:
<?xml version="1.0"?>
<foo>
<?perl
# All of the above turns into prints. But we want the header to be
# printed before that.
use CGI;
BEGIN {
# ... do something with the POSTdata ...
print CGI->header(-type => 'application/xml',
-charset => 'UTF/8',
-status => "201 Created");
}
?>
<bar name="Cheers" location="East West 13th St, NY, USA" />
<pub name="Eagle & Child" location="Oxford, Oxon, UK" />
</foo>
You can't put a lump of Perl before an XML declaration because that would break XML's syntax. Sticking the header print inside a BEGIN
block causes the HTTP header to be emitted in a place that keeps web servers happy. END
is slightly less useful than BEGIN
, but could be used to clean up resources after you've finished executing. If you try to use INIT
or CHECK
, you'll get an error looking like:
Too late to run INIT block at DATA line 5, <DATA> line 1.
This is because the Perl runtime has already started up, and you can't catch the transition between the compilation phase(s) and normal execution. See "BEGIN, CHECK, INIT and END" in perlmod for more on this.
Similar Modules and Related Software
You may wish to consider a number of other modules which do a similar thing to Piffle::Template
. They're almost certainly far better.
- Text::Template - Mark-Jason Dominus
-
Good and simple, with more support than this thing. Perl code can be written between curly brackets: '{' and '}'. Each pair of brackets corresponds to an
eval""
, and the resulting value of the block gets interpolated into the output, raw. Loops are done inside the brackets, and if you want each pass to generate output, you have to concatenate onto a variable. The delimiter syntax can be varied from the default. - Embperl - G. Richter
-
Uses a rather complicated syntax for its embedding glue, and features a pluggable framework, but does essentially the same thing as
Piffle::Template
. Automatically HTML-escapes its interpolations. ePerl
- Ralf S. Engelschall-
A perl-in-plaintext dialect similar to
Embperl
andPiffle::Template
which comes with its own Perl interpreter and an Apache plugin. Allows shorthand for interpolation. Bound to be much faster than my effort, at the expense of some extra weight and dependencies. mmm-mode
-
This is an Emacs mode for writing blocks of one language inside another language, using delimiters to separate the two. It can be used with any of the above, and features delimiter definitions for some of them.
BUGS
The embedding syntax cannot be changed, and it's somewhat monopurpose in intent: write Perl in XML, but make it validate.
You need to escape the embedding tokens to stop Piffle::Template
from choking on them.
Error reports can catch some very strange code when the generated script is syntactically invalid, mostly around interpolations with escapes. You have to know what you're doing in order to debug this sometimes, despite the #line
directives. Experiment with template_to_perl()
to see the kinds of horrors that get generated.
However, if you think this wheel is rounder than others out there, please tell me. I might make it into a more formal release. However, there are plenty of wheels out there to choose from. I think this one has the nicest syntax, however.
AUTHOR
Andrew Chadwick, <andrewc-ptemplate200402@piffle.org>.
This software may be distributed under the same terms as Perl itself.