|
\n');
}
if ( Shocked ) {
document.write('');
}
else {
document.write(' ');
}
//-->
|
|
|
|
Written by David Lyon It is not uncommon to see CGI programs that contain the HTML code that is returned to the browser, inside one or more print statements in the code of the script. Clearly this is not a good idea, since it makes it at least difficult, if not nearly impossible, for a non-programmer to alter the output format. At best, a webmaster with little understanding of perl could identify the HTML code inside the program and alter some obvious items, like colours. Still, as applications grow more complex, this approach becomes even more difficult to manage. If control structures (if, while etc) are being used to generate the output, or if there are multiple pages of output composed by chunks of html that are being generated in different places in the code, even someone who is comfortable with perl could find it hard to figure out how to alter the look of the output. And this is not the only problem. The code itself becomes difficult to manage and maintain. Program code is difficult to read an maintain on its own right. Cluttering it with HTML here and there does not help the situation at all. I will describe a different approach to the problem here, the one that we use for our programs and which proves to be a very good solution for almost any type of application. The idea in this approach is to isolate the HTML code in a separate file, which we call a template. A template is just like any regular HTML file, but it contains some special markup dictating where to insert the various dynamic data that will be produced by the CGI program. The program need only be concerned with generating the actual dynamic content, then it can simply insert it in appropriate place in the template and thus come up with the final output.
What a template looks likeThe only thing that sets a template apart from a
regular HTML file is that it contains comments of the form
<!--cgi:variable-->.
Such comments will be identified by the CGI program and replaced with the
appropriate value for <HTML> ... Your search for <b><!--cgi:search_string--></b> resulted in <b><!--cgi:total_results--></b> matching pages. ... </HTML> Filling in the templateA simple way of doing this is the following: open(TEMPLATE, $template_file);
while(<TEMPLATE>)
{
$buffer .= $_;
}
$buffer =~ s/<!--cgi:search_string-->/$search_string/g;
$buffer =~ s/<!--cgi:total_results-->/$total_results/g;
print $buffer;
This will do the job, but we can certainly do much better than that. A realistic CGI-based application might contain many scripts generating many pages each, and each of those pages might contain much more than just a handful of variable data. Clearly we could write something much more general, a little module, that can handle output generation in the same way, but without requiring the programmer to explicitly perform substitutions of the template directives in the CGI program. To introduce you to the use of a template I will show a simple example: my $template = new Test::Template("template.html");
my %data = (
name => 'Nick',
email => 'nick@ukname.net',
homepage => 'http://www.ukname.net',
);
my $html = $template->cast(\%data);
First of all we create a Test::Template object corresponding to the template file we want to use. The filename (or fully qualified path to it) is passed as an argument to the object constructor that returns a reference to the newly created object. Then we prepare the data that we are going to cast to the template. Test::Template expects us to supply it with a reference to a hash table (associative array) mapping data keys (template variables) to the values that should be inserted for them in the template. Thus all the processing done by the CGI program should end up defining a set of such key-value pairs in a hash reflecting the dynamic content of the output. Finally, we call the cast method of the template object passing a reference to our data hash. The cast method does not alter the template, but simply uses it to produce a filled-in copy of it that is returned by the call. That means, that you can reuse a template as much as you like in a program. This allows you to construct your output from 'sub-templates'. For example, in our search engine script, there is one template that defines the general layout of the results page, but there is also a template that defines the format of a single result listing. The program uses the latter to generate result listings by substituting, title, description, url, score, etc and then puts them all together and inserts them in the general layout template. To create the result listings we only create one template object and use it for all results, each time passing a different hash reference to it. This has the advantage that the template file is read only once and that the processing needed to generate an other instance of the html output is minimal.
Passing substitution handlers..In most cases the functionality we discussed so far is more than enough to do the job. There are cases though where passing static values for substitution in a template is a too restrictive mechanism. Sometimes you want the template to be able to describe not only the location of the substitutions but also some dynamic behavior. In the example above I might want to let the template author choose whether the homepage url should appear in linked or plain text form. Of course, the template variable could just store the url and the template could contain a directive: <a href="<!--cgi:homepage-->"><!--cgi:homepage--></a> to construct the linked form, but in other situations there might not be such workarounds. I would like to let the user pass parameters in a template directive, and instead of substituting a static string for the directive, I would like the substitution string to be generated by a function that will be called with the parameters in the directive. Test::Template allows you to use references to subroutines instead of scalars as values of the substitution hash. When a directive for such a template variable (that really is a subroutine) is met, Test::Template will arrange for the subroutine to be called and will use its return value as a substitution string. Further if the template directive includes arguments in parentheses as in <!--cgi:homepage('linked')--> the subroutine will be passed those arguments using perl's standard argument passing mechanism. Arguments in the parentheses follow standard perl syntax for subroutine calls. So let's review our example above, now using the subroutine calling mechanism... my $template = new Test::Template("template.html");
my %data = (
name => 'Nick',
email => 'nick@ukname.net',
homepage => sub {
if($_[0] eq 'linked')
{ return ... }
else
{ return ... }
}
);
my $html = $template->cast(\%data);
A final note to be made is that, while this tool was initially developed to aid the writing of CGI scripts that produce HTML output, it is absolutely fine to use for any kind of program that needs to format text files into a configurable layout. You can download the module here, template.zip Have fun programming with Test::Template.
By David Lyon |
© 1999-2012,
Creative Fundamentals, Inc.
Hosting and bandwidth generously provided by YourDomainHost
Domain Registration generously provided by NICForce