ClassParser(3) User Contributed Perl Documentation ClassParser(3)
NAME
"Apache::HTML::ClassParser" - Apache mod_perl extension
for generating dynamic HTML pages based on "CLASS"
attributes
SYNOPSIS
# In Apache's httpd.conf file:
AddType text/html .chtml
SetHandler perl-script
PerlHandler +Apache::HTML::ClassParser
DESCRIPTION
"Apache::HTML::ClassParser" is yet another "mod_perl"
Apache module for dynamically generating HTML. Its dis
tinctive feature, unlike existing techniques, is that it
uses pure, standard HTML files: no print-statement-laden
CGI scripts, no embedded statements from some programming
langauge, and no pseudo-HTML elements. Code is cleanly
separated into a separate file. What links the two
together are "CLASS" attributes for HTML elements.
CONFIGURATION
In order to have "ClassParser" be a handler for HTML
files, configuration directives, such as those shown in
the SYNOPSIS, must be added to Apache's "httpd.conf" file.
The HTML files to be handled by "ClassParser", as with all
Apache handler modules, can be specified either by loca
tion, filename extension, or both. For more detail on
Apache configuration directives, see [Apache].
Cooperation with "Apache::Filter"
"ClassParser" is "Apache::Filter"-aware. However, if used
in a filter chain, it must be the first filter. (This is
because it uses the "HTML::Tree" module that uses mmap(2)
to read an HTML file.)
For example, to configure Apache to have HTML files run
through "ClassParser" then "Apache::SSI" (server-side
includes), do:
PerlModule Apache::Filter
PerlModule Apache::HTML::ClassParser
PerlModule Apache::SSI
AddType text/html .chtml
SetHandler perl-script
PerlSetVar Filter On
PerlHandler Apache::HTML::ClassParser Apache::SSI
TERMINOLOGY AND CONVENTIONS
Element vs. Tag
It is often the case that the term HTML "tag" is used when
the correct term of "element" should be. From the HTML
4.0 specification, section 3.2.1, "Elements":
Elements are not tags. Some people refer to elements
as tags (e.g., "the "P" tag"). Remember that the ele
ment is one thing, and the tag (be it start or end
tag) is another. For instance, the "HEAD" element is
always present, even though both start and end "HEAD"
tags may be missing in the markup.
In this documentation, the distinction between "element"
and "tag" is necessary.
Class vs. "CLASS"
In this documentation, there are unfortunately two mean
ings of the word "class":
1. A class attribute of an HTML element, e.g.:
Introduction
where "CLASS"es are typically used to convey style
information.
2. A Perl class from which objects are created, e.g.:
package MyClass;
sub new {
my $class = shift;
my $this = {};
return bless $this, $class;
}
$object = MyClass->new();
(See [Wall], pp. 290-292.)
Therefore, throughout this document, "class" written as
""CLASS"" shall mean the class attribute of an HTML ele
ment (case 1) and "class" written simply as "class" shall
mean a Perl class (case 2).
The HTML File
The file for a web page is in pure HTML. (It can also
contain JavaScript code, but that's irrelevant for the
purpose of this discussion.) At every location in the
HTML file where something is to happen dynamically, an
HTML element must contain a "CLASS" attribute (and perhaps
some "dummy" content). (The dummy content allows the web
page designer to create a mock-up page.)
For example, suppose the options in a menu are to be
retrieved from a relational database, say the flavors
available on an ice cream shop's web site. The HTML would
look like this:
The "CLASS"es "query_flavors" and "next_flavor" will be
used to generate HTML dynamically. The values of the
"CLASS" attributes can be anything as long as they agree
with those in the code file (specified later). The text
"Tooty Fruity" is dummy content.
The "query_flavors" "CLASS" will be used to perform the
query from the database; "next_flavor" will be used to
fetch every tuple returned from the query and to substi
tute the name and ID number of the flavor.
The value of a "CLASS" attribute may contain multiple
classes separated by whitespace. (More on this later.)
The Code File
For every HTML file that is to be used with this tech
nique, there must be an associated code file in Perl. It
must have the same name as the HTML file except that the
extension is ".pm" rather than ".chtml". (There is an
exception; see "Using a Different Code File via "pm_uri""
below.) That code file must define its own package
(a.k.a. module), e.g.:
# ice_cream.pm
package IceCream;
to implement a class; that class must have a constructor.
The Constructor and "class_map"
A package requires a constructor method that must be named
"new()". A minimal such constructor (for which most of
the code is taken from [Wall], p. 295) is:
sub new {
my $that = shift;
my $class = ref( $that ) || $that;
my $this = {
class_map => {
query_flavors => \&query_flavors,
next_flavor => \&next_flavor,
},
# other stuff you want here ...
};
return bless $this, $class;
}
A second requirement is that the object's hash must con
tain a "class_map" key whose value is a reference to a
hash containing a mapping from "CLASS" attribute values
from the HTML file to functions (methods of the class) in
the Perl file.
This is the key concept: it is the "class_map" that links
the HTML file to the Perl code.
In the above constructor, the "CLASS" names "query_fla
vors" and "next_flavor" both map to methods having the
same name. In practice, this probably will (and should)
be the case; however, there is no requirement that it be
so. This allows more than one "CLASS" name to map to the
same method. (If there were such a requirement, there
would be no need for the "class_map".)
Class Methods
Class methods are passed the following arguments:
""$this"" A reference to an object of a class.
""$node"" A reference to an HTML element node, e.g.,
"SELECT".
""$class"" The value of the "CLASS" attribute of the
HTML element the method is being called
for. This is useful if more than one class
maps to the same method.
""$is_end_tag""
True only when the method is being called
for the end tag of an HTML element.
Class methods must return a Boolean value (zero or non-
zero for false or true, respectively). There are two
meanings for the return value; they are the same as for
visitor functions used by HTML::Tree. Repeated here for
convenience, they are:
1. If the $is_end_tag argument is false, returning false
means: do not visit any of the current node's child
nodes, i.e., skip them and proceed directly to the
current node's next sibling and also do not call the
visitor again for the end tag; returning true means:
do visit all child nodes and call the visitor again
for the end tag.
2. If the $is_end_tag argument is true, returning false
means: proceed normally to the next sibling; returning
true means: loop back and repeat the visit cycle from
the beginning by revisiting the start tag of the cur
rent element node (case 1 above).
The implementation of the "query_flavors()" and "next_fla
vor()" methods shall be presented in stages.
The "query_flavors()" method begins by getting its argu
ments as described above:
sub query_flavors {
my( $this, $node, $class, $is_end_tag ) = @_;
The query must be performed upon encountering the start
tag of the "SELECT" element; therefore, the method returns
false immediately if $is_end_tag is true. This tells
"ClassParser" not to proceed with parsing the "SELECT"
element's child elements again (in this case, the single
"OPTION" element) and to proceed to its next sibling ele
ment (i.e., the element after the "/SELECT" end tag):
return 0 if $is_end_tag;
The bulk of the code is standard DBI/SQL. A copy of the
database and statement handles is stored in the object's
hash so the "next_flavor()" method can access them later:
$this->{ dbh } = DBI->connect( 'DBI:mysql:ice_cream:localhost' );
$this->{ sth } = $this->{ dbh }->prepare( '
SELECT flavor_id, flavor_name
FROM flavors
ORDER BY flavor_name
' );
$this->{ sth }->execute();
(If the "Apache::DBI" module was specified ahead of
"ClassParser" in the Apache "httpd.conf" file, database
connections will be transparently persistent. See
[Stein], pp. 236-237.)
Finally, the method returns true to tell "ClassParser" to
proceed with parsing the "SELECT" element's child ele
ments:
return 1;
}
The "next_flavor()" method begins identically to
"query_flavors()":
sub next_flavor {
my( $this, $node, $class, $is_end_tag ) = @_;
The fetch of the next tuple from the query must be per
formed upon encountering the start tag of the "OPTION"
element; therefore, the method returns true immediately if
$is_end_tag is true. This tells "ClassParser" to loop
back to the beginning of the element, in this case to do
another fetch:
return 1 if $is_end_tag;
The next portion of code fetches a tuple from the
database. If there are no more tuples, the method returns
false. This tells "ClassParser" not to emit the HTML for
the "OPTION" element and also tells it to stop looping:
my( $flavor_id, $flavor_name ) = $this->{ sth }->fetchrow();
unless ( $flavor_id ) {
$this->{ sth }->finish();
$this->{ dbh }->disconnect();
return 0;
}
The code also disconnects from the database. (However, if
"Apache::DBI" was specified, the "disconnect()" becomes a
no-op and the connection remains persistent.)
The next portion of code substitutes content in the HTML
that will be emitted. The first line sets the value of
the "OPTION" element's "VALUE" attribute to be the "fla
vor_id" from the tuple:
$node->att( 'value', $flavor_id );
and the next line substitutes the text of the first child
node (in this case, the text "Tooty Fruity") for the "fla
vor_name" from the tuple:
$node->children()->[0]->text( $flavor_name );
Finally, the method returns true to tell "ClassParser" to
emit the HTML for the "OPTION" element now containing the
dynamically generated content:
return 1;
}
Other Stuff
Built-in CLASSes
There are several HTML manipulations that are performed
routinely; therefore, CLASSes to perform these manipula
tions are built-in without needing to be explicitly listed
in a "class_map".
All of the built-in CLASSes always return false when
called for an end tag; all but "if" and "unless" always
return true for a start tag.
""append::""attribute""::""key
Append to the value of an attribute of the current
element. The attribute is the name of the HTML
attribute whose value is to be appended to and key is
the key into $this that contains the value to be
appended. This example appends the value of
"$this->{ flavor_id }" to the "HREF" attribute:
Flavor details
THIS BUILT-IN CLASS IS DEPRECATED IN FAVOR OF
"sub_param". IT WILL BE REMOVED IN A FUTURE RELEASE.
""check::""key
Set the "CHECKED" attribute of the current "INPUT"
element for checkboxes and radio buttons only if a
value is true. The key is the key into $this that
contains the value to test. This example selects the
checkbox only if "$this->{ sprinkles }" is true.
Sprinkles
""check_value::""key
Set the "CHECKED" attribute of the current "INPUT"
element for checkboxes and radio buttons depending
upon the value of the "VALUE" attribute. The key is
the key into $this that contains the value to compare
against. This example selects the radio button only
where the "VALUE" is equal to "$this->{ flavor_id }":
Chocolate
Strawberry
Vanilla
""href::""key
This is a shorthand for "sub::href::"key since it
occurs frequently.
""href_id::""key
This is a shorthand for "sub_id::href::"key since it
occurs frequently.
THIS BUILT-IN CLASS IS DEPRECATED IN FAVOR OF
"href_param". IT WILL BE REMOVED IN A FUTURE RELEASE.
""href_param::""key
This is a shorthand for "sub_param::href::"key since
it occurs frequently.
""if::""key
Emit the HTML up to and including the end tag for the
current element only if a value is true. The key is
the key into $this that contains the value to be
tested. This example emits a table only if
"$this->{ results }" is true:
...
""select::""key
Set the "SELECTED" attribute of the current "OPTION"
element depending upon the value of the "VALUE"
attribute. The key is the key into $this that con
tains the value to compare against. This example
selects the option only where the "VALUE" is equal to
"$this->{ flavor_id }":
(This is just like "check_value", but for "OPTION"
elements.)
""sub::""attribute""::""key
Substitute the value of an attribute of the current
element. The attribute is the name of the HTML
attribute whose old value is to be substituted and key
is the key into $this that contains the new value to
be substituted. This example substitutes the value of
the "TYPE" attribute with the value of
"$this->{ form_type }":
""sub_id::""attribute""::""key
Substitute the "ID part" of the value of an attribute
of the current element. The attribute is the name of
the HTML attribute that contains the value whose "ID
part" is to be substituted and key is the key into
$this that contains the new ID to be substituted. The
"ID part" matches the pattern "\d+" within the value,
i.e., a numeric ID. This example substitutes the "1"
in the value of the "HREF" attribute with the value of
"$this->{ flavor_id }":
Go
THIS BUILT-IN CLASS IS DEPRECATED IN FAVOR OF
"sub_param". IT WILL BE REMOVED IN A FUTURE RELEASE.
""sub_param::""attribute""::""key
Substitute the "parameter" of the value of an
attribute of the current element. The attribute is
the name of the HTML attribute that contains the value
whose "parameter" is to be substituted and key is the
key into $this that contains the new parameter to be
substituted. The "parameter" matches the pattern
key"=[^&=]+" within the value, i.e., a name=value
pair. This allows multiple parameters to be substi
tuted. This example substitutes "joe" with the value
of "$this->{ user }" and "1" with the value of
"$this->{ pref_id }":
Go
(See Multiple CLASS Values below for information about
using multiple classes in the value of a "CLASS"
attribute.)
""text::""key
Substitute the text of the first child node (which
must be a text node). The key is the key into $this
that contains the text to be substituted. This exam
ple substitutes the text "Tooty Fruity" with the value
of "$this->{ flavor_name }":
Tooty Fruity
""unless::""key
Emit the HTML up to and including the end tag for the
current elemment only if a value is false. (This is
the opposite of "if::"key.) The key is the key into
$this that contains the value to be tested. (See the
example for "if::"key.)
""value::""key
This is a shorthand for "sub::value::"key since it
occurs frequently.
""value_id::""key
This is a shorthand for "sub_id::value::"key since it
occurs frequently.
THIS BUILT-IN CLASS IS DEPRECATED IN FAVOR OF
"value_param". IT WILL BE REMOVED IN A FUTURE
RELEASE.
""value_param::""key
This is a shorthand for "sub_param::value::"key since
it occurs frequently.
Multiple CLASS Values
The value of a "CLASS" attribute may contain multiple
classes separated by whitespace. When that is the case,
the function that each maps to is called in turn and the
return result is the logical-and of the functions. Just
as with Perl's "&&" operator, the first to return false
"short-circuits" the evaluation.
However, that is true only when the functions are being
called for the start tag; when being called for the end
tag, only the first function is called regardless of its
return value.
In light of this, the first example can be rewritten as:
This would call "next_flavor()" as before, but, if it
returns true, it would also call the remaining functions
in turn.
The "next_flavor()" function would also have to change to:
sub next_flavor {
my( $this, $node, $class, $is_end_tag ) = @_;
return 1 if $is_end_tag;
( $this->{ flavor_id }, $this->{ flavor_name } ) =
$this->{ sth }->fetchrow();
unless ( $this->{ flavor_name } ) {
# ... same as before ...
}
return 1;
}
The switch to using "$this->{ flavor_id }" and
"$this->{ flavor_name }" is necessary since all the built-
in CLASSes use "$this->{" key "}" for the values they use.
Because the built-in CLASSes are called to do the substi
tutions, the lines:
$node->att( 'value', $flavor_id );
$node->children()->[0]->text( $flavor_name );
that are present in the original version of "next_fla
vor()" have been removed.
Improved Performance via "bind_values()"
Although the following has more to do with DBI than
"ClassParser", it's presented here since it will improve
the overall performance when using the two together.
Generally, the DBI function "bind_values()" should be used
when retrieving multiple tuples from a database query.
The "query_flavors()" function can be rewritten to use it
as follows:
sub query_flavors {
# ... same as before ...
$this->{ sth }->bind_values(
\$this->{ flavor_id }, \$this->{ flavor_name }
);
return 1;
}
(See the DBI manual page for details.) In this case, the
result attributes must to be bound to "$this->{" key "}"
so the values can be accessed by the "next_flavor()" func
tion that can be rewritten as follows:
sub next_flavor {
my( $this, $node, $class, $is_end_tag ) = @_;
return 1 if $is_end_tag;
unless ( $this->{ sth }->fetch() ) {
# ... same as before ...
}
return 1;
}
Notice that "fetchrow()" has been replaced by "fetch()"
and that there are no assignments.
Handling GET Requests
Dynamically generated web pages often need parameters to
determine the content. Such paramaters can be passed via
the query string as in:
http://www.icecream.com/flavor/detail.chtml?flavor_id=4
It's desirable to perform parameter validation before dis
playing any part of a web page because, if you start to
display the page and discover an invalid parameter, it's
too late. For example, the "flavor_id" should be checked
to ensure that it refers to an existing flavor in the
database: if it doesn't, a redirection can be done to an
error page.
For this to work, the code file must define a method named
"get()". The implementation for this example shall be
presented in stages. The "get()" method begins simply as:
sub get {
my $this = shift;
The code then prepares an SQL query via DBI:
my $dbh = DBI->connect( 'DBI:mysql:ice_cream:localhost' );
my $sth = $dbh->prepare( '
SELECT 1
FROM flavors
WHERE flavor_id = ?
' );
"ClassParser" stores a copy of the reference to the cur
rent "Apache::Request" object into "$this->{ r }" from
which query parameters can be extracted:
my $r = $this->{ r };
(The variable name $r is used by convention.) A check is
made to ensure "flavor_id" was given as a parameter and,
if so, an attempt is made to execute the query and fetch
the result row. If anything fails, the display of the web
page is aborted by returning the standard Apache
"NOT_FOUND" error:
return NOT_FOUND unless
$r->param( 'flavor_id' ) &&
$sth->execute( $r->param( 'flavor_id' ) ) &&
$sth->fetchrow();
If all succeeds, then the function simply returns "OK":
return OK;
}
The return value must be one of the "Apache::Constants".
If the value is "OK", then the display of the web page
proceeds normally; if the value is anything other than
"OK", then the web page is not displayed. In either case,
that return value is passed back to Apache.
Handling POST Requests
Ordinarily, the "ACTION" attribute in an HTML "FORM" ele
ment contains a URI pointing at a CGI script, e.g.: