The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

HTML::Seamstress - dynamic HTML generation via pure HTML and pure Perl.

SYNOPSIS

  # HTML
  <html>

  # supply element automatically binds $s->{supply}
  <table class=supply id="$s->_aref($s->load_data)">

    <tr>  <th>name<th>age<th>weight</th> </tr>

    # iterator element automatically binds $s->{iterator}
    <tr class=iterator id="$s->{supply}->shift">

        <td class=worker id="$s->_text($s->{iterator}->{name})">    </td>
        <td class=worker id="$s->_text($s->{iterator}->{age})">     </td>
        <td class=worker id="$s->_text($s->{iterator}->{weight})">  </td>

   </tr>

  </table>

 </html>

  # Perl call to generate HTML
  use HTML::Seamstress;
  HTML::Seamstress->weave(html => 'simple.html', using => 'Simple::Class');

  # Perl call to generate a Perl program, which when run, generates HTML
  use HTML::Seamstress;
  HTML::Seamstress->compile(html => 'simple.html', using => 'Simple::Class');

  # Simple/Class.pm
 package simple;

 use base qw(HTML::Stitchery);

 my @name   = qw(bob bill brian babette bobo bix);
 my @age    = qw(99  12   44    52      12   43);
 my @weight = qw(99  52   80   124     120  230);


 sub new {
    my $this = shift;
    bless {}, ref($this) || $this;
 }


 sub load_data {
    my @data;

    for (0 .. 5) {
        push @data, { 
            age    => $age[rand $#age] + int rand 20,
            name   => shift @name,
            weight => $weight[rand $#weight] + int rand 40
            }
    }

    return \@data;
 }

 1;

DESCRIPTION

Disclaimer One - THIS IS ALPHA SOFTWARE. USE AT YOUR OWN RISK

Disclaimer Two - This package is (too?) similar to HTML::Template

On to the description

HTML::Seamstress allows webpages to be built by serving as the bridge between experts in their respective pure technologies: HTML experts do their thing, object-oriented Perl experts do their thing and HTML::Seamstress serves to weave the two together, traversing the pure HTML and making use of the output of Perl objects at various points in the traversal.

Its distinctive 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 semantic attributes (CLASS and ID) for HTML elements.

In model-view-controller terms, the HTML is the view. Seamstress is the controller, making calls to view-agnostic Perl methods to retrieve model data for inclusion in the view.

In HTML::Seamstress the model classes are completely view and controller independant, and are thus use-able outside of HTML and most importantly, unit-testable outside of Perl.

Seamstress knows what Perl methods to call and when by the looking up CLASS attributes within a tag that are special to it. The attributes are supply, iterator, and worker tags. The id tag is used for Perl code.

  • A worker class is used when actual "work" is going to be done on the HTML file. This work is usually simple such as _text, which sets the content aspect of the HTML::Element to some text. The others are listed in the manpage for HTML::Stitchery.

  • A supply class is called for side-effect. It creates a store of data for use by iterator and worker tags. Note that this creation may be actual or via a Perl tie. HTML::Seamstress automatically stores the results of supply attribute evaluation in the page object under $s-{supply}> for later reference.

  • An iterator class is used to pull records from a supply store previously created. HTML::Seamstress automatically stores the results of iterator attribute evaluation in the page object under $s-{iterator}> for later reference.

Companion modules

HTML::Seamstress has one simple job as described above. A number of other modules, work to make this a complete suite for other HTML-based tasks:

  • HTML::Stitchery provides a number of useful "stitches" to be automatically woven into HTML files. These stitches are nothing more than object methods. For practical examples of many common dynamic HTML generation tasks such as dynamic table generation, language localization, database connectivity, conditional HTML, and so forth see its documentation. But in doing so, note well that all of the above tasks are implemented as Perl object methods and incur the minal class attribute adulteration of the HTML. In fact, the actual attribute to be used can be configured by setting $worker_attr, $supply_attr, $iterator_attr in HTML::Seamstress.

  • CGI::Seamstress is a derived class of HTML::Seamstress, HTML::Stitchery and CGI.pm. All capabilities of each of these individual technologies are offered to the programmer while placing no burden on the HTML designer. It, ahem, isn't written yet. But it does sound good and orthogonal, doesn't it? :)

  • Apache::Seamstress is a derived class of HTML::Seamstress, HTML::Stitchery, CGI.pm, and Apache::Request. And it isn't written yet either. Sigh. I don't think it should be actually. A better way to seamless support both CGI and mod_perl is via Apache::Registry

Sample usage of HTML::Seamstress

The t/ directory of _HTML::Stitchery_ (not HTML::Seamstress) contains a large number of examples. Further examples are in the CGI::Seamstress and Apache::Seamstress directories.

This, and other small snippets of documentation are taken from Paul J. Lucas' HTML Tree distribution (http://homepage.mac.com/pauljlucas/software/html_tree), which CHTML::Seamstress was based on but changed due to experience.

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:

    <SELECT NAME="Flavors" SUPPLY="$s->query_flavors">
      <SPAN CLASS=ITERATOR id="$s->{supply}->next_flavor">
        <OPTION CLASS=WORKER id="$s->_text($s->{iterator}->{flavor}" VALUE="0">
             Tooty Fruity
        </OPTION>
      </SPAN>
    </SELECT>

query_flavors , next_flavor, and the worker will be used to generate HTML dynamically. The values of the attributes attributes can be any Perl code as long as they agree with those in the code file (specified later).The text "Tooty Fruity" is dummy content.

The query_flavors SUPPLY 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 substitute the name and ID number of the flavor.

The Code File

The associated code file in Perl in specified via the weave configuration option of HTML::Seamstress.

The implementation of the query_flavors() and next_flavor() methods shall be presented in stages.

The query_flavors() method begins by getting its arguments as described above:

    sub query_flavors {
        my $this = shift;

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();

Finally, the method returns true to tell HTML::Seamstress to proceed with parsing the SELECT element's child elements if any rows were returned from the query.

        return $this->{sth}->rows;
    }

The next_flavor() method begins identically to query_flavors():

    sub next_flavor {
        my $this = shift;

The next portion of code fetches a tuple from the database. If there are no more tuples, the method returns false. This tells HTML::Seamstress 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, and we are running mod_perl, then the disconnect() becomes a no-op and the connection remains persistent.)

Finally, the method returns true to tell HTML::Seamstress to emit the HTML for the OPTION element now containing the dynamically generated content: return $flavor_name;

    }


 1;

