Chapter 23

HTML with Perl Modules


CONTENTS


This chapter covers how to use Perl with HTML FORMs to get user input and respond back to Web servers. One of the examples in this chapter covers getting statistics from a Web site. The topics in this chapter include extending the way the collection of user input in an HTML FORM is handled, processing the input, and then displaying the results back in tabular form. The data used in this chapter is information from the well-known, free utility getstats. I also introduce a way to produce clickable images and show you how to connect to scripts that handle the input for you.

At the end of this chapter, you'll continue to work with public-domain extension modules to Perl, CGI.pm and HTML.pm, which remove a lot of the onus from writing HTML pages and segregates the application from the HTML standard, thus making the application more portable and less susceptible to changes in the standard.

Presenting Data on the Web Using CGI Scripts

The World Wide Web provides a lot of flexibility for presenting and publishing data. You can present data in graphical images, or text data arranged neatly in tables, or just as plain columnar text. In graphical form, data can be shown as figures and charts, even as images of tables, and so on. For tabular forms of presentation, you can show data by using the built-in tabulating features of HTML.

This chapter covers the basics of representing data on the Web. I do not assume that you have an existing, multi-layered, whiz-bang database. With such a database, no doubt you'll also have the tools to get this data out in just about any format you need. Instead, I concentrate on the basic comma-delimited format generated by most spreadsheets.

It is easy to generate comma-delimited data from commonly available software. This chapter cannot possibly cover the database engines and display options for all the software packages out there in the software world. Finally, given the examples in this chapter, you can easily extend the methods learned from applying them to your own databases.

You already learned the basic principles in Chapter 22, "Using HTML FORMs with Perl CGI Scripts," concerning the collecting of responses from an HTML form. Here are the basic steps involved:

  1. Present the form for user input. This is covered in Chapter 22.
  2. Collect the information requested based on user responses. I have covered this procedure using standard Perl scripts. In this chapter, I extend the procedure to using modules.
  3. Present the data back to the user. After reading this chapter, you should know how to send back the responses as simple HTML pages. I cover how to present the data back in the form of tables in this chapter.

Collecting User Input Using Perl Modules

In this chapter I cover how to use existing Perl module extensions to collect user input in a CGI script. The best way to show something is by example. Refer to the Perl script in Listing 22.7 (in Chapter 22) for processing a very simplified credit card application. We had to go through several steps for extracting the data from the environment variables and setting the internal variables in the handler script. What if this was not necessary? That's when the CGI modules come in.

The CGI modules, CGI.pm and its related files, covered in this section are used in conjunction with HTML modules in the next section of this chapter. The file you need to install this package is called CGI-modules.2.75.tar.gz. You can get it from your nearest CPAN site. The author of this package is Lincoln Stein. Please convey your comments directly to him at lstein@genome.wi.mit.edu.

Untar and unzip the package file. You'll be left with a directory called CGI-modules.2.75 in the same directory. Move all the files in the /usr/lib/perl5/CGI-modules-2.75/CGI directory to the /usr/lib/perl/CGI directory. Now you're set to use the CGI modules stuff. Refer to the ./doc directory for more information.

Listing 23.1 illustrates how the same application could be rewritten using the CGI module extension for Perl.


Listing 23.1. A sample application rewritten with CGI modules.
 1 #!/usr/bin/perl
 2 #
 3 # The sample script file to show how to use
 4 # the CGI modules for FORM handling in Perl.
 5 #
 6 #
 7 use CGI::Base;
 8 use CGI::Request qw(:DEFAULT :cgi-lib);
 9 { package CREDIT;
10            $income='income';
11            $ssn='ssn';
12            $fname='fname';
13            $lname='lname';
14            $mname='mname';
15            $dependants='dependants';
16 }
17 print PrintHeader();
18 GetRequest('CREDIT');
19 #
20 # Now the variable $form has your input data.
21 # Create your associative array.
22 #
23 if (($CREDIT::income < '1') || ($CREDIT::income > '6')) {
24            $error = "Please specify your income range [$income]";
25            }
26 if (error eq "") {
27 if ($CREDIT::ssn =~ /[0-9]{3}-[0-9][0-9]-[0-9]{4}/)
28            {
29            $snumber = $CREDIT::ssn;
30            $snumber =~ s/\-//g;
31            }
32 elsif ( $CREDIT::ssn =~ /[0-9]{9}/) {
33            $snumber = $CREDIT::ssn;
34            }
35 else    {
36 $error = "Enter the social security number in the form XXX-XX-XXXX";
37            }
38 }
39 if ($CREDIT::fname eq ""  && error eq "") {
40            $error =  "Please enter your first name";
41            }
42 if ($CREDIT::lname eq "" && $error eq "") {
43            $error =  "Please enter your last name";
44            }
45 if ($CREDIT::mname eq "" && $error eq "") {
46            $error = "Your mother's maiden name is required";
47            }
48 if ($CREDIT::dependants < 1 && $error eq  "") {
49            $error = "Now, now, we have to be dependant on ourselves.";
50            }
51 #
52 #
53 print <<"HTMLHEAD";
54 <HTML>
55 <BODY>
56 <p>
57 HTMLHEAD
58 if ($error eq "")
59            {
60            print "\n <H2>Congratulations!</H2> ";
61            print "<P>Your application has been accepted";
62            print "<P>We will be living off your interest payments shortly";
63            }
64 else
65            {
66            print "\n <H2>Error!</H2> ";
67            print "\n <P>$error<P>";
68            print "\n <P>Please correct the error and retry";
69            }
70 print <<END;
71 <HR>
72 <B> Application you requested...</B><P>
73            SSN = $CREDIT::ssn <BR>
74              Name =        $CREDIT::fname  $CREDIT::lname: <BR>
75            Mom's Maiden Name = $CREDIT::mname; <BR>
76            With $CREDIT::dependants; dependants <BR>
77            Income code: $CREDIT::income; <BR>
78 <HR>
79 END
80 print FmtRequest();  append CGI variables to the form
81 print "</HTML></BODY>\r\n";
82 #

Let's examine some of the lines that show how the CGI module is being used. Lines 7 and 8 are used to declare that you intend to use the Base and Request classes of the CGI module. Both modules reside in the /usr/lib/perl5/CGI directory by default as Base.pm and Request.pm files, respectively. The CGI::Base class is required for all functions that you intend using in the CGI module. The CGI::Request class is required to parse incoming user input from QUERY_STRING in your CGI script.

The CGI::Base class transparently handles all the POST and PUT requests and reads from STDIN into QUERY_STRING. You have to parse the value of the environment variable QUERY_STRING yourself or use the CGI::Request class. CGI::Base automatically sets Perl variables with the same name with the environment variable value.

The CGI::Request does require the CGI::Base object for its initialization and subsequent use, even though it does not inherit any information from the CGI::Base object.

Lines 9 through 16 declare a package for parsing the incoming fields in the QUERY_STRING. The assignments are of the form variable = name in a query string. The local package CREDIT declares those Perl variables that are required by this script as well as what strings to use to extract those values.

Line 17 prints the response header for the HTML request back to the client. Line 18 takes the input from the client (in QUERY_STRING) and parses it into the members in the CREDIT package. Lines 23 through 69 process the error-handling for the CGI script as before. Lines 70 through 79 echo the members of the CREDIT package. Line 81 terminates the output from the CGI script. Terminating the CGI script destroys the CGI::Base and CGI::Request objects automatically. Line 80 is used for debugging and echoes all the environment variables set by the CGI::Base object. This output is very similar to Listing 20.9 in Chapter 20, "Introduction to Web Pages and CGI." In fact, given that Listing 20.9 is about 45 lines long, you can write a similar application that is much shorter using CGI class, as shown in Listing 23.2.


Listing 23.2. Echoing CGI environment variables.
1 #!/usr/bin/perl
2 use CGI::Base;
3 use CGI::Request;
4
5 print PrintHeader();
6 print FmtRequest();

It's complicated enough to write CGI scripts. It's worse to write lengthy Perl scripts to generate HTML for you. Using the CGI classes certainly takes some (not all) of the drudgery away.

In the next section, you'll use some more features of HTML to show data.

Using Tables in HTML

The HTML 3.2 specification allows for displaying data in a clean tabular form using HTML widgets. The possibilities of showing data in a nice tabular format are tremendous.

Tables in HTML pages are in the following form:

<TABLE BORDER>
<TH> Header  Column 1 </TH>
<TH> Header  Column 2 </TH>
<TH> Header  Column 3 </TH>
...
<TR>
<TD> Row 1 Column 1 </TD>
<TD> Row 1 Column 2 </TD>
<TD> Row 1 Column 3 </TD>
<TD> Row 1 Column 4 </TD>
...

<TR>
<TD> Row 2 Column 1 </TD>
<TD> Row 2 Column 2 </TD>
<TD> Row 2 Column 3 </TD>
<TR>
  ...
</TABLE>

The <TABLE> and </TABLE> tags delimit the table. The BORDER attribute instructs the browser to put lines around the cell. If you do not want borders around the cells, omit the BORDER attribute. The table will be as wide as the width of all the columns. Browsers adjust the width of columns to accommodate all the text as best they can. The ALIGN attribute can take one of three values to align the text in a table cell: left, right, or center (the default).

The data in between the table data tags, <TD> and </TD>, is for the cell at a current row number. Rows start at every row tag, <TR>. The table header tags, <TH> and </TH>, specify the titles in the columns in the first row. Table data input is finished with the </TABLE> tag.

If not enough headers are specified, the headers for the table will be empty. Note that the first row has four columns, but only three headers. Therefore, the fourth column will not have a heading.

The Table widget provides other nifty features such as column spanning, where the colspan attribute determines the number of columns a heading or item will span. The rowspan attribute specifies the number of rows an item will span. Some sample code is shown in Listing 23.3.


Listing 23.3. Using row and column spanning.
 1 <html><head>
 2 </head>
 3
 4 <body>
 5 <center><h1>Show Tables</h1></center>
 6 <p>
 7 <hr>
 8 <B> Column Span </B>
 9 <hr>
