GavilanCSIS 54: A Simple Template System

A common thing to do in perl is to control a web site. You can write scripts (or use someone else's) to create the html files that comprise a web site.
This page contains a simple template system. It has the following features:

Reading the data file

The first task of the script is to parse the data file and put everything into appropriate variables. 

Here is the data file:

Andrew
55
Hello everybody, This is Andrew's page.
###
Bob
22
Hi there, welcome to Bob's page.
###
Charlie
44
You have reached Charlie's page.
###

The format of the data file isn't completely clear from looking at it. (Of course, our script is the ultimate definition of the format.) Here's how the data is formatted:

Obviously, this script is just a toy right now. But it demonstrates our technique and some of the trade-offs to consider. Here is the part of the script that reads the file:

open DATAFILE, "<cmsdata.txt";

$state = 0;    # 0 - expecting name   
   ## ( 1 ) ##
               # 1 - expecting age
               # 2 - expecting content

open IN, "<data.txt";
while ($line = <IN>) {                    ## ( 2 ) ##
  chomp($line);
  if ($line eq "###") {
    makefile($name,$age,$content);
        ## ( 3 ) ##
    $content = "";
    $state = 0; }   
                     ## ( 4 ) ##
  elsif ($state == 0) { $name = $line; $state = 1;}
  elsif ($state == 1) { $age = $line;  $state = 2;}
  elsif ($state == 2) { $content .= $line; }
  }
}

 

What's going on here? First, the data file is opened. Next, a loop is entered (note 2). This loop runs once for each line of the data file.

This script is making use of a technique called a state machine. The variable $state is being used to keep track of how much of a record has been seen so far. $state is going to be one of 3 values (the numbers 0-2).

The loop begins in state 0. It also gets set to state 0 when the record separator (###) is seen. In state 0, we expect to see the first line of a record, which should be someone's name. So we set (note 4) the $name variable to that line, ...

And change the state. We change to state 1 (because we're going to expect the age line on the next trip around the loop.)

And so it continues. The next time around the loop, the only part that executes is the elsif ($state==1) ... clause. At that point, we are indeed expecting the age line of the record, so the $age variable gets set, and again we increment the $state variable to prepare for the next trip around the loop.

What happens the next time around the loop? State is 2, so that line gets appended to the $content variable. Why does it get appended? Because I decided that the content part of the record can be more than one line. Notice that we don't change the $state variable there either. The script will stay in state 2....

Until it sees a line that is 3 hashes (###). When the line is equal to the 3 hashes, or the record seperator, we change the state back to 0 to prepare for the next record. At this point, we have gathered all the data in a single record, so its probably a good time to either save it or do something with it. We go ahead and build the page (with the function makepage) and then continue reading the data records.

 

Making a page

Using three variables ($name, $age, and $content) we build a web page. I like to keep my perl code free of any other languages, so I'm going to put all the HTML in a different file. This will be my template file:

<html>
  <head>
    <title>--NAME--'s Web Page</title>
  </head>
  <body>
    <h1>Welcome to --NAME--'s Web Page.</h1>
    --CONTENT--
    <hr />
    <font size=-1>
      <a href="index.html">Back to index</a>
    </font>
  </body>
</html>


Ah.. whoops! Looks like there wasn't any use for the age data to be kept around. We'll have to fix that in the next version!

Nonetheless, this is our template that we will drop the data into. Just like making a form letter, we'll substitute the string --NAME-- for the actual name from the data, and likewise for the string --CONTENT--.

But first, there's one other string we need to make a web page. A name for the file! (Can't have an HTML file without a filename, can we?)

We can build a file name using the $name from the data record. (right?)

If we want to build a table of contents, we'll probably want to save those file names. A list should do the trick.

Finally, we'll do the real work of the template engine, the substutiting of the --PLACEHOLDER-- for the real data from our data file.

sub makefile {
  my %vars = {};
  $vars{'NAME'} = shift;
  $vars{'AGE'} = shift;
  $vars{'CONTENT'} = shift;
  my $filename = "$vars{'NAME'}.html";   ## ( 1 ) ##
  push(@files,$filename);
                ## ( 2 ) ##
  open INFILE, "<cmstemplate.html";
  open OUTFILE, ">$filename";
  while ($line = <INFILE>) {
    $line =~ s/--(\w+)--/$vars{$1}/g;
   ## ( 3 ) ##
    print OUTFILE $line;
  }
  close INFILE;
  close OUTFILE;
}

 

This subroutine stores its arguments into a hash table. (Got that?)

At note 1, the filename is built. (Any potential problems with this approach?)

At note 2, the filename is saved into a separate list (@files) for making a TOC later.

Then the template is opened and looped through. A new file for writing is opened, using the $filename we just defined.

At note 3, the substitution happens. This is done using a regular expression. In this particular regular expression, (or regex) a substitution is being done. Any part of the line matching the --PLACEHOLDER-- is replaced with the value from $vars{'PLACEHOLDER'}.

Our data file creates "placeholder"s of NAME, AGE, and CONTENT. These correspond to the places in the template where we've put the string --NAME-- and --CONTENT--.  (never used --AGE--, but we could!)

 

What about the Table of Contents?

Remember saving the filenames into the @files list?

open TOC, ">index.html";
for $f (@files) {
  print TOC "<a href='$f'>Person</a><br />";
}
close TOC;

Believe it or not, it works! Web browsers will display this as a web page, despite the fact that it lacks <html> and <body> tags.

It it acceptable to the client (or boss)? I doubt it. Obviously our job here is not done.

 

The files

 

 



Address of this page is http://hhh.gavilan.edu/phowell/csis054/cms.html
Please contact Peter Howell at phowell@gavilan.edu for questions or comments.
Last updated March 4, 2010.