Things you may be wondering

Where is the if-then-else tag?

There is no if-then-else tag. That functionality occurs implicitly. When a portion of the HTML document is bracketed by a tag whose class is supply and that tag's id when evaluated returns a Perl false value, then that tag, and the entire HTML subtree under it are not placed in the output document.

This is what an if statement does

Also note, some cases of if-then-else are better handled by doing if-then-else in your CGI program and then issuing a redirect to one of several appropriate pages instead of spaghetti'ing the hell out of one document with a maelstrom of if-then-elses.

Where is the loop tag?

Again there isn't one. The combination of supply and iterator serves as a basic general purpose loop. In fact, if you look at the compiled code, you will see something like this: { last unless $page->{iterator}->() ...

   redo;
 } 

which is the most general Perl loop available.

Where is the include file tag?

There isn't one. I insist of using HTML reuse via HTML design programs. Seamstress believes that a quality HTML designer can do quality things with a quality program and library element re-use is one of those things.

Closely related software products

HTML::Template

I consider this package to be to be HTML::Template's little brother. HTML::Template is more mature than this and also has excellent and flexible caching technology built in. Both modules believe that very little logic should exist in HTML files.

So why continue with it if the packages are similar in philosophy and HTML::Template is well designed and debugged? For me, the answer is manyfold:

  • HTML::Template is already showing signs that its restrictive variable-only approach is somewhat weak

    Note the recent creation of HTML::Template::Expr. And note that you are moving into "3rd technology" (ie. something other than pure Perl and pure HTML) realm with this and you must learn it's new rules and exceptions.

    In fact, HTML::Template itself requires you to learn a number of pseudo-HTML operators and then learn how to convert pure model code to a hashref for use by them.

    I personally tire of learning more and more non-Perl syntax which makes simple things easy and hard things acts of contortion. I'd rather stick with as much pure Perl and as little extra-perl-invented conventions.

    With Seamstress, if you know Perl and simply add CLASS and ID tags in the HTML tags where you want dynamic expansion you are finished.

  • Because the templating instructions are within the HTML tags themselves the page will always be cleaner than an HTML::Template page

    The comparative example below will show this.

  • HTML::Seamstress can compile its templated documents into pure Perl

    This is useful for fast execution in CGI or mod_perl.