10 <TABLE BORDER>
11 <TH> Column 1 </TH>
12 <TH> Column 2 </TH>
13 <TH colspan=2> Column 3 and 4 </TH>
14
15 <TR>
16 <TD> Row 1 Column 1 </TD>
17 <TD> Row 1 Column 2 </TD>
18 <TD> Row 1 Column 3 </TD>
19 <TD> Row 1 Column 4 </TD>
20
21 </TABLE>
22 <P>
23 <HR>
24 <B> Row Span </B>
25 <hr>
26 <TABLE BORDER>
27 <TH rowspan=3> Column 1 </TH>
28 <TH> Column 2 </TH>
29 <TH> Column 3 </TH>
30 <TR>
31 <TD> Row 1 Column 1 </TD>
32 <TD> Row 1 Column 2 </TD>
33 <TR>
34 <TD> Row 2 Column 1 </TD>
35 <TD> Row 2 Column 2 </TD>
36 <TR>
37 <TD> Row 3 Column 1 </TD>
38 <TD> Row 3 Column 2 </TD>
39 <TD> Row 3 Column 3 </TD>
40 <TR>
41 <TD> Row 4 Column 1 </TD>
42 <TD> Row 4 Column 2 </TD>
43 <TD> Row 4 Column 3 </TD>
44 </TABLE>
45  4647 <P>
46 <HR>
47 <B> Row  and Column Span </B>
48 <hr>49 <TABLE BORDER>
50 <TH rowspan=2> Column 1 </TH>
51 <TH colspan=2> Column 2  </TH>
52 <TR>
53 <TD> Row 1 Column 1 </TD>
54 <TD> Row 1 Column 2 </TD>
55 <TR>
56 <TD> Row 2 Column 1 </TD>
57 <TD> Row 2 Column 2 </TD>
58 <TR>
59 <TD> Row 3 Column 1 </TD>
60 <TD> Row 3 Column 2 </TD>
61 <TD> Row 3 Column 3 </TD>
62 <TR>
63 <TD> Row 4 Column 1 </TD>
64 <TD> Row 4 Column 2 </TD>
65 <TD> Row 4 Column 3 </TD>
66 </TABLE>
67
68 </body></html>

Now that you know how to put data in a table for an HTML page, let's see what we can display using these tables. For this example, you will display the statistics of which pages on the server get hit the most. This way you can gauge what the most popular items are on the Web site.

The statistics for the number of hits per file, including the date and IP number of the requesting server, are kept in a file called access_log in your Web server's logs directory. This is the file to look at if you want to know which file has been hit the most. The location of this file is set when your Web server is configured. You can find this file with the find command (find / -name access_log -print) if you are not sure where to look.

Rather than write a whole statistics utility from scratch, you can use existing tools to get the information from access_log. In this example, you use getstats. The getstats program was written by Kevin Hughes at Enterprise Integration Technologies (www.eit.com). Get it via FTP from ftp.eit.com/web.software/getstats or from http://www.eit.com/goodies/software/getstats/src/statform.html. (It's available in source form only, and you have to use GNU's gcc to compile it. The source might be called getstats.XX.c, where XX is the version number. The version I work with in this section is 12.)

One word of caution before you build the file: determine the type of server you are running and the type of format your access_log is in. The most common server is the ncSA server. Chances are that your access_log looks like this:

crow.lib.uh.edu - - [04/Mar/1995:16:28:39 -0600] "GET /iistv.html
            HTTP/1.0" 200 850

The first item is the name of the calling browser, followed by a hyphen or an IP number, and then either a user name or a hyphen. Within the square brackets is the time of access, followed by the method of access and the file accessed. The HTTP server version number is listed next. Then the server result code is shown, followed by the number of bytes sent back. This is known as the COMMON format.

If your log file is different from this one, determine the type of server you have and get the correct tool for it. In almost all cases, getstats will work for you, so try it anyway. You may be pleasantly surprised.

Tip
A search on the words Perl, statistics, and Web in the Netscape browser will produce some very interesting links to programs similar to getstats. A good statistics program with a graphical output is gwstats.

Let's now work with the getstats program to see how to use it. Before you can use it, you have to set some values for the getstats program sources and then recompile it.

First of all, in the getstats.12.c source file you have to set #define COMMON to 1, not the value 0, which is the way it's delivered. Also, be sure to define the location of the files in your WWW directory, especially the name of your server and the location of the access_log file. Creating the program is as easy as typing the following command:

gcc getstats.12.c -o getstats.

Ignore any warnings you get with the gcc compiler. The warnings, if any, are harmless and are about comparing an integer with a pointer.

To get all the statistics, you just type getstats at the prompt. If you do not get any output or the program appears to hang, make sure you have defined the COMMON value to 1 and then recompile.

The output from the getstats program is long and verbose, depending on what files you have on your system. I am particularly interested in the following section of output:

HTTP Server Request Statistics
Covers: 07/18/95 to 03/09/96 (236 days).
All dates are in local time.
Sorted by request name, 11 unique requests.

# of requests : Last Access (M/D/Y) : Request
----------------------------------------------

 6 : 08/25/95 : /bosnia1.htm
 2 : 03/09/96 : /cgi-bin/cgipm.pl
 1 : 03/09/96 : /cgi-bin/query.pl
20 : 10/04/95 : /cons2.htm
33 : 10/17/95 : /eit.home.html
 1 : 03/09/96 : /euromap.gif
 1 : 03/09/96 : /iistv.html
13 : 10/17/95 : /index.html
 1 : 03/09/96 : /paktravel.html
49 : 10/11/95 : /training.htm
 2 : 03/09/96 : /x.html

----------------------------------------------

...

The Perl script to extract this section of code is shown in Listing 23.4. The variable $a is assigned the returned string from the getstats command.


Listing 23.4. The Perl script to get the needed information.
 1 #!/usr/bin/perl
 2
 3 $a = 'getstats';
 4
 5 $found = 0;
 6 foreach $x (split('\n', $a)) {
 7             if ($x =~ /\#/) {
 8             $x = "";   # just ignore the header and any comments
 9             }
10             if ($x =~ /(.*) : (.*) : (.*)/ )  {
11             $x =~ s/ //g; # remove all extranous spaces.
12             print $x . "\n";
13             }
14 }

Here's the output from the script in Listing 23.4:

6:08/25/95:/bosnia1.htm
2:03/09/96:/cgi-bin/cgipm.pl
1:03/09/96:/cgi-bin/query.pl
20:10/04/95:/cons2.htm
33:10/17/95:/eit.home.html
1:03/09/96:/euromap.gif
1:03/09/96:/iistv.html
13:10/17/95:/index.html
1:03/09/96:/paktravel.html
9:10/11/95:/training.htm
2:03/09/96:/x.html

The strange construct in the if clause, ($x =~ /(.*) : (.*) : (.*)/ ), looks for three words separated by colons. The ~= does the search-and-replace operation to eliminate any extra white space.

Displaying statistics in a tabular form in HTML involves taking the output now safely stored in colon-delimited strings. This involves modifying the script to include printing out the correct table tags along with the data.

The script is now modified to display table tags along with the returned data from the getstats program. The modified script is shown in Listing 23.5.


Listing 23.5. The modified Perl listing to include table information.
 1 #!/usr/bin/perl
 2 #
 3 # Return
 4 #
 5
 6 $|=1;            # Flush output back immediately.
 7
 8 #
 9 # Return the type of document.
10 #
11 print "Content-type: text/html\n\n";
12
13 $date = 'date';
14 print <<"HTMLHEAD";
15 <HTML>
16 <HEAD>  <TITLE> Our Server Statistics </TITLE>
17 </HEAD>
18 <BODY>
19 <H1>The top 10 most recent files hit </H1>
20 <p>
21 <H2>Today's Date: $date </H2>
22 <p>
23 HTMLHEAD
24
25 $a = 'getstats';
26
27 print "\n <TABLE BORDER> ";
28 print "\n <TD> Hits </TD> ";
29 print "\n <TD> Last </TD> ";
30 print "\n <TD> Filename </TD> ";
31 # print $a;
32 $found = 0;
33 $ctr = 0;
34 foreach $x (split('\n', $a)) {
35             if ($x =~ /\#/) {
36             $x = "";   # just ingore it.
37             }
38             if ($x =~ /(.*) : (.*) : (.*)/ )  {
39             $x =~ s/ //g;
40             ($hits, $recent, $fname) = split(':',$x);
41             $ctr++;
42             if ($ctr < 10)  {
43        print "\n<TR><TD>$hits</TD>\n<TD>$recent</TD><TD>$fname</TD>";
44                         }
45             }
46 }
47 print "\n </TABLE> ";
48
49 #
50 # Okay finish the HTML document.
51 #
52
53 print <<"HTML";
54 <p>
55 <p>
56 </BODY></HTML>
57 HTML

The $| command in the script at line 6 forces the output back to the calling browser as soon as the print statement is executed. This keeps the browser from timing out at the other end if the execution of the getstats command takes too long.

The header and footer for the HTML document are generated from the statements print <<"HTMLHEAD" and print <<"HTML", respectively. See line 53 for an example. Everything between print and its terminating words (for example, HTMLHEAD) is printed verbatim. This keeps me from having to type many print commands.

Also, note that I use a counter called $ctr in this script to limit the output (line 42) to only 10 rows. The machine I work on does not get hit this often, nor does it have that many files to offer. Your site may have a lot more hits per file. Therefore, in order to limit the output, you might want to keep this number to a reasonable value.

Now you know how to display data in a table. There is much more to displaying data in a tabular form that simply cannot be covered in one chapter alone. Please refer to the online documentation for writing HTML pages.

Using the HTML Module

HTML::Base is an expansion module for Perl 5 that provides an object-oriented way to build HTML pages. Its purpose is to create HTML 2.0 tags, plus a few tags from the HTML 3.0 standard, including the Table tags. The package comes with documentation in the html_base.pod file.

