|
Piffle::Template - Perlish templating language |
Piffle::Template - Perlish templating language
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);
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.
# 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_file must be specified.
source_file, this may be either a
filename or a filehandle.
$@ as its single argument), or a
filehandle (to which the error message will be printed). The default is to
propagate them back via die.
<?include...?> directive.
Piffle::Template->expand(%opt);
Identical arguments, and return values to expand_template(), but
with object-oriented syntax.
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.
There are only three constructs to worry about, the rest is just Perl, and the language you're embedding it in.
<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
selected 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 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.
<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:
{$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.
{%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.
{@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.
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.
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;
}
}
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.
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 the perlmod manpage for more on this.
You may wish to consider a number of other modules which do a similar thing to
Piffle::Template. They're almost certainly far better.
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.
Piffle::Template. Automatically HTML-escapes its interpolations.
ePerl - Ralf S. EngelschallEmbperl and Piffle::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
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.
Andrew Chadwick, <andrewc-ptemplate200402@piffle.org>.
This software may be distributed under the same terms as Perl itself.
|
Piffle::Template - Perlish templating language |