Let's write a sample piece of code in both HTML::Template and HTML::Seamstress.

 <HTML>
  <HEAD><TITLE>Test Template</TITLE>
  <BODY>
  My Home Directory is <TMPL_VAR NAME=HOME>
  <P>
  My Path is set to <TMPL_VAR NAME=PATH>
  </BODY>
  </HTML>

 <HTML>
  <HEAD><TITLE>Test Template</TITLE>
  <BODY>
    My Home Directory is <SPAN CLASS=worker id="$s->_text($ENV{HOME}")> </SPAN>
  <P>
    My Path is set to <SPAN CLASS=worker id="$s->_text($ENV{PATH}")> </SPAN>
  </BODY>
  </HTML>

Hmm, the HTML::Template code is cleaner. Let's try something harder. I have to win this argument. :-)

 <TMPL_LOOP NAME="THIS_LOOP">
  Word: <TMPL_VAR NAME="WORD"><BR>
  Number: <TMPL_VAR NAME="NUMBER"><P>
   </TMPL_LOOP>

 <span class=supply id="$s->{this_loop}">
    <span class=iterator id="$s->{supply}->Next"> # next via Set::Array method
     Word: <br class=worker id="$s->_text($s->{iterator}{word})"></br>
     Numb: <br class=worker id="$s->_text($s->{iterator}{number})"></br>
    </span>
 </spna>

How this software differs from Paul J. Lucas' HTML_Tree

In concept, Seamstress and Lucas' HTML_Tree (call it ltree) are the same, with Seamstress being developed after usage of ltree. However, there are some technical differences between the packages:

  • HTML::Seamstress parses the HTML using SBURKE's HTML::Tree distribution. SBURKE's HTML parser is written in C and Paul Lucas' HTML parser is written in C++, so they are both fast.

  • in ltree, a .pm file must be associated with your HTML file and you must write your own constructor.

    Both of these steps are optional with HTML::Seamstress. You can always inherit the HTML::Stitchery constructor if you want and if you have an HTML file that you don't want HTML::Seamstress dhtml in, then you don't have to have a dummy .pm file, simply omit the using argument to HTML::Seamstress' weave method... actually at the moment I require a file a well, but this restriction and automatic common page objects will be made available in a later version.

  • method lookups in ltree are done through two mechanisms: a hash called %function_map:

       % function_map = (  href_id => \&sub_href_id , .... ) ;

    and the class_map attribute of your required constructor in your required .pm file.

    HTML::Seamstress simply relies on Perl object-oriented single dispatch. But it could do multiple-dispatch as well.

  • each object method had to do its own argument splitting in ltree:

       # ltree example - note API is slightly different too
       sub sub_href_id { 
          my ($this, $node, $class_arg, $is_end_tag) = @_; 
          my ($method, @arg) = split '::', $class_arg;

    Because HTML::Seamstress uses pure Perl in the attributes as opposed to a pseudo-HTML-like language, it does not have to do argument splitting but lets Perl handle that.

  • ltree does not support traversal of infinite levels of reference nesting.

    Instead it has a simple text() method which returns the key in the hash of its arguments. Because HTML::Seamstress uses Perl, arbitrary degrees of nesting of hashes, arrays, as well as method overloads could be supported.

AUTHOR

T. M. Brannon <tbone@cpan.org>

SEE ALSO

  • Paul J. Lucas' HTML Tree distribution

    (http://homepage.mac.com/pauljlucas/software/html_tree)

  • XMLC

    This is a Java framework with the exact same HTML pages. It creates DOM object files and works on HTML, XHTML and XML

      http://xmlc.enhydra.org/
  • HTML Tidy

    The demo programs tidy up their HTML output via this program:

     http://www.w3.org/People/Raggett/tidy/

TO DO

  • support injection for compilation

2 POD Errors

The following errors were encountered while parsing the POD:

Around line 543:

You forgot a '=back' before '=head1'

Around line 836:

You forgot a '=back' before '=head1'