Note
The HTML::Base module is copyrighted 1995 by Anderson-Coates under the same terms as Perl itself. This program is free software; you can redistribute it and/or modify it. Please read all accompanying notices. The author of this package is Greg Anderson of Anderson-Coates, a consulting firm specializing in professional Internet software and services. Contact Mr. Anderson directly by e-mail at greg@acoates.com or through his Web site at http://www.acoates.com/.

Using this package shields you from a lot of the nuances of HTML syntax. Basically, you should be able to use this package without worrying about the nitty-gritty details of HTML. For example, special characters such as the ampersand (&) are output as the correct ASCII escape character required for HTML-all you do is type in the ampersand in the text you want displayed. Plus, using the module lets you use the flexibility and language abstraction of Perl. You really do not need to learn the syntax for HTML to use the module described here. However, such knowledge is invaluable when debugging the output from a script using this module.

To install the HTML package, simply copy the file Base.pm to a subdirectory called HTML in whatever directory you use to store Perl 5 modules. For example, if your Perl 5 modules are in /usr/local/lib/perl5, you should create a subdirectory there called HTML and copy Base.pm into it, like this:

mkdir /usr/local/lib/perl5/HTML
cp Base.pm /usr/local/lib/perl5/HTML/Base.pm

Each object in the HTML::Base class represents a single instance of an HTML tag. An object whose class is defined by HTML::Base could be called an "HTML object."

The primary function of the HTML::Base module is to provide definitions and methods for classes of HTML objects. A base class, known as HtmlObject, is defined from which all other HTML objects are derived. All objects know where they are situated in the hierarchy of HTML objects that make up a page (or pages) of HTML. They also know how to realize (display) themselves.

Here are the steps involved in creating the document with this package:

  1. To use this HTML package, include the use HTML::Base; statement.
  2. Create a top-level object. There is only one "top" object in the system. This will become the current object until the next object is created.
  3. Create all other objects below the top object in a hierarchical fashion.
  4. Display the top-level object to generate the HTML output. All other objects below it will also show themselves.

A key point to keep in mind when working with any HTML object in the package is that you are always working with a "current" object. As you create more objects, they in turn become the current object. You can always make an object current by calling the make_current function. To go back up the hierarchy, you call the end_object function on each object that you want to be the default.

HTML objects are created using the new function. Each newly created object becomes the current object and is then the parent of the next object created. This chain of parenthood continues until an object is ended, or until another object is made the current object.

When an entire hierarchy of HTML objects has been created, it must be realized (or displayed). Realization is when the objects may be told to output the appropriate HTML for their object classes. The output is sent either to standard output or to a file. Listing 23.6 presents a modified example of what comes with the documentation.


Listing 23.6. A sample HTML package usage.
 1 #!/usr/bin/perl
 2 use HTML::Base;
 3
 4 # Start the HTML, create a <BODY> tag
 5 $body = new HTML::Base::Body;
 6
 7 # Create an <H1> Heading
 8 new HTML::Base::Header 1;
 9
10             # Add some text to the header
11             new HTML::Base::Text "Header Level 1 with Image";
12
13             # Add an image to the header
14             new HTML::Base::Image ('SRC','notepad.gif');
15
16 # Make the body current again
17 $body->make_current;
18
19             # Add a paragraph to the body
20             new HTML::Base::Paragraph;
21
22 # Add some text to the paragraph
23 new HTML::Base::Text "This is a paragraph";
24
25 # Output everything to stdout.
26 $body->realize;

Here is the output from the HTML module:

<BODY>
<H1>
Heading r Level 1 with Image
<IMG SRC="notepad.gif"></H1>
<P>
This is a paragraph
</P>
</BODY>

To use HTML::Base in your Perl 5 program, include the following use command in the beginning of your program:

use HTML::Base;

HTML::Base exports no subroutine names into your program's name space.

All objects that can output an HTML tag are derived from subclasses of the class created by the base. Each HTML object knows how to display itself and how to use fields called attributes in the display process.

Each HTML object knows which attributes to recognize and will ignore all strings. It is okay to give your own attributes to HTML objects during their construction as long as their names do not conflict with any of the standard HTML attributes.

Constructing HTML Objects

HTML objects are constructed using the new function. The simplest case is an HTML object that needs no attributes:

$line = new HTML::Base::HorizontalRule;

This creates a line tag, making it the child of the current HTML object. After construction, the new object becomes the current object; therefore, the next HTML object to be constructed will be the child of this HorizontalRule object. You can prevent this from happening by calling

$line->HTML::Base::end_object()

This call to the end_object() function will set the current object as the parent of the line object.

Some HTML objects must have a mandatory first parameter specified. For example, the HTML headings come in six flavors (numbered 1-6); therefore, to create a Header object you can use either one of the following two lines:

$h2 =  new HTML::Base::Header 2;

$h2 =  new HTML::Base::Header 'Level' => 2;

This creates a Level-2 heading as the child of the current HTML object. All HTML objects will accept attributes. It is assumed that the attributes (if any) will follow any required parameters in the new call and take the form of simple key-value pairs, like this:

