SSI is quite powerful, but it does have limitations. Its advantages are that it is efficient and simple enough for HTML designers without programming experience to use. The disadvantages are that it only has a handful of commands, and it only parses static documents. HTML::Template is a simple template parser that addresses both of these issues while still maintaining a simple interface.
HTML::Template actually has fewer commands than SSI, but because the value of its variable tags can be set to anything by a CGI script, it is more flexible. While it's true that an SSI document can include CGI output, this becomes unwieldy if a page includes several complex components that must each execute a CGI script. HTML::Template supports complex templates with the execution of a single CGI script.
Let's look at a very simple example that displays the current date and time. Example 6-3 shows the template file.
<HTML> <HEAD> <TITLE>Current Time</TITLE> </HEAD> <BODY BGCOLOR="white"> <H1>Current Time</H1> <P>Welcome. The current time is <TMPL_VAR NAME="current_time">.</P> </BODY> </HTML>
This is a standard HTML file with one added tag: <TMPL_VAR NAME="current_date" >. HTML::Template's commands can be formatted like standard HTML tags or as comments. The following is also acceptable:
<!-- TMPL_VAR NAME="current_date" -->
This alternate syntax makes the commands easier to input into HTML editors that may be restrictive about the tags they allow. In order to use this template, we must create a CGI script that is the target of the request. The code for this is shown in Example 6-4.
#!/usr/bin/perl -wT use strict; use HTML::Template; use constant TMPL_FILE => "$ENV{DOCUMENT_ROOT}/templates/current_time.tmpl"; my $tmpl = new HTML::Template( filename => TMPL_FILE ); my $time = localtime; $tmpl->param( current_time => $time ); print "Content-type: text/html\n\n", $tmpl->output;
We create a constant called TMPL_FILE that points to the template file we will use. We then create an HTML::Template object, assign a parameter, and output it. Most tags have a NAME attribute; this value of this attribute corresponds to a parameter set by a CGI script via HTML::Template's param method, which (by design) works much like CGI.pm's param method. In fact, you can import parameters from CGI.pm when you create a HTML::Template object:
my $q = new CGI; my $tmpl = new HTML::Template( filename => TMPL_FILE, associate => $q );
This loads all of the form parameters that your CGI script just received; you can of course still use the param method to add additional parameters or override those loaded from CGI.pm.
HTML::Template's commands are summarized in Table 6-4.
Element |
Attribute |
Description |
---|---|---|
TMPL_VAR |
NAME="param_name" |
This tag is replaced by the value of the parameter param_name; has no closing tag. |
ESCAPE="HTML|URL" |
If this is set to "HTML", then the value substituted for this tag is HTML escaped (e.g., " will be replaced by ", etc.); "URL" will encode the value for URLs. No escaping is done if this is set to or omitted. |
|
TMPL_LOOP |
NAME="param_name" |
Loops over content between its opening and closing tags for each item in the array that corresponds to param_name, see below. |
TMPL_IF |
NAME="param_name" |
Content within this tag is omitted unless the parameter param_name is true. |
TMPL_ELSE |
This reverses the condition for the remaining content within a TMPL_IF or TMPL_UNLESS tag. |
|
TMPL_UNLESS |
NAME="param_name" |
The reverse of TMPL_IF. Content within this tag is omitted unless the parameter param_name is false. |
TMPL_INCLUDE |
NAME="/file/path" |
Includes the contents of another file; has no closing tag. |
Only the TMPL_LOOP, TMPL_IF, and TMPL_UNLESS commands have opening and closing tags; the others are single tags (like <HR> or <BR>).
One of the most convenient features that HTML::Template offers is the ability to create loops. The previous example didn't take advantage of this, so let's look at a more complex example. HTML::Template requires an array of hashes for loops. It loops over each element in the array and creates variables corresponding to the hash keys. You can visualize this structure as a table, as in Table 6-5, which can be represented in Perl as an array of hashes, as in Example 6-5.
Name |
Location |
Age |
---|---|---|
Mary |
Minneapolis |
37 |
Fred |
Chicago |
24 |
Martha |
Orlando |
51 |
Betty |
Los Angeles |
19 |
... |
... |
... |
@table = ( { name => "Mary", location => "Minneapolis", age => "37" }, { name => "Fred", location => "Chicago", age => "24" }, { name => "Martha", location => "Orlando", age => "51" }, { name => "Betty", location => "Los Angeles", age => "19" }, ... );
Example 6-6 contains a script that will display all of the standard colors available on systems that support the X Window system.
#!/usr/bin/perl -wT use strict; use HTML::Template; my $rgb_file = "/usr/X11/lib/X11/rgb.txt"; my $template = "/usr/local/apache/templates/xcolors.tmpl"; my @colors = parse_colors( $rgb_file ); print "Content-type: text/html\n\n"; my $tmpl = new HTML::Template( filename => $template ); $tmpl->param( colors => \@colors ); print $tmpl->output; sub parse_colors { my $path = shift; local *RGB_FILE; open RGB_FILE, $path or die "Cannot open $path: $!"; while (<RGB_FILE>) { next if /^!/; chomp; my( $r, $g, $b, $name ) = split; # Convert to hexadecimal #RRGGBB format my $rgb = sprintf "#%0.2x%0.2x%0.2x", $r, $g, $b; my %color = ( rgb => $rgb, name => $name ); push @colors, \%color; } close RGB_FILE; return @colors; }
This CGI script uses the rgb.txt file that is typically found on X Window systems at /usr/X11/lib/X11/rgb.txt. This file contains a list of colors along with their 8-bit values for red, green, and blue:
! $XConsortium: rgb.txt,v 10.41 94/02/20 18:39:36 rws Exp $ 255 250 250 snow 248 248 255 ghost white 248 248 255 GhostWhite 245 245 245 white smoke 245 245 245 WhiteSmoke ...
We read the red, green, and blue values and convert them to the hexadecimal equivalent that is used on HTML pages (e.g., #336699). We create a separate hash for each color with an entry for the RGB value and the name of the color. Then we add each hash to the array @colors. We need to pass only @colors as a parameter to HTML::Template, which we can use as a loop variable within our HTML template. Within the loop, we have access to the "rgb" and "name" elements of our hashes, as shown in Example 6-7.
<HTML> <HEAD> <TITLE>X11 Color Viewer</TITLE> </HEAD> <BODY BGCOLOR="white"> <DIV ALIGN="center"> <H1>X11 Color Viewer</H1> <HR> <TABLE BORDER="1" CELLPADDING="4" WIDTH="400"> <TMPL_LOOP NAME="colors"> <TR> <TD BGCOLOR="<TMPL_VAR NAME="rgb">"> </TD> <TD><TMPL_VAR NAME="name"></TD> </TR> </TMPL_LOOP> </TABLE> </DIV> </BODY> </HTML>
This loop structure is flexible enough to allow us to display other forms of data, such as hashes. Example 6-8 lists a CGI script that generates all of the environment variables and their values.
#!/usr/bin/perl -wT use strict; use HTML::Template; use constant TMPL_FILE => "$ENV{DOCUMENT_ROOT}/templates/env.tmpl"; my $tmpl = new HTML::Template( filename => TMPL_FILE, no_includes => 1 ); my @env; foreach ( sort keys %ENV ) { push @env, { var_name => $_, var_value => $ENV{$_} }; } $tmpl->param( env => \@env ); print "Content-type: text/html\n\n", $tmpl->output;
HTML::Template has no facility for handling hashes directly, but because it will loop over arrays of hashes, we build a hash for each pair in %ENV and add it to an array, @env. We then pass a reference to @env as a parameter to our HTML::Template object and output the parsed file. Our template file is shown in Example 6-9.
<HTML> <HEAD> <TITLE>Environment Variables</TITLE> </HEAD> <BODY BGCOLOR="white"> <TABLE BORDER="1"> <TMPL_LOOP NAME="env"> <TR> <TD><B><TMPL_VAR NAME="var_name"></B></TD> <TD><TMPL_VAR NAME="var_value"></TD> </TR> </TMPL_LOOP> </TABLE> </BODY> </HTML>
Note that we called param once, even though there are three different HTML::Template tags in this file. The var_name and var_value variables were set because they corresponded to hash keys within the @env array.
HTML::Template offers two ways to create a conditional just like Perl: TMPL_IF and TMPL_UNLESS. You can use these to include or omit particular portions of your HTML template. Both tags take a NAME attribute that corresponds to a parameter, just like previous tags, which is evaluated in a boolean context. There is no way to create expressions to evaluate within your templates, since the goal is to keep templates simple. Note also that you do not always have to set a separate parameter in order to use these tags. For example, you could include a block like this in your document:
<TMPL_IF NAME="secret_msg" > <P>Psst, here's your secret message: <TMPL_VAR NAME="secret_msg">.</P> </TMPL_IF>
Here the same parameter is used in both the TMPL_IF and TMPL_VAR commands. If there is a secret message, it is displayed. If there isn't (i.e., if it is an empty string), then nothing is displayed instead.
You can also use loop parameters as conditions. If the loop parameter contains any values, it returns true; otherwise it returns false. This is useful for displaying search results when there are no matches:
<P>Here are the results of your query:</P> <TABLE> <TR> <TH>Software Title</TH></TR> <TH>Home Page</TH></TR> </TR> <TMPL_LOOP NAME="results"> <TR> <TD><TMPL_VAR NAME="sw_title"></TD> <TD><A HREF="<TMPL_VAR NAME="url">"><TMPL_VAR NAME="sw_url"></A></TD> </TR> </TMPL_LOOP> <TMPL_UNLESS NAME="results"> <TR> <TD COLSPAN="2"> No software titles match your query. </TD> </TR> </TMPL_UNLESS> </TABLE>
In this example, a user is searching for software according to some criteria. If the query matches any titles, then the name and home page of the titles are displayed on separate rows in a table. If no rows match, then the script says this instead. This template gives the interface designer full control over how the results are presented to the user without being too complicated to understand.
The final command, TMPL_INCLUDE, includes the content of other files in your template. The content of these files is included before loops and variables are parsed, so you can include files that contain loop and variable tags (or even other include tags). This is similar to the SSI include command, except there is no ability to provide a virtual path to the file; you must provide a filesystem path. HTML::Template does no validation that the file is within the document root, so an HTML developer could easily include the following statement in a file and HTML::Template would act accordingly:
<TMPL_INCLUDE NAME="/etc/passwd" >
This is not as serious a security issue as it might appear, since an HTML designer could always copy the contents of /etc/passwd into an HTML file manually or create a symbolic link to it. However, this potential is something you should be aware of. You can disable includes entirely with the no_includes option when you create an HTML::Template object.
HTML::Template is certainly a very elegant solution for projects where the roles of HTML designers and developers are clearly separated. HTML::Template has only been available a short while but has matured quickly. It also offers more advanced features including caching output that we haven't discussed. The features we discussed are accurate as of Version 1.7, but new features are still being added, so check the documentation for more information. You can find HTML::Template on CPAN; for the latest information, including information on the mailing list and CVS, consult the online documentation.
Copyright © 2001 O'Reilly & Associates. All rights reserved.