I've had it with templating tools and code like
print table(map tr(map td($_), qw/foo bar baz/), 1..10)
and even with subs that transform lists of array references into tables.

Why can't we just break the laws of reusability and pure functions, and get something readable? :)
sub table (&) { print "<table>\n"; shift->(); print "</table>\n"; } sub row (&) { print "<tr>"; shift->(); print "</tr>\n"; } sub cell { print "<td>$_</td>" for @_; } table { for (1..10) { row { cell "foo", "bar", "baz" } } }; table { row { cell @$_ } for $db->query('SELECT * FROM foo')->arrays } +;

Replies are listed 'Best First'.
Re: Readable HTML tables
by Sidhekin (Priest) on Aug 26, 2003 at 01:13 UTC
    First of all, ++Juerd.

    I agree that maps, and nested maps in particular, are less readable than normal loops. Your code is very readable. But I like pure functions. This approach leaves me no easy way to stick the generated HTML into a variable. And it may just be that it is 3 AM hereabouts, but I cannot see how to do nested tables at all, when the table function is written to print.

    So, here is an alternative, returning good old-fashioned strings. Beware, I have been known to overdesign stuff. In fact, I am getting dangerously close to inventing a HTML templating system, aren't I? :-)

    But the code actually using the functions should still be readable, I hope.

    Update: My original implementation of these routines had a memory leak. Also, the functions' behaviour when called in non-void context "inside" another call ... did not DWIM. While the first problem could be solved in several ways, the least bad solution I found to the second, was to make the functions' behaviour depend on void context or not.

    Having made this change, the original three examples remain unchanged, and produce the same output as before. My two new examples illustrate the second problem.

    { my %coll; $coll{contents} = \0; # A ref. Any ref. my $element = sub { my ($name, $optional_newline) = @_;; $optional_newline ||= ''; return sub (&) { my $ret = do { # I cannot localize lexicals, but I can localize members of a # lexically scoped aggregate, much to the same effect: local $coll{contents}=""; local $coll{attributes}=""; shift->(); "<$name$coll{attributes}>$optional_newline" . "$coll{contents}</$name>$optional_newline"; }; if (defined wantarray) { return $ret; } elsif (ref $coll{contents}) { return; } else { $coll{contents} .= $ret; return; # We are in void context, but might as well be explici +t. } }; }; sub table (&); sub row (&); sub cell (&); *table = $element->( table => $/ ); *row = $element->( 'tr' => $/ ); *cell = $element->( 'td' ); # The only way to get any actual contents: sub lit (@) { $coll{contents} .= $_ for @_ } # In the simple case, this one reads a lot better: sub cells (@) { cell { lit $_; } for @_ } # I cannot approach a templating system without attributes, right? # (I am sorry already. I just cannot help it ...) sub att (@) { # Sets/unsets all attributes each time -- last call gets served: $coll{attributes}=""; while (@_) { my ($key, $value) = (shift, shift); unless (defined $value) { $coll{attributes} .= " $key"; next; } # From CGI::Utils::simple_escape: $value =~ s{&}{&amp;}gso; $value =~ s{<}{&lt;}gso; $value =~ s{>}{&gt;}gso; $value =~ s{\"}{&quot;}gso; $coll{attributes} .= qq( $key="$value"); } } } # Your examples would then need s/cell/cells/g and a print: print table { for (1..10) { row { cells "foo", "bar", "baz"; } } }; print table { row { cells @$_ } for $db->query('SELECT * FROM foo')->a +rrays }; # I have been known to overdesign my examples as well ... print table { att border => 1; my %valign = qw( foo top bar center baz bottom); my $quux_cell = sub { my $quux = shift; cell { lit $quux; att valign => $valign{$quux} if $valign{$quux}; +}; }; for (1..10) { row { cell { table { for ("top", "center", "bottom") { row { cell { att align => 'center'; lit $_; }; }; } }; }; for (qw/foo bar baz/) { $quux_cell->($_); } } } }; # New examples, per the update (see above): # Would have had the rows appearing twice in my original implementatio +n; # the first time in the order generated: print table { my @r = map {row { cells $_, $_*$_ }} 1..10; for (0..$#r) { my $j = rand($_); @r[$_,$j]=@r[$j,$_]; } lit @r; }; # Would have had all of the inner tables appearing twice in my # original implementation; the first time at the start of the row, # outside of the TD elements: print table { my $x; for (1..3) { row { for (1..3) { ++$x; cells $x, table { att border => 1; for (1..$x) { row { cells(("&nbsp;")x$x) } } }; } }; } };

    The Sidhekin
    print "Just another Perl ${\(trickster and hacker)},"

      But I like pure functions. This approach leaves me no easy way to stick the generated HTML into a variable.

      There's that print again. I said I was breaking those laws :)

      our $print = sub { print shift }; sub table (&) { $print->("<table>\n"); shift->(); $print->("</table>\n +"); } sub row (&) { $print->("<tr>"); shift->(); $print->("</tr>\n"); } sub cell { $print->("<td>$_</td>") for @_; } table { row { cell "foo" } }; my $html = "<html><body>"; local $print = sub { $html .= shift }; table { row { cell "foo" } }; $html .= "</body></html>";
      But really, I did not intend to write re-usable or pure code. Just something that feels comfortable and is very easy to use. In practice, I rarely need HTML in a variable. I don't mind writing HTML manually for that one time per month :)

      Alternatively, break the rules again and just open the scalar as if it were a file:

      sub table (&) { print-"<table>\n"; shift->(); print "</table>\n"; sub row (&) { print "<tr>"; shift->(); print "</tr>\n"; } sub cell { print "<td>$_</td>" for @_; } table { row { cell "foo" } }; my $html = "<html><body>"; open my $fh, '>>', \$html; select $fh; table { row { cell "foo" } }; close $fh; $html .= "</body></html>";

      I cannot see how to do nested tables at all, when the table function is written to print.

      You shouldn't need nested tables. If you find yourself needing a table in another table, that is probably because one table is there just for the layout of the page. And such a table isn't created with something like this, because you need attributes badly.

      I meant the code to be simple. To get there, I broke quite some unwritten rules about purity and reusability. It was not my intention to support anything more than printing simple tables.

      Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

        Alternatively, break the rules again and just open the scalar as if it were a file

        Yes, sure. I said "no easy way", not "no way at all". But you still cannot do nested elements.

        You shouldn't need nested tables. If you find yourself needing a table in another table, that is probably because one table is there just for the layout of the page. And such a table isn't created with something like this, because you need attributes badly.

        You may tell me what I "should", but when people are paying me to put tables in tables, I prefer not to argue. Besides, I addressed my need of attributes, right? :-)

        I meant the code to be simple. To get there, I broke quite some unwritten rules about purity and reusability. It was not my intention to support anything more than printing simple tables.

        Acknowledged and appreciated. I am not saying "thou shalt not print", just I often find myself needing to stuff it into a variable. Or nested tables. I just took inspiration from it, to do my own thing. Personally, I never find myself writing "simple tables" (no one is paying for that), so to me the inspiring thing was the code layout, and I tried to work out how I could make my code look like that.

        The Sidhekin
        print "Just another Perl ${\(trickster and hacker)},"

Re: Readable HTML tables
by Aristotle (Chancellor) on Aug 26, 2003 at 00:16 UTC
    Distributive property anyone?
    use CGI qw(:standard); print table(Tr([ map td([ qw/foo bar baz/]), 1..10 ])); print table(Tr([ map td($_), $db->query('SELECT * FROM foo')->arrays ] +));
    I find those perfectly readable. Shrug.

    Makeshifts last the longest.

      print table(Tr([ map td([ qw/foo bar baz/]), 1..10 ])); table { row { cell qw/foo bar baz/ } for 1..10 };
      It's just that I prefer {{}} to (([([])])), that's all... Unlike you, I don't find the CGI-ish solution perfectly readable.

      Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

        You don't have to break their being pure functions to get simple curlies - they can easily enough be rewritten to take a block. The curlies do look a little less busy. If you're consistent and sufficiently generous with your whitespace the paren-brackets are no less readable though, IMHO.

        Makeshifts last the longest.

Re: Readable HTML tables
by The Mad Hatter (Priest) on Aug 25, 2003 at 19:49 UTC
    ++ for the use of prototypes to allow for nice looking code blocks like that...I learn something new everyday.
Re: Readable HTML tables
by Anonymous Monk on Aug 25, 2003 at 17:11 UTC
    CGI.pm's html generation routines allow you to visualize it

      Still not simple enough for me. I want normal loops instead of maps. Maps are hard to read.

      Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

Re: Readable HTML tables
by Anonymous Monk on May 30, 2004 at 01:06 UTC
    sub forloop { return join "", map{$_[1]->();}(@{$_[0]}) } sub table { "<table>\n".$_[0]."</table>\n";} sub row { "<tr>".$_[0]."</tr>\n";} sub cell { "<td>".$_[0]."</td>";} my $data000 = [map{chomp;$_;}(<DATA>)]; print table( forloop $data000 , sub{ row(cell($_)) }, ) __DATA__ one two three four five
    one
    two
    three
    four
    five