From the last lesson, it is clear that your CGI program must generate all the HTML in order to maintain its state.
Unfortunately, embedding all that HTML in your CGI program can quickly become unwieldy and unmanageable.
One elegant solution is to use what I call
Perl-based HTML.
- Perl-based HTML
In a previous lesson (Module 2, Lesson 10, "Perl Substitute Operator"), you saw a small routine that makes it possible for a perl program to read an HTML file and expand perl variables inside the HTML. I have taken this concept another step to help with the state-management problem.
sub htmlp
{
local $filename = shift;
my $fhstring = $filename;
$fhstring =~ s/[^a-z]//i;
unless (-f $filename) {
print qq(<h1>Error: </h1>\n );
print qq(<p><em>htmlp</em> can't find "$filename"=/p>\n);
return "";
}
open($fhstring, "<$filename");
while(<$fhstring>) {
# to execute perl code
s/$\{(.*?)}/eval($1),""/eg;
# $$filename to include another file
s/$$([\S;]+;?)/htmlp($1)/eg;
# $variable to include a variable
s/$(\w+)//eg;
print;
}
close $fhstring;
return "";
}
This version of the htmlp routine adds a couple of new features:
$variable is still expanded to the contents of the variable
$$filename includes another file by recursively calling htmlp
executes arbitrary perl code from within the file
Using the $variable feature, it becomes trivial to rapidly deploy new states by calling htmlp with a file that has embedded Perl variables like this
(I call these "htmlp" files):
<form action="$callback" method=POST>
<h3>Please write in my Guestbook:</h3>
<p>Your Name:
<br><input type=text name=name value="$name" size=40>
<p>Where are you from?
<br><input type=text name=wherefor value="$wherefor" size=40>
<p>Say something cool:
<br>
<textarea cols=40 rows=10 name=message wrap=hard>$message</textarea>
<p>
<input type=submit value=" Make it so! ">
<input type=reset value=" Reset the form ">
<input type=hidden name=state value=validate>
</form>
Each of the variables in the htmlp file is defined in the Perl language CGI program. In this case:
$callback is the URL of the CGI program.
$name, $wherefor, and $message are the variables that carry the context of the form between calls.
An error routine can use this htmlp file:
<h3>Error:</h3>
<p>$error
<form action="$callback" method=POST>
<p><input type=submit value=" Continue ">
<input type=hidden name=name value=$name>
<input type=hidden name=wherefor value=$wherefor>
<input type=hidden name=message value=$message>
<input type=hidden name=state value=edit>
</form>
Simply define the $error variable before calling htmlp("error.htmlp"). The $filename feature is convenient for creating one htmlp file that can be used repeatedly. This makes it much easier to present the user with a consistent interface, without having to code and recode the same screen several times, and then try to keep changes in sync. The feature is the final piece that makes this more than just a convenience. This makes it possible to embed repetitive and conditional code inside the HTML. No, it does not run client-side, that is for languages like Java and JavaScript, but it does allow things like calling another routine for repetitive data or short conditional code for negotiating browser feature sets.
For those of you who are interested in how the regular expressions work in this code,
click here for an explanation.
The htmlp() routine uses the following code to replace strings in the input file:
# to execute perl code
s/$\{(.*?)}/eval($1),""/eg;
# $$filename to include another file
s/$$([\S;]+;?)/htmlp($1)/eg;
# $variable to include a variable
s/$(\w+)//eg;
Each of these regular expressions replace the left-hand side (LHS) with the results of an expression. For example, the first one:
s/$\{(.*?)}/eval($1),""/eg;
matches anything that starts with a $, followed by a pair of curly braces and what is contained within the braces.
You may note the ".*?" within the parenthesis. This makes the repeat
less greedy. Without the question mark, the .* would match all the rest of the characters of the string, up to the last "}" character. With the question mark, it matches up to the
first }.
On the right-hand side (RHS) of the first substitution, the expression eval($1) inserts the return value of the expression in $1 (which is in turn replaced with the contents of .*? from the LHS). This returned value must be discarded so that it does not end up in your HTML! In order to accomplish that, the ,"" is used to force the whole expression to return a null string,
which will be inserted in the HTML without consequence.
Question: So, if the return value is discarded, what effect does this code have, if any?
Answer: Remember that the standard output stream of your CGI program is passed through the server to the client (browser).
During the process of this substitution, whatever data is sent from the $1 expression to the standard output will be sent to the client
at that time. The effect is that the output from this code will be inserted in the correct place with the .htmlp file.
The (RHS) right hand side of the second substitution works very similarly. It allows you to insert another
file in the stream, much like a server-side include. (In fact, this is the code I use to accomplish SSI on servers that do not otherwise support it.) The RHS expression simply includes calls the htmlp() routine recursively.
The third substitution is probably the simplest. It simply inserts the value of a variable in the stream.