HTML::Seamstress - Perl extension for HTML generation via tree rewriting
<html> <head> <title>Hello World</title> </head> <body> <h1>Hello World</h1> <p>Hello, my name is <span id="name">ah, Clem</span>. <p>Today's date is <span id="date">Oct 6, 2001</span>. </body> </html>
use HTML::Seamstress; # HTML::Seamstress HTML::TreeBuilder :) my $tree = HTML::Seamstress->new_from_file($html_file); $tree->name_handler('bob'); $tree->date_handler(`date`); sub name_handler { my ($tree, $name) = @_; my $name_tag = $tree->look_down('id', 'name'); $name_tag->detach_content; # delete dummy content ("ah, Clem") $name_tag->push_content($name); } sub date_handler { my ($tree, $date) = @_; my $name_tag = $tree->look_down('id', 'date'); $name_tag->detach_content; # delete dummy content ("Oct 6, 2001") $name_tag->push_content($date); }
use HTML::Seamstress; my $tree = HTML::Seamstress->new_from_file($html_file); $tree->content_handler(name => 'bob'); $tree->content_handler(date => `date`);
From reading HTML::Tree::Scanning, we know that HTML has a tree structure. HTML::Seamstress is a subclass of HTML::TreeBuilder which makes it a little easier to perform common HTML templating operations as tree rewriting.
The "SYNOPSIS" gave an example of text substitution. From a tree-writing perspective, text substitution involves an in-place change to the content of a node of an HTML tree.
There are two methods for rewriting the content of a node. The more powerful method is set_child_content. The more convenient is content_handler.
set_child_content
content_handler
This method looks down $tree using the criteria specified in @look_down using the the HTML::Element look_down() method.
@look_down
look_down()
After finding the node, it detaches the node's content and pushes $content as the node's content.
This is a convenience method. Because the look_down criteria will often simply be:
sid => 'fixup'
to find things like:
<a sid=fixup href=http://www.somesite.org>replace_content</a>
You can call this method to shorten your typing a bit:
$tree->content_handler( fixup => 'new text' )
Instead of typing:
$tree->set_child_content(sid => 'fixup', 'new text')
In tree rewriting terms, an if directive is used to decide whether a particular node of the HTML tree is preserved or deleted.
if
For example, given this Template-style HTML:
[% IF age < 10 %] Hello, does your mother know you're using her AOL account? [% ELSIF age < 18 %] Sorry, you're not old enough to enter (and too dumb to lie about your age) [% ELSE %] Welcome [% END %]
Here is the HTML and Perl for Seamstress:
<span id=age_handler> <span id="under10"> Hello, does your mother know you're using her AOL account? </span> <span id="under18"> Sorry, you're not old enough to enter (and too dumb to lie about your age) </span> <span id="welcome"> Welcome </span> </span> package HTML::Seamstress use HTML::Seamstress; my $tree = HTML::Seamstress->new(); $tree->parse_file($filename); $tree->age_handler($age); print $tree->as_HTML; sub age_handler { my ($tree, $age) = @_; my $SPAN = $tree->look_down('id', 'age_handler'); if ($age < 10) { $SPAN->look_down('id', $_)->detach for qw(under18 welcome); } elsif ($age < 18) { $SPAN->look_down('id', $_)->detach for qw(under10 welcome); } else { $SPAN->look_down('id', $_)->detach for qw(under10 under18); } }
package Simple::Class; use Set::Array; 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 } } Set::Array->new(@data); } 1;
my $data = Simple::Class->load_data; ++$_->{age} for @$data
<html> <table id="load_data"> <tr> <th>name</th><th>age</th><th>weight</th> </tr> <tr id="iterate"> <td id="name"> NATURE BOY RIC FLAIR </td> <td id="age"> 35 </td> <td id="weight"> 220 </td> </tr> </table> </html>
require 'simple-class.pl'; use HTML::Seamstress; # load the view my $seamstress = HTML::Seamstress->new_from_file('simple.html'); # load the model my $o = Simple::Class->new; my $data = $o->load_data; # find the <table> and <tr> my $table_node = $seamstress->look_down('id', 'load_data'); my $iter_node = $table_node->look_down('id', 'iterate'); my $table_parent = $table_node->parent; # drop the sample <table> and <tr> from the HTML # only add them in if there is data in the model # this is achieved via the $add_table flag $table_node->detach; $iter_node->detach; my $add_table; # Get a row of model data while (my $row = shift @$data) { # We got row data. Set the flag indicating ok to hook the table into the HTML ++$add_table; # clone the sample <tr> my $new_iter_node = $iter_node->clone; # find the tags labeled name age and weight and # set their content to the row data $new_iter_node->content_handler($_ => $row->{$_}) for qw(name age weight); $table_node->push_content($new_iter_node); } # reattach the table to the HTML tree if we loaded data into some table rows $table_parent->push_content($table_node) if $add_table; print $seamstress->as_HTML;
require 'simple-class.pl'; use HTML::Seamstress; # load the view my $seamstress = HTML::Seamstress->new_from_file('simple.html'); # load the model my $o = Simple::Class->new; $seamstress->table ( # tell seamstress where to find the table, via the method call # ->look_down('id', $gi_table). Seamstress detaches the table from the # HTML tree automatically if no table rows can be built gi_table => 'load_data', # tell seamstress where to find the tr. This is a bit useless as # the <tr> usually can be found as the first child of the parent gi_tr => 'iterate', # the model data to be pushed into the table table_data => $o->load_data, # the way to take the model data and obtain one row # if the table data were a hashref, we would do: # my $key = (keys %$data)[0]; my $val = $data->{$key}; delete $data->{$key} tr_data => sub { my ($self, $data) = @_; shift(@{$data}) ; }, # the way to take a row of data and fill the <td> tags td_data => sub { my ($tr_node, $tr_data) = @_; $tr_node->content_handler($_ => $tr_data->{$_}) for qw(name age weight) } ); print $seamstress->as_HTML;
* HTML
<html> <table id="load_data" CELLPADDING=8 BORDER=2> <tr> <th>name</th><th>age</th><th>weight</th> </tr> <tr id="iterate1" BGCOLOR="white" > <td id="name"> NATURE BOY RIC FLAIR </td> <td id="age"> 35 </td> <td id="weight"> 220 </td> </tr> <tr id="iterate2" BGCOLOR="#CCCC99"> <td id="name"> NATURE BOY RIC FLAIR </td> <td id="age"> 35 </td> <td id="weight"> 220 </td> </tr> </table> </html>
* Only one change to last API call.
This:
gi_tr => 'iterate',
becomes this:
gi_tr => ['iterate1', 'iterate2']
Often when a table has no rows, we want to display a message indicating this to the view. Use conditional processing to decide what to display:
<span id=no_data> <table><tr><td>No Data is Good Data</td></tr></table> </span> <span id=load_data> <html> <table id="load_data"> <tr> <th>name</th><th>age</th><th>weight</th> </tr> <tr id="iterate"> <td id="name"> NATURE BOY RIC FLAIR </td> <td id="age"> 35 </td> <td id="weight"> 220 </td> </tr> </table> </html> </span>
None by default.
http://perlmonks.org/index.pl?node_id=302606
http://perlmonks.org/index.pl?node_id=303188
http://www.metaperl.com
http://xmlc.enhydra.org
Terrence Brannon, <tbone@cpan.org<gt>
Copyright 2003 by Terrence Brannon.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
To install HTML::Seamstress, copy and paste the appropriate command in to your terminal.
cpanm
cpanm HTML::Seamstress
CPAN shell
perl -MCPAN -e shell install HTML::Seamstress
For more information on module installation, please visit the detailed CPAN module installation guide.