new HTML::Base::Anchor ('HREF','http://www.ikra.com/',
            'Name'  => "Hello");

HREF is recognized as a valid attribute. Name is not used by the object because it's not all uppercase letters.

Those HTML objects that do recognize attributes expect them to be set in the constructor. Consider the following line, which creates an HTML image reference:

new HTML::Base::Image
            ('SRC','pictures/Goofy.gif',
            'ALT','Goofy.pix',
            'ALIGN','MIDDLE');

An image tag is created with the given SRC, ALT, and ALIGN attributes. Note that all attributes are in capitals. Lowercase and mixed-case letters for attribute names will cause the attribute to be ignored.

Specifying the Body of the Text in HTML Documents

Use the HTML::Base::Text portions for implementing regular text in HTML. There are three attributes for this object: Text, Eval, and Verb. The output from all three is in the form of a paragraph.

Text is a special-purpose HTML object that has no HTML tag associated with it. Instead, it is meant to contain the text that makes up the actual content of the HTML document. A Text object that is a child of an HTML object will output its text within the scope of the HTML tags of its owner.

When being passed to the HTML::Base::Text constructor, the text to be displayed must be the first parameter, preceding any attributes to be set. The text may also be passed in as the attribute 'Text', but if specified like this, it must be the first attribute given.

All three of the following lines are equivalent:

new HTML::Base::Text "This is my text";

new HTML::Base::Text ('Text', 'This is my text');

new HTML::Base::Text Text => 'This is my text';

By default, the text is sanitized for HTML when an object is being realized and makes the text HTML easier to read by translating special HTML characters (such as &) into their HTML escape equivalents.

Two other attributes are defined for the Text object. If Verb is defined in the constructor, then the text will not be processed in any way or form before being output. This allows you to pump out raw text "as is" to the HTML document. You are responsible for the sanity of such code. This is useful for sending code samples as part of output.

Similarly, if Eval is defined, the text is first passed to the Perl eval() function. The output of that call is sent, unfiltered, to the output stream. The value of Eval is set to 1 for evaluation to take place.

Controlling the Output Destination File

By default, all HTML output by the objects is directed to STDOUT. This can be changed using the OUTPUTFILE attribute of the Page object, which creates the <HTML> and </HTML> tags and takes the attributes OUTPUTFILE and OUTPUTMODE.

The OUTPUTMODE attribute can be set to appeND or OVERWRITE. Thus, HTML::Base::Page not only outputs the <HTML> and </HTML> tags, but also controls the file handle to which output for a particular page of HTML is sent.

Each Page object tracks its own output file handle. This allows you to nest Page objects in a hierarchy (if you want to). Here is the segment of code to track the page it's writing to and its respective output.

$page = new HTML::Base::Page ('OUTPUTFILE','first.html');
    new HTML::Base::Text "This is being written to first.html!";
    new HTML::Base::Page ('OUTPUTFILE','second.html');
    new HTML::Base::Text "This is being written to second.html!";
$page->make_current;
    new HTML::Base::Text "This also is being written to first.html!";
$page->realize;

Here is the way to look at the output in two different HTML files:

$ more first.html
<HTML>
This is being written to first.html!
This also is being written to first.html!
</HTML>
$ more second.html
<HTML>
This is being written to trasho.html!
</HTML>

Using the Tables Feature in HTML::Base

The 0.6 release of the HTML::Base includes support for generating tables for HTML 3.0 and later. See Listing 23.7 for an example. Note in Listing 23.7 how each table row object is created and then ended before a new one is created. The end step is not necessary when creating data items because the object is smart enough to figure out which parent to use. Note ending </TR> tags in this output. This does not affect the output in any way with Netscape, although this is not the "right" way to generate the table row end tags. I cover the correct way to end these <TR> objects in Listing 23.8.

Tip
If Listing 23.9 does not work, you may have to fix it yourself. The 0.6 release of the HTML::Base also had a minor bug in it. The references in the Base.pm file should be
my $self = new HTML::Base::BinaryTag ("TABLE",@_);
instead of
my $self = new BinaryTag ("TABLE",@_);


Listing 23.7. Using tables in HTML::Base.
 1 #!/usr/bin/perl
 2
 3             use HTML::Base;
 4
 5             # Start the HTML, create a <BODY> tag
 6             $body = new HTML::Base::Body;
 7
 8             # Create an <H1>
 9             new HTML::Base::Header 1;
10
11             # Add some text to the header
12             new HTML::Base::Text "This is a header";
13
14             # Add an image to the header
15             new HTML::Base::Image ('SRC','notepad.gif');
16
17             # Make the body current again
18             $body->make_current;
19             # Add a paragraph to the body
20             new HTML::Base::Paragraph;
21
22             # Add some text to the paragraph
23             new HTML::Base::Text "This is a paragraph";
24
25             $outtable = new HTML::Base::Table ('BORDER', ' ');
26             $h = new HTML::Base::TableHeader;
27             new HTML::Base::Text "Header 1";
28             $h->end_object;
29
30             $h = new HTML::Base::TableHeader;
31             new HTML::Base::Text "Header 2";
32             $h->end_object;
33
34             new HTML::Base::TableRow ;
35             $r = new HTML::Base::TableData ;
36             new HTML::Base::Text "Row 1 Col 1";
37
38             $r = new HTML::Base::TableData ;
39             new HTML::Base::Text "Row 1 Col 2";
40
41             new HTML::Base::TableRow ;
42
43             $r = new HTML::Base::TableData ;
44             new HTML::Base::Text "Row 2 Col 1";
45
46             $r = new HTML::Base::TableData ;
47             new HTML::Base::Text "Row 2 Col 2";
48
49             $outtable->end_object;
50
51             # Output everything
52             $body->realize;

