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

I wish to use HTML::Template to create the columns and rows of a table, using a LOOP inside of a LOOP.
I've read the POD, and jeffa's excellent HTML::Template Tutorial, but the problem is that I can't understand how his various map's are creating the required data structures for HTML::Template.

I've created an Array of Array's full of data to put into a HTML table, but can't figure out how to get it into HTML::Template.

I have an array,
@data = ( [1, 2, 3, 4, 5], ['one', 'two', 'three', 'four', 'five'], ['ein', 'zwei', 'drei', 'veir', 'funf'], ['hana', 'dool', 'set', 'net', 'dasut'], ['yi', 'er', 'san', 'si', 'wu'], );
and would like to output a HTML table like so,
1 one ein hana yi 2 two zwei dool er 3 three drei set san 4 four veir net si 5 five funf dasut wu
I know how to manipulate the @data array to print it out like that,
use strict; use warnings; my @array = ([1, 2, 3, 4, 5], ['one', 'two', 'three', 'four', 'five'], ['ein', 'zwei', 'drei', 'veir', 'funf'], ['hana', 'dool', 'set', 'net', 'dasut'], ['yi', 'er', 'san', 'si', 'wu'],); my @sorted; my $i = 0; for (@array) { for ( @{$_} ) { push @{$sorted[$i]}, $_; $i ++; } $i = 0; } for (@sorted) { for ( @{$_} ) { print $_, ' ' } print $/ }
outputs
1 one ein hana yi 2 two zwei dool er 3 three drei set san 4 four veir net si 5 five funf dasut wu
...so I do know how to manipulate the data, I just don't understand exactly what data structure HTML::Template requires.

If anybody gives a solution using map, I would really appreciate an explanation of what the map is doing.

Here is the code I have so far, including the HTML template:
#!/usr/bin/perl -wT use strict; use CGI; $CGI::DISABLE_UPLOADS = 1; use CGI::Carp qw/fatalsToBrowser/; use HTML::Template; use vars qw/ $q $template $html @loop_data @data /; $q = new CGI; $html = do { local $/; <DATA> }; $template = HTML::Template->new(scalalref => \$html); @data = ( [1, 2, 3, 4, 5], ['one', 'two', 'three', 'four', 'five'], ['ein', 'zwei', 'drei', 'veir', 'funf'], ['hana', 'dool', 'set', 'net', 'dasut'], ['yi', 'er', 'san', 'si', 'wu'], ); ### DO SOMETHING HERE TO CREATE @loop_data ! $template->param(loop1 => \@loop_data); print $q->header; print $template->output; exit; __DATA__ <html> <body> <table> <!-- TMPL_LOOP NAME=loop1 --> <tr> <!-- TMPL_LOOP NAME=loop2 --> <td><!-- TMPL_VAR NAME=var1 --></td> <!-- /TMPL_LOOP --> </tr> <!-- /TMPL_LOOP --> </table> </body> </html>
Thanks!

Replies are listed 'Best First'.
Re: HTML table with HTML::Template and loop in a loop
by blokhead (Monsignor) on Sep 19, 2002 at 12:33 UTC
    In this case, you want to slice across the arrays -- pick all the ones, then all the twos, etc. So your structure must be converted slightly, basically just swap logical rows with columns in your representation. To determine the structure, remember that all named items in your template must correspond to a hash key in the structure, and that loops correspond to array references. This is how you would logically build up your %loop_data structure, starting with the outer loop and moving inwards:
    <!-- TMPL_LOOP NAME=loop1 --> # means you must have a hash key 'loop1' with an array ref as its valu +e # in the hash. %loop_data = ( loop1 => [ ..something.. ] ); <!-- TMPL_LOOP NAME=loop2 --> # another loop, so each of these 'somethings' must have a hash key loo +p2 # pointing to a value of some array ref %loop_data = ( loop1 => [ { loop2 => [ ..something.. ] }, { loop2 => [ ..something.. ] }, ... ] ); <!-- TMPL_VAR NAME=var1 --> # now each of these somethings must contain a hash key var1 pointing t +o # some scalar data. %loop_data = ( loop1 => [ { loop2 => [ { var1 => 'data'} ] }, { loop2 => [ { var1 => 'otherdata'} ] }, ... ] );
    You may find it useful to rename the structure like this:
    %loop_data = ( rows => [ { cols => [ { data => 'data'} ] }, { cols => [ { data => 'otherdata'} ] }, ... ] );
    You can either build up your original structure in this way, or use something like the following to convert to this new structure:
    #!/usr/bin/perl -wT use strict; use HTML::Template; my $html = do { local $/; <DATA> }; my $template = HTML::Template->new(scalarref => \$html); my @data = ( [1, 2, 3, 4, 5], ['one', 'two', 'three', 'four', 'five'], ['ein', 'zwei', 'drei', 'veir', 'funf'], ['hana', 'dool', 'set', 'net', 'dasut'], ['yi', 'er', 'san', 'si', 'wu'], ); my %loop_data; foreach my $array_num (0 .. $#data) { my $array_ref = $data[$array_num]; foreach my $array_index (0 .. $#{@$array_ref}) { $loop_data{rows}[$array_index]{cols}[$array_num]{data} = $arra +y_ref->[$array_index]; } } $template->param(\%loop_data); print $template->output; exit; __DATA__ <html> <body> <table> <!-- TMPL_LOOP NAME=rows --> <tr> <!-- TMPL_LOOP NAME=cols --> <td><!-- TMPL_VAR NAME=data --></td> <!-- /TMPL_LOOP --> </tr> <!-- /TMPL_LOOP --> </table> </body> </html>
    This produces the correct output for me.

    blokhead

      blokhead, your explanation at the start is certainly helping me to start to understand it, but I'm not quite there yet!

      Unfortunately, if I run your code, I get an error
      Bizarre copy of ARRAY in leave at ./test.cgi line 21, <DATA> line 1.
      and I can't figure out what's wrong.

      btw, I'm using a cut 'n' paste of your code.

        There is some talk in google that  Bizarre copy of ... might be caused by a perl 5.6.0 bug. Would you try  perl -v please?

        Are you sure the long lines that have been word-wrapped were fixed after copying over? The copy and paste into a new file works for me, but I have my wordwrap setting up higher than default. I've never seen that error before.. Bizarre copy? ;) I wonder what the "in leave" part means.

        blokhead

