http://www.perlmonks.org?node_id=639293

hacker has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to learn more and more about how I can use HTML::Template to optimize what I've previously done "statically" with CGI.pm.

Here's a place where I'm currently snagged:

my $snapdir = "/some/path/to/snapshots"; my @iso = ('ca', 'zh_CN', 'cs', 'da', 'en', 'fo', 'fr', 'de', 'it', 'ja', 'no', 'pl', 'ru', 'es', 'th', 'tr'); my @lang = ('Catalan', 'Chinese', 'Czech', 'Danish', 'English', 'Faroese', 'French', 'German', 'Italian', 'Japanese', 'Norwegian', 'Polish', 'Russian', 'Spanish', 'Thai', 'Turk +ish'); my @languages; push @languages, { name => $lang[$_], iso => $iso[$_], file => "viewer_$iso[$_].prc", }, foreach (0..$#iso); print '<table border="1">'; print '<th>Language</th>'; print '<th>Last updated</th>'; foreach (0..$#iso) { my $string = shift; my $viewer = { name => $lang[$_], iso => $iso[$_]}; my $ppi = { name => $lang[$_], iso => $iso[$_]}; $viewer->{file} = "$root/viewer_$iso[$_].prc"; $ppi->{file} = "$root/ppi_$iso[$_].prc"; next unless stat($viewer->{file}); $viewer->{size} = (CORE::stat($viewer->{file}))[7]; $ppi->{size} = (CORE::stat($ppi->{file}))[7]; $viewer->{time} = (CORE::stat($viewer->{file}))[9]; $ppi->{time} = (CORE::stat($ppi->{file}))[9]; $languages[$_] = $viewer; my $name = sprintf("%-12s", $viewer->{'name'}); my $filedate = strftime "%D %r", localtime $viewer->{'time' +}; my $filesize = insert_commas($viewer->{'size'}); my $ppisize = insert_commas($ppi->{size}); print "<tr>"; print "<td align=\"right\">" . $cgi->a({-href=>substr("$snapdir/$viewer->{'file'}", 3 +5), -title=>"$filesize bytes"}, "$name") . "(" . $cgi->a({-href=>substr("$snapdir/$ppi->{'file'}" +, 34), -title=>"$ppisize bytes"}, "ppi") . ")" . "</td>"; print "<td>$filedate</td>"; print "</tr>"; } print "</table>"; print $cgi->end_div();

What is the easiest way to convert this construct into something that HTML::Template, via TMPL_LOOP, can grok?

Replies are listed 'Best First'.
Re: Converting "static" CGI.pm code into HTML::Template loops
by graff (Chancellor) on Sep 17, 2007 at 00:55 UTC
    Just think of each row in your output table as one interation of a single <TMPL_LOOP NAME="..."> ... </TMPL_LOOP>.

    Your foreach loop, instead of printing things, will be pushing elements onto an array of hashes, one hash for each iteration / table row, where the hash keys will be the names assigned to the specific TMPL_VAR's that you put inside the TMPL_LOOP portion of the template.

    I think the relevant part of your template would probably look something like this:

    <table border="1"> <tr> <th>Language</th> <th>Last updated</th> </tr> <TMPL_LOOP NAME="LANG_ROWS"> <tr> <td align="right"> <a href=<TMPL_VAR NAME="LINK_viewer"> title=<TMPL_VAR NAME="SIZE_view +er">> <TMPL_VAR NAME="TEXT_viewer"></a> (<a href=<TMPL_VAR NAME="LINK_ppi"> title=<TMPL_VAR NAME="SIZE_ppi">>p +pi</a>) </td> <td align="right"><TMPL_VAR NAME="FILEDATE"></td> </tr> </TMPL_LOOP> </table>
    Then, your foreach loop just needs to make sure that a hash is filled with values for the six TMPL_VAR names (FILEDATE, TEXT_viewer, SIZE_viewer, LINK_viewer, SIZE_ppi, LINK_ppi), and the hash is pushed onto an array for use with the template -- something like this might suffice:
    my @table_data; for ( 0 .. $#iso ) { my %row_data = (); for my $file ( qw/viewer ppi/ ) { my $file_path = join( "_", "$root/$file", "$iso[$_].prc" ); last unless stat( $file_path ); $row_data{"LINK_$file"} = "\"$snapdir/$file_path\""; $row_data{"SIZE_$file"} = sprintf( "\"%s bytes\"", insert_commas( -s _ )); if ( $file eq 'viewer' ) { $row_data{"FILEDATE"} = strftime( "%D %r", localtime( $^T - ( -M _ * 3600 * 24 + ))); $row_data{"TEXT_$file"} = sprintf( "%-12s", $lang[$_] ); } } push @table_data, { %row_data } if ( scalar( keys %row_data ) == 6 + ); }
    (none of the above has been tested -- updated to fix indenting, and to avoid pushing empty/incomplete hashes onto the array by checking the number of hash keys)

    As mentioned in an earlier reply, you should be able to work out the rest by reading the fine manual for HTML::Template.

    I saw a lot of unnecessary work going on in the original foreach loop, and tried to eliminate it in the suggested code. I think my version does basically the same thing...

    ... Except I was baffled by (and did not try to repeat) the OP's use of substr() to create the link targets for the href attributes. As a rule, I'd say "just don't do that." It's really a bad idea, because it creates a sort of brittleness, obfuscation and dependency on external details (like changes to path names) that make the script harder to maintain. (Why use the values 34 or 35? How would they change if the file path changes?)

    There is a better way to get the intended result, which does not involve using substr() with "magic numbers".

Re: Converting "static" CGI.pm code into HTML::Template loops
by snopal (Pilgrim) on Sep 16, 2007 at 21:22 UTC

    HTML::Template is a well documented module that has perfectly good examples that DIRECTLY apply to your question. I doubt very much that someone will want to recreate such a fine document here.