Here is the output for the HTML tables in the output file:

<BODY>
<H1>
This is a header
<IMG SRC="notepad.gif"></H1>
<P>
This is a paragraph
<TABLE BORDER=" ">
<TH>
Header 1
</TH>
<TH>
Header 2
</TH>
<TR>
<TD>
Row 1 Col 1
</TD>
<TD>
Row 1 Col 2
</TD>
<TR>
<TD>
Row 2 Col 1
</TD>
<TD>
Row 2 Col 2
</TD>
</TR>
</TR>
</TABLE>
</P>
</BODY>

Sure, the output does not look pretty as far as HTML pages go. However, the code generating this HTML output is abstracted from the HTML implementation below it. If the HTML specification is upgraded, the package optimized, or the module otherwise enhanced, then our Perl scripts would not be affected as long as the interface is kept consistent.

Let's rewrite the usage of the getstats module with the CGI and HTML modules (see Listing 23.8). The placement of the <TR> and </TR> tags is now correct because the row object is ended correctly. Contrast the output of this listing with the output from Listing 23.9. You'll see how the </TR> tags are matched when objects are ended and how they output one long list when objects are not ended correctly.

As a rule, if you create a row, you must end it.


Listing 23.8. A rewrite of the getstats script using Perl modules.
 1 #!/usr/bin/perl
 2 #
 3 # Return Statistics using Perl Modules.
 4 #
 5 use CGI::Base;
 6 use CGI::Request qw(:DEFAULT :cgi-lib);
 7
 8 print PrintHeader();
 9
10 use HTML::Base;
11
12 # Start the HTML, create a <BODY> tag
13 $body = new HTML::Base::Body;
14
15 # Create an <H1> header
16 new HTML::Base::Header 1;
17
18 # Add some text to the header
19 $date = 'date';
20 new HTML::Base::Text "The top 10 most recent files hit as of $date";
21
22 # Make the body current again
23
24 $body->make_current;
25 # Add a paragraph to the body
26
27 new HTML::Base::HorizontalRule;
28 new HTML::Base::Paragraph;
29
30 $a = '/usr/local/bin/getstats';
31
32 #
33 # Remove the following lines
34 #
35 #print "\n <TABLE BORDER> ";
36 #print "\n <TD> Hits </TD> ";
37 #print "\n <TD> Last </TD> ";
38 #print "\n <TD> Filename </TD> ";
39
40 $outtable = new HTML::Base::Table ('BORDER', '');
41
42 $h = new HTML::Base::TableHeader;
43 new HTML::Base::Text "Hits";
44 $h->end_object;
45
46 $h = new HTML::Base::TableHeader;
47 new HTML::Base::Text "Last";
48 $h->end_object;
49
50 $h = new HTML::Base::TableHeader;
51 new HTML::Base::Text "Filename";
52 $h->end_object;
53
54 $found = 0;
55 $ctr = 0;
56 foreach $x (split('\n', $a)) {
57             if ($x =~ /\#/) {
58             $x = "";   # just ignore it.
59             }
60             if ($x =~ /(.*) : (.*) : (.*)/ )  {
61             $x =~ s/ //g;
62             ($hits, $recent, $fname) = split(':',$x);
63             $ctr++;
64             if ($ctr < 10)   {
65             # print "\n<TR><TD>$hits</TD>\n<TD>$recent</TD><TD>$fname</TD>";
66              $r =new HTML::Base::TableRow ;
67             $h = new HTML::Base::TableData ;
68               new HTML::Base::Text " $hits";
69               $h->end_object;
70               $h = new HTML::Base::TableData ;
71               new HTML::Base::Text " $recent";
72               $h->end_object;
73               $h = new HTML::Base::TableData ;
74               new HTML::Base::Text " $fname";
75               $h->end_object;
76               $r->end_object;
77               }
78             }
79 }
80 $outtable->end_object;
81 #
82 # Okay finish the HTML document.
83 #
84 $body->realize;

Here's the output of the getstats rewrite:

Content-type: text/html

<BODY>
<H1>
The top 10 most recent files hit as of Sun Feb 4 16:55:30 CST 1996

</H1>
<HR>
<P>
<TABLE BORDER>
<TH>
Hits
</TH>
<TH>
Last
</TH>
<TH>
Filename
</TH>
<TR>
<TD>
 57
</TD>
<TD>
 01/26/96
</TD>
<TD>
 /cgi-bin/travel.pl
</TD>
</TR>
<TR>
<TD>
 56