(jeffa) Re: HTML table with HTML::Template and loop in a loop
by jeffa (Bishop) on Sep 19, 2002 at 16:59 UTC
    Time to break the problem down! First off, have you read my other tutorial Map: The Basics? Anyhoo, any time i deal with map and complex data structures, i always bring Data::Dumper along for the ride, consider this piece of code first:
    use strict; use HTML::Template; use Data::Dumper; my @array = ( [(1..5)], [qw(one two three four five)], [qw(ein zwei drei veir funf)], [qw(hana dool set net dasut)], [qw(yi er san si wu)], ); print Dumper \@array;
    This will give you an idea of what your data structure looks like. In this case, it is a two dimensional array, or an array of array references. Moving ahead, let's now build a template to work with:
    <table> <tmpl_loop rows> <tr> <tmpl_loop cells> <td><tmpl_var data></td> </tmpl_loop> </tr> </tmpl_loop> </table>
    So, in order for this to work, we need a need to pass the the param() method one arg named 'rows', and that "key" must point to a list of hash references. Each of these hash references will have a one key, 'cells'. For now, let's just focus on the first list of hash references. We will use map to transform the list of array references from @array into a list of hash refs. Each key will be called 'cells' and the values will be each of the inner array references from @array. Read from right to left:
    my $rows = [ map {{ cells => $_ }} @array ]; print Dumper $rows;
    The whole map statement must be 'wrapped' by brackets to specify that $rows is really an array reference.

    Now all we need to do is turn each of those elements into yet another hash ref, yet another single key, 'data':

    my $rows = [ map {{ cells => [ map {{ data => $_ }} @$_ ] }} @array ]; print Dumper $rows;
    That is what HTML::Template wants - each loop must be a list of hash references. And yes, this is not easy stuff! :) (This is also why i wrote DBIx::XHTML_Table, but that is another story.) Here a complete script that you can play with. Good luck! :)
Re: HTML table with HTML::Template and loop in a loop
by cfreak (Chaplain) on Sep 19, 2002 at 17:59 UTC

    Well there are some good explainations here but I'm not sure anyone got at your core question. Which is "What type of data structure does HTML::Template take?"

    At the most basic level HTML::Template takes a hash. If you have a hash of key value pairs that all appear in the template then you can simply pass it the param call the hash and it will work.

    The <tmpl_loop> structure takes a reference to an array of hash references. And you can have loops inside your loops.

    So here's how you can make @data into @loop_data, I'm going to avoid using map so that you can get a clearer idea of where the data is going:

    my @loop_data = (); foreach my $array_ref(@data) { my @inner_loop = (); foreach my $element (@$array_ref) { push(@inner_loop,{var1=>$element}); } push(@loop_data,{loop2=>\@inner_loop}); }

    Hope that helps... I'm going to post this and the try to write some code that does it with map I can't think of it off the top of my head right now.

    ***UPDATE***
    Here's some code to do it with map

    my @loop_data = map { {loop2=> [map { {var1=>$_ } } @$_ ] } } @data;

    ***UPDATE 2 ***
    Fixed the bug. :) thanks fireartist

    Chris

    Lobster Aliens Are attacking the world!
      Thanks cfreak, this was helpful.

      small typo though,
      my @loop_data = (); foreach my $array_ref(@data) { my @inner_loop = (); foreach my $element (@$_) { push(@inner_loop,{var1=>$element}); } push(@loop_data,{loop2=>\@inner_loop}); }
      In the 4th line, (@$_) should read (@$array_ref).
Re: HTML table with HTML::Template and loop in a loop
by blokhead (Monsignor) on Sep 20, 2002 at 02:02 UTC
    Just to note, jeffa's and cfreak's similar solutions don't quite get the output that fireartist wanted. Specifically, they output a table which looks like this:
    1 2 3 4 5 one two three four five ein zwei drei veir funf hana dool set net dasut yi er san si wu
    However, what fireartist wanted was the following table:
    1 one ein hana yi 2 two zwei dool er 3 three drei set san 4 four veir net si 5 five funf dasut wu
    The desired HTML table output doesn't exactly match the structure of the @data array given in the code. In fact, luckily for fireartist, I don't think this "correct" output is possible to do efficiently with map().

    With that said, the two solutions given by jeffa and cfreak are good starters for the most common HTML::Template situations. For everyone's sanity, it's usually best to keep the internal data structures consistent with the structure of the output table. In this case, the @data structure was symmetric to the output table, but not quite the same (perhaps because of other uses of @data in other code, or just unfamiliarity with HTML::Template). But in about 90% of situations, you want to make your internal data structured in the same way as the output. This way you get to use map(), and these two writeups are right on point for help in those situations (especially jeffa's suggestion to use Data::Dumper).

    blokhead

      Doh! I complete missed that. Bad jeffa.

      To make up for my oversight, here is another way to do it:

      use Math::Matrix; # ... yadda yadda my $matrix = Math::Matrix->new(@array); my $rows = [ map {{ cells => [ map {{ data => $_ }} @$_ ] }} @{$matrix->transpose} ];

      jeffa

      L-LL-L--L-LL-L--L-LL-L--
      -R--R-RR-R--R-RR-R--R-RR
      B--B--B--B--B--B--B--B--
      H---H---H---H---H---H---
      (the triplet paradiddle with high-hat)
      
Re: (fireartist)HTML table with HTML::Template and loop in a loop
by fireartist (Chaplain) on Sep 23, 2002 at 14:57 UTC
    Thanks everyone for your help,

    using blokhead's first reply, I typed the following data structure, and passed it 'raw' to HTML::Template.
    %loop_data = ( rows => [ { cells => [ { data => 'one'}, { data => 'ein'}, ], }, { cells => [ { data => 'two'}, { data => 'zwei'}, ], }, { cells => [ { data => 'three'}, { data => 'drei'}, ], }, ], );
    Being able to see this raw data structure helped me to figure out how to create it programatically for HTML::Template.

    I was now able to see how cfeaks's "foreach" example worked.

    then... thanks to jeffa's replies and his Map: The Basics tutorial, I was able to figure out what the map statements were doing.

    Using this new-found knowledge, I was able to write this new script which uses an array '@columns' to 1/, fill one loop (the first table row) with values, and 2/ create the rest of the table by filling a loop-within-a-loop with data from a database.
    #!/usr/bin/perl -wT use strict; use CGI; use DBI; use HTML::Template; use vars qw/ $q $dbh $sth $rv $rc $template $html @columns @headings_data @rows_data $sql / +; @columns = qw/ prod_code dep_1 dep_2 dep_3 /; @headings_data = map {{ cell_data => $_ }} @columns; $sql = 'SELECT ' . join(', ', @columns) . ' FROM products WHERE dep_1= +01'; $dbh = DBI->connect("dbi:mysql:database:host", "user", "pass") or print_error("Could not connect to Database", $DBI::errstr); $sth = $dbh->prepare( $sql ) or die("Couldn't prepare the database", "$DBI::errstr"); $rv = $sth->execute(@_) or die("Couldn't execute database", "$DBI::errstr"); while ( my @data = $sth->fetchrow_array ) { my @cells = map {{ cell_data => $_ }} @data; push @rows_data, { cells => \@cells }; } $html = do { local $/; <DATA> }; $template = HTML::Template->new(scalarref => \$html); $template->param( headings => \@headings_data, rows => \@rows_data ); $q = new CGI; print $q->header; print $template->output; $dbh->disconnect; exit; __DATA__ <html> <body> <table> <tr> <!-- TMPL_LOOP NAME=headings --> <td><!-- TMPL_VAR NAME=cell_data --></td> <!-- /TMPL_LOOP --> </tr> <!-- TMPL_LOOP NAME=rows --> <tr> <!-- TMPL_LOOP NAME=cells --> <td><!-- TMPL_VAR NAME=cell_data --></td> <!-- /TMPL_LOOP --> </tr> <!-- /TMPL_LOOP --> </table> </body> </html>
    The original data I posted was not in the format I was actually working with, but I presented it as such to enable me to figure out exactly what HTML::Template wanted, and how to create that myself (rather that using another-monks map-statement-on-a-plate that I couldn't understand).
    A big thankyou to everyone for having the patience to help me past another milestone of Perl understanding!