package Whatpm::HTMLTable;
use strict;
## An implementation of "Forming a table" algorithm in HTML5
sub form_table ($$$;$) {
my (undef, $table_el, $onerror, $must_level) = @_;
$onerror ||= sub { };
$must_level ||= 'm';
## Step 1
my $x_width = 0;
## Step 2
my $y_height = 0;
my $y_max_node;
## Step 3
my $pending_tfoot = [];
## Step 4
my $table = {
#caption
column => [],
column_group => [],
row => [], ## NOTE: HTML5 algorithm doesn't associate rows with
s.
row_group => [],
cell => [],
};
my @column_has_anchored_cell;
my @row_has_anchored_cell;
my @column_generated_by;
my @row_generated_by;
## Step 5
my @table_child = @{$table_el->child_nodes};
return $table unless @table_child;
my $process_row_group;
my $end = sub {
## Step 20 (End)
for (@$pending_tfoot) {
$process_row_group->($_);
}
## Step 21
for (0 .. $x_width - 1) {
unless ($column_has_anchored_cell[$_]) {
if ($table->{column}->[$_]) {
$onerror->(type => 'column with no anchored cell',
node => $table->{column}->[$_]->{element},
level => $must_level);
} else {
$onerror->(type => 'colspan creates column with no anchored cell',
node => $column_generated_by[$_],
level => $must_level);
}
last; # only one error.
}
}
for (0 .. $y_height - 1) {
unless ($row_has_anchored_cell[$_]) {
if ($table->{row}->[$_]) {
$onerror->(type => 'row with no anchored cell',
node => $table->{row}->[$_]->{element},
level => $must_level);
} else {
$onerror->(type => 'rowspan creates row with no anchored cell',
node => $row_generated_by[$_],
level => $must_level);
}
last; # only one error.
}
}
## Step 22
#return $table;
}; # $end
## Step 6, 7, 9
my $current_element;
my $current_ln;
NEXT_CHILD: {
$current_element = shift @table_child;
if (defined $current_element) {
redo NEXT_CHILD unless $current_element->node_type == 1;
my $nsuri = $current_element->namespace_uri;
redo NEXT_CHILD unless defined $nsuri and
$nsuri eq q;
$current_ln = $current_element->manakai_local_name;
if ($current_ln eq 'caption' and not defined $table->{caption}) {
## Step 8
$table->{caption} = {element => $current_element};
redo NEXT_CHILD; # Step 9
}
redo NEXT_CHILD unless {
#caption => 1, ## Step 7
colgroup => 1,
thead => 1,
tbody => 1,
tfoot => 1,
tr => 1,
}->{$current_ln};
} else {
## End of subsection
## Step 6 2nd paragraph
$end->();
return $table;
}
} # NEXT_CHILD
## Step 10
while ($current_ln eq 'colgroup') { # Step 10, Step 10.4
## Step 10.1: column groups
my @col = grep {
$_->node_type == 1 and
defined $_->namespace_uri and
$_->namespace_uri eq q and
$_->manakai_local_name eq 'col'
} @{$current_element->child_nodes};
if (@col) {
## Step 1
my $x_start = $x_width;
## Step 2, 6
while (@col) {
my $current_column = shift @col;
## Step 3: columns
my $span = 1;
my $col_span = $current_column->get_attribute_ns (undef, 'span');
## Parse non-negative integer
if (defined $col_span and $col_span =~ /^[\x09-\x0D\x20]*([0-9]+)/) {
$span = $1 || 1;
}
## ISSUE: If span=0, what is /span/ value?
## Step 4, 5
$table->{column}->[++$x_width] = {element => $current_column}
for 1..$span;
}
## Step 7
my $cg = {element => $current_element,
x => $x_start, y => 0,
width => $x_width - $x_start};
$table->{column_group}->[$_] = $cg for $x_start .. $x_width - 1;
} else { # no children
## Step 1
my $span = 1;
my $col_span = $current_element->get_attribute_ns (undef, 'span');
## Parse non-negative integer
if (defined $col_span and $col_span =~ /^[\x09-\x0D\x20]*([0-9]+)/) {
$span = $1 || 1;
}
## ISSUE: If span=0, what is /span/ value?
## Step 2
$x_width += $span;
## Step 3
my $cg = {element => $current_element,
x => $x_width - $span, y => 0,
width => $span};
$table->{column_group}->[$_] = $cg for $cg->{x} .. $x_width - 1;
}
## Step 10.2, 10.3
NEXT_CHILD: {
$current_element = shift @table_child;
if (defined $current_element) {
redo NEXT_CHILD unless $current_element->node_type == 1;
my $nsuri = $current_element->namespace_uri;
redo NEXT_CHILD unless defined $nsuri and
$nsuri eq q;
$current_ln = $current_element->manakai_local_name;
redo NEXT_CHILD unless {
colgroup => 1,
thead => 1,
tbody => 1,
tfoot => 1,
tr => 1,
}->{$current_ln};
} else {
## End of subsection
## Step 5 of overall steps 2nd paragraph
$end->();
return $table;
}
} # NEXT_CHILD
}
## Step 11
my $y_current = 0;
## Step 12
my @downward_growing_cells;
my $growing_downward_growing_cells = sub {
for (@downward_growing_cells) {
for my $x ($_->[1] .. ($_->[1] + $_->[2] - 1)) {
$table->{cell}->[$x]->[$y_current] = [$_->[0]];
$_->[0]->{height}++;
}
}
}; # $growing_downward_growing_cells
my $process_row = sub {
## Step 1
$y_height++ if $y_height == $y_current;
## Step 2
my $x_current = 0;
## Step 3
my $tr = shift;
$table->{row}->[$y_current] = {element => $tr};
my @tdth = grep {
$_->node_type == 1 and
defined $_->namespace_uri and
$_->namespace_uri eq q and
{td => 1, th => 1}->{$_->manakai_local_name}
} @{$tr->child_nodes};
my $current_cell = shift @tdth;
## Step 4
$growing_downward_growing_cells->();
return unless $current_cell;
## ISSUE: Support for empty
(removed at revision 1376).
CELL: while (1) {
## Step 5: cells
$x_current++
while ($x_current < $x_width and
$table->{cell}->[$x_current]->[$y_current]);
## Step 6
$x_width++ if $x_current == $x_width;
## Step 7
my $colspan = 1;
my $attr_value = $current_cell->get_attribute_ns (undef, 'colspan');
if (defined $attr_value and $attr_value =~ /^[\x09-\x0D\x20]*([0-9]+)/) {
$colspan = $1 || 1;
}
## Step 8
my $rowspan = 1;
my $attr_value = $current_cell->get_attribute_ns (undef, 'rowspan');
if (defined $attr_value and $attr_value =~ /^[\x09-\x0D\x20]*([0-9]+)/) {
$rowspan = $1;
}
## Step 9
my $cell_grows_downward;
if ($rowspan == 0) {
$cell_grows_downward = 1;
$rowspan = 1;
}
## Step 10
if ($x_width < $x_current + $colspan) {
@column_generated_by[$_] = $current_cell
for $x_width .. $x_current + $colspan - 1;
$x_width = $x_current + $colspan;
}
## Step 11
if ($y_height < $y_current + $rowspan) {
@row_generated_by[$_] = $current_cell
for $y_height .. $y_current + $rowspan - 1;
$y_height = $y_current + $rowspan;
$y_max_node = $current_cell;
}
## Step 12
my $cell = {
is_header => ($current_cell->manakai_local_name eq 'th'),
element => $current_cell,
x => $x_current, y => $y_current,
width => $colspan, height => $rowspan,
};
$column_has_anchored_cell[$x_current] = 1;
$row_has_anchored_cell[$y_current] = 1;
for my $x ($x_current .. ($x_current + $colspan - 1)) {
for my $y ($y_current .. ($y_current + $rowspan - 1)) {
unless ($table->{cell}->[$x]->[$y]) {
$table->{cell}->[$x]->[$y] = [$cell];
} else {
$onerror->(type => "cell overlapping:$x:$y", node => $current_cell,
level => $must_level);
push @{$table->{cell}->[$x]->[$y]}, $cell;
}
}
}
## Step 13
if ($cell_grows_downward) {
push @downward_growing_cells, [$cell, $x_current, $colspan];
}
## Step 14
$x_current += $colspan;
## Step 15-17
$current_cell = shift @tdth;
if (defined $current_cell) {
## Step 16-17
#
} else {
## Step 15
$y_current++;
last CELL;
}
} # CELL
}; # $process_row
$process_row_group = sub ($) {
## Step 1
my $y_start = $y_height;
## Step 2
for (grep {
$_->node_type == 1 and
defined $_->namespace_uri and
$_->namespace_uri eq q and
$_->manakai_local_name eq 'tr'
} @{$_[0]->child_nodes}) {
$process_row->($_);
}
## Step 3
if ($y_height > $y_start) {
my $rg = {element => $current_element, ## ISSUE: "element being processed"?
x => 0, y => $y_start,
height => $y_height - $y_start};
$table->{row_group}->[$_] = $rg for $y_start .. $y_height - 1;
}
## Step 4
## Ending a row group
## Step 1
while ($y_current < $y_height) {
## Step 1
$growing_downward_growing_cells->();
## Step 2
$y_current++;
}
## Step 2
@downward_growing_cells = ();
}; # $process_row_group
## Step 13: rows
unshift @table_child, $current_element;
ROWS: {
NEXT_CHILD: {
$current_element = shift @table_child;
if (defined $current_element) {
redo NEXT_CHILD unless $current_element->node_type == 1;
my $nsuri = $current_element->namespace_uri;
redo NEXT_CHILD unless defined $nsuri and
$nsuri eq q;
$current_ln = $current_element->manakai_local_name;
redo NEXT_CHILD unless {
thead => 1,
tbody => 1,
tfoot => 1,
tr => 1,
}->{$current_ln};
} else {
## Step 6 2nd paragraph
$end->();
return $table;
}
} # NEXT_CHILD
## Step 14
if ($current_ln eq 'tr') {
$process_row->($current_element);
# advance (done at the first of ROWS)
redo ROWS;
}
## Step 15
## Ending a row group
## Step 1
while ($y_current < $y_height) {
## Step 1
$growing_downward_growing_cells->();
## Step 2
$y_current++;
}
## Step 2
@downward_growing_cells = ();
## Step 16
if ($current_ln eq 'tfoot') {
push @$pending_tfoot, $current_element;
# advance (done at the top of ROWS)
redo ROWS;
}
## Step 17
# thead or tbody
$process_row_group->($current_element);
## Step 18
# Advance (done at the top of ROWS).
## Step 19
redo ROWS;
} # ROWS
$end->();
return $table;
} # form_table
## TODO: Implement scope="" algorithm
1;
## $Date: 2008/05/05 08:00:25 $