</TD>
<TD>
 02/02/96
</TD>
<TD>
 /index.html
</TD>
</TR>
<TR>
<TD>
 43
</TD>
<TD>
 02/02/96
</TD>
<TD>
 /cgi-bin/credit.pl
</TD>
</TR>
<TR>
<TD>
 42
</TD>
<TD>
 02/04/96
</TD>
<TD>
 /cgi-bin/test-cgi
</TD>
</TR>
<TR>
<TD>
 14
</TD>
<TD>
 02/02/96
</TD>
<TD>
 /credit.html
</TD>
</TR>
<TR>
<TD>
 12
</TD>
<TD>
 02/02/96
</TD>
<TD>
 /training.htm
</TD>
</TR>
<TR>
<TD>
 11
</TD>
<TD>
 01/24/96
</TD>
<TD>
 /pubs.html
</TD>
</TR>
<TR>
<TD>
 10
</TD>
<TD>
 02/02/96
</TD>
<TD>
 /mfc.html
</TD>
</TR>
<TR>
<TD>
 10
</TD>
<TD>
 12/28/95
</TD>
<TD>
 /cgi-bin/test-cgi.pl
</TD>
</TR>
</TABLE>
</P>
</BODY>

Making Clickable Images in HTML

Pictures often convey more information than do gobs of text. Sometimes a picture or graph can describe data better than a table. HTML documents allow you to display GIF or JPEG images in documents. With CGI, you can even have "hot" portions of a GIF image so that clicking the hot area of the image produces input from the client to the server. Currently, the images have to be in the GIF format.

To make an image "clickable," you have to define regions on the image in the form of rectangles, circles, or other closed polygons. The coordinates within each defined region are then associated with an URL to follow if the click happens to be in that region. The mapping of an image click to coordinates is done through a program called image map.

If you do not have the image map executable on your machine, you have to install it yourself. This installation is simpler than it sounds. For UNIX systems, your httpd daemon software should untar itself with a cgi-src directory containing the source for image map. For a CERN server, this file is called htimage, but the building and installation is very similar. Edit the source file to point to the location of your server's root tree and make the executable using the makefile provided with the server software.

The image map program on almost all UNIX systems requires a file called imagemap.conf. The location of this file is set in the image map executable. If you are making the imagemap file, you have to edit the CONF_FILE constant to specify the location of the imagemap.conf file in the source file. The default line is shown here:

#define CONF_FILE "/usr/local/etc/httpd/conf/imagemap.conf"

The imagemap.conf file is a text file with all mappings as one item per line. The items in the imagemap.conf file have two text parts each. The first part is the name of the GIF file with the extension replaced with a colon. The second part is the absolute path to the map file for the image. Therefore, notepad.gif would have this entry on my system:

notepad: /usr/local/etc/httpd/htdocs/notepad.map

By convention, the image and the map file share the same base name. The .gif extension is for the image, and the .map extension is for the map file. The map file is a text file as well, containing the methods to use when mapping mouse clicks on the image. One method is defined per line. Each method in the map file therefore defines the hot spots for the image. Each method is of the form

method URL coordinate1 coordinate2 .... coordinateN

where coordinates take the form x,y. The number of coordinates depends on the type of hot spot. If regions overlap in a mapping file, the first region hit is returned.

Here are the types of methods:

circle Defines a circle defined by the center point followed by a point on the circumference.
rect Defines a rectangle defined by the upper-left and lower-right corner coordinates.
poly Defines a polygon defined by coordinate pairs defining each vertex. The limit is 100 vertices.
point Defines a region around a point defined by the coordinate. It's not very useful unless you have very fine resolution images.
Default This is the catch-all of all regions not defined in the previous mapping methods.

A sample mapping is shown in Listing 23.9 for a notepad.gif image. In the image itself, you have to add the URL as shown in Listing 23.9. Note that the HREF URL for this image ends in imagemap/image-name.

point http://www.ikra.com/cgi-bin/pointer.pl   5,5
rect  http://www.ikra.com/cgi-bin/makesquare.pl   15,5   25,25
circle http://pop.ikra.com/cgi-bin/round.html  50,50 50,70
poly  http://www.ikra.com/pointy/noke.html  200,10 200,100, 150,50
default http://ikra.com/cowdunga.html

Listing 23.9. The URL for the mapping.
 1 <html>
 2 <body>
 3 <TITLE>Clickable images Alignment</TITLE>
 4 <p>
 5 <HR>
 6 <A HREF="http://www.ikra.com/cgi-bin/imagemap/notepad">
 7 <IMG SRC="notepad.gif" ISMAP> </A>
 8 <HR>
 9 </body>
10 </html>

Summary

This chapter has been an introduction to some of the techniques available to you for presenting data with CGI Perl modules. I covered ways of abstracting your CGI scripts from server implementations by using Perl modules. I also covered how to show data in a table and how to collect user input via images. The two modules covered in this chapter include the CGI and HTML modules available from the CPAN archives at http://www.perl.com. Both modules provide a clean interface for your CGI scripts and can also be used to generate your own HTML documents.