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{&}{&}gso;
$value =~ s{<}{<}gso;
$value =~ s{>}{>}gso;
$value =~ s{\"}{"}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((" ")x$x) }
}
};
}
};
}
};
The Sidhekin
print "Just another Perl ${\(trickster and hacker)}," |