You want to store a parameterized template in an external file, read it in from your CGI script, and substitute your own variables for escapes embedded in the text. This way you can separate your program from the static parts of the document.
To expand only variable references, use this
template
function:
sub template { my ($filename, $fillings) = @_; my $text; local $/; # slurp mode (undef) local *F; # create local filehandle open(F, "< $filename\0") || return; $text = <F>; # read whole file close(F); # ignore retval # replace quoted words with value in %$fillings hash $text =~ s{ %% ( .*? ) %% } { exists( $fillings->{$1} ) ? $fillings->{$1} : "" }gsex; return $text; }
On a data file like this:
<!-- simple.template for internal template() function --> <HTML><HEAD><TITLE>Report for %%username%%</TITLE></HEAD> <BODY><H1>Report for %%username%%</H1> %%username%% logged in %%count%% times, for a total of %%total%% minutes.
Or use the CPAN module Text::Template to expand full expressions if you can guarantee the data file is secure from tampering. A data file for Text::Template looks like this:
<!-- fancy.template for Text::Template --> <HTML><HEAD><TITLE>Report for {$user}</TITLE></HEAD> <BODY><H1>Report for {$user}</H1> { lcfirst($user) } logged in {$count} times, for a total of { int($total / 60) } minutes.
Parameterized output for your CGI scripts is a good idea for many reasons. Separating your program from its data lets you give other people (art directors, for instance) the ability to change the HTML but not the program. Even better, two programs can share the same template, so style changes in the template will be immediately reflected in both.
For example, suppose you have stored the first template from the Solution in a file. Then your CGI program contains the definition of the
template
subroutine above and makes appropriate settings for variables
$username
,
$count
, and
$total
. You can fill in the template by simply using:
%fields = ( username => $whats_his_name, count => $login_count, total => $minute_used, ); print template("/home/httpd/templates/simple.template", \%fields);
The template file contains keywords surrounded with double percent symbols (
%%KEYWORD%%
). These keywords are looked up in the
%$fillings
hash whose reference was passed as the second argument to
template
.
Example 20.7
is a more elaborate example using an
SQL database.
#!/usr/bin/perl -w # userrep1 - report duration of user logins using SQL database use DBI; use CGI qw(:standard); # template() defined as in the Solution section above $user = param("username") or die "No username"; $dbh = DBI->connect("dbi:mysql:connections:mysql.domain.com", "connections", "seekritpassword") or die "Couldn't connect\n"; $sth = $dbh->prepare(<<"END_OF_SELECT") or die "Couldn't prepare SQL"; SELECT COUNT(duration),SUM(duration) FROM logins WHERE username='$user' END_OF_SELECT # this time the duration is assumed to be in seconds if (@row = $sth->fetchrow_array()) { ($count, $seconds) = @row; } else { ($count, $seconds) = (0,0); } $sth->finish(); $dbh->disconnect; print header(); print template("report.tpl", { 'username' => $user, 'count' => $count, 'total' => $seconds });
If you want a fancier, more flexible solution, look at the second template in the Solution section, which relies upon the CPAN module Text::Template. Contents of braces found within the template file are evaluated as Perl code. Ordinarily, these substitutions will just be simple variables:
You owe: {$total}
but they can also include full expressions:
The average was {$count ? ($total/$count) : 0}.
Example 20.8 is an example of how you could use that template.
#!/usr/bin/perl -w # userrep2 - report duration of user logins using SQL database use Text::Template; use DBI; use CGI qw(:standard); $tmpl = "/home/httpd/templates/fancy.template"; $template = Text::Template->new(-type => "file", -source => $tmpl); $user = param("username") or die "No username"; $dbh = DBI->connect("dbi:mysql:connections:mysql.domain.com", "connections", "secret passwd") or die "Couldn't db connect\n"; $sth = $dbh->prepare(<<"END_OF_SELECT") or die "Couldn't prepare SQL"; SELECT COUNT(duration),SUM(duration) FROM logins WHERE username='$user' END_OF_SELECT $sth->execute() or die "Couldn't execute SQL"; if (@row = $sth->fetchrow_array()) { ($count, $total) = @row; } else { $count = $total = 0; } $sth->finish(); $dbh->disconnect; print header(); print $template->fill_in();
The more powerful approach raises security concerns. Anyone who can write to the template file can insert code that your program will run. See Recipe 8.17 for ways to lessen this danger.
The documentation for the CPAN module Text::Template; Recipe 8.16 ; Recipe 14.10