http://qs1969.pair.com?node_id=256105

Below is a module I've written to dynamically create HTML tables. I've never liked creating them by hand. Comments and suggestions are very welcome.
package CGI::Tables; use strict; use vars qw[@ISA @EXPORT $VERSION]; $VERSION = '0.9'; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(table); #feedit: options, data #receive: html #effect: none sub table { my $data = pop; my $keyed = { @_ }; my $html; my %attrs = %{shift @$data} if "HASH" eq ref $data->[0]; $data = arrange($data, $keyed) if $keyed->{cols} or $keyed->{rows}; $data = add_titles($keyed, $data) if $keyed->{col_titles} or $keyed->{row_titles}; $html .= "<table" . attributes(%attrs) . ">\n"; $html .= row(@$_) for @$data; $html .= "</table>\n"; return $html; } #feedit: row data #receive: html #effect: none sub row { my (@cells) = @_; my %attrs = %{shift @cells} if "HASH" eq ref $cells[0]; my $html = "<tr" . attributes(%attrs) . ">"; $html .= cell($_) for @cells; $html .= "</tr>\n"; return $html; } #feedit: cell data #receive: html #effect: none sub cell { my ($data) = @_; return "<td>$data</td>" if not ref $data; my %attrs = %{shift @$data} if "HASH" eq ref $data->[0]; my $html = "<td" . attributes(%attrs) . ">"; $html .= $_ for @$data; $html .= "</td>"; $html =~ s/td/th/g if $attrs{_header}; return $html; } #feedit: keyed args, data #receive: data #effect: none sub add_titles { my ($keyed, $data) = @_; my @titled; if ($keyed->{col_titles}) { my @titles; push @titles, title($keyed->{col_titles}, $_) for 0..$#{$data->[0]}; push @titled, [@titles]; } if ($keyed->{row_titles}) { unshift @{$titled[0]}, "" if $keyed->{col_titles}; for (0..$#$data) { push @titled, [ title($keyed->{row_titles}, $_), @{$data->[$_]} ]; } } else { push @titled, @$data; } return \@titled; } #feedit: titles, col/row number #receive: title #effect: none sub title { my ($titles, $n) = @_; my $title; if ("ARRAY" eq ref $titles) { $title = $titles->[$n]; } elsif ("CODE" eq ref $titles) { $title = $titles->($n); } return [ {_header=>1}, $title ]; } #feedit: attribute hash #receive: code for inside html tags #effect: none sub attributes { my (%attrs) = @_; my $html = ""; for (keys %attrs) { next if $_ =~ /^_/; $html .= " $_=\"$attrs{$_}\""; } return $html; } #feedit: data, keyed #receive: arranged data #effect: none sub arrange { my ($data, $keyed) = @_; my ($rows, $cols) = size($#$data + 1, $keyed); # add extra cells that are needed push @$data, "" until $#$data + 1 == $rows * $cols; my @arranged; if ($keyed->{cols}) { for (1..$rows) { push @arranged, [ splice @$data, 0, $cols ]; } } else { push @arranged, [] for 1..$rows; while (@$data) { push @$_, shift @$data for @arranged; } } return \@arranged; } #feedit: number of data elements, keyed #receive: number of rows, cols #effect: none sub size { my ($n_data, $keyed) = @_; my ($rows, $cols, $ratio); if ($rows = $keyed->{rows}) { $cols = divisor($n_data, $rows); } elsif ($cols = $keyed->{cols}) { $rows = divisor($n_data, $cols); } return $rows, $cols; } #feedit: number of data elements, number of (rows, cols) #receive: number of other way #effect: none sub divisor { my ($n_data, $rows) = @_; my $cols = int $n_data / $rows; $cols++ if $n_data % $rows; return $cols; } 1; __END__ =head1 NAME CGI::Tables - An easy way to create HTML tables =head1 SYNOPSIS use CGI::Tables; print table cols => 2, [qw(Cell Cell Cell)]; =head1 DESCRIPTION Only one function is exported: C<table()>. You pass it the parameters +and the data, in that order. The data is passed as an array ref and the parameters as a list. In return, you get HTML for a table. =head2 Arranging Data There are three ways to set up the data. All of the examples in this section produce identical tables. =over =item Hard Coding This method is the old fashioned way. If you're lazy (it's a virtue), +use one of the other two. This I<is> the most efficient way though. Pass t +he rows as array refs and the cells inside that. table [ [ "Row 1 Cell 1", "Row 1 Cell 2", "Row 1 Cell 3" ], [ "Row 2 Cell 1", "Row 2 Cell 2", "Row 2 Cell 3" ] ]; =item Cols If you pass the number of columns you want and put the cells in the da +ta array ref, CGI::Tables will automagically figure out how many rows the +re are and place the data in the correct place. The cells go from left to + right, top to bottom. table cols => 3, [ "Row 1 Cell 1", "Row 1 Cell 2", "Row 1 Cell 3", "Row 2 Cell 1", "Row 2 Cell 2", "Row 2 Cell 3" ]; =item Rows This is similar to passing the number of columns. The only difference is that the data as arranged top to bottom, left to right. table rows => 2, [ "Row 1 Cell 1", "Row 2 Cell 1", "Row 1 Cell 2", "Row 2 Cell 2", "Row 1 Cell 3", "Row 2 Cell 3" ]; =back =head2 Cells When passing a cell, it is perfectly acceptable to pass a string. You +can pass an array ref if you so desire. The elements of ref will be concatenate +d and placed in the cell. =head2 Column and Row Headers Column and row headers are passed by C<col_titles> and C<row_titles>, respectively. You can either pass an array ref of titles or a code/sub +routine reference. Code refs are passed column/row indices, which start at zer +o. Examples: table rows => 2, col_titles => sub { return "Col " . (shift() + 1) } +, [ @data ]; table rows => 2, row_titles => ["Row 1", "Row 2"], [ @data ]; =head2 Specifying HTML Attributes HTML attributes can be specifed for the table, a row, or a cell. Stick + a hash ref as the first element in the appropriate array ref, and it wil +l be added. For cells, pass an array ref (which would otherwise be optional +) with a hash ref in front. Row attributes cannot be passed when the tab +le is created dynamically. The following example will print a table of width + 600 with one cell that has #660000 as the background color. table cols => 2, [ {width => 600}, [ {bgcolor => "#660000"}, "Cell 1 +" ], "Cell 2", "Cell 3", "Cell 4" ]; =head1 AUTHOR Matthew Diephouse matt@diephouse.com =cut

elusion : http://matt.diephouse.com