--- loncom/xml/lontable.pm 2011/04/05 10:02:58 1.16 +++ loncom/xml/lontable.pm 2011/04/13 10:08:06 1.17 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Generating TeX tables. # -# $Id: lontable.pm,v 1.16 2011/04/05 10:02:58 foxr Exp $ +# $Id: lontable.pm,v 1.17 2011/04/13 10:08:06 foxr Exp $ # # # Copyright Michigan State University Board of Trustees @@ -57,8 +57,9 @@ package Apache::lontable; use strict; use Apache::lonlatextable; use Apache::lonnet; # for trace logging. +use Data::Dumper; -my $tracing = 0; # Set to 1 to enable log tracing. 2 for local sub tracing. +my $tracing = 1; # Set to 1 to enable log tracing. 2 for local sub tracing. =pod @@ -108,7 +109,7 @@ modified by this. These configuration i my $table = lontable::new(\%config_hash) -=over3 +=over 3 =item alignment @@ -211,11 +212,10 @@ of the cells in the row. =item cells + Array of hashes where each element represents the data for a cell. The contents of each element of this hash are described below: -=over 3 - =item header If present, the row is a 'header' that is it was made via the @@ -248,14 +248,13 @@ The contents of the cell. =back -=back =cut sub new { my ($class, $configuration) = @_; - + if ($tracing) {&Apache::lonnet::logthis("new table"); } # Initialize the object member data with the default values # then override with any stuff in $configuration. @@ -267,8 +266,14 @@ sub new { theme => "plain", column_count => 0, row_open => 0, - rows => [], - col_widths => {} + rows => { + 'body' => [], + 'head' => [], + 'foot' => [] + }, + col_widths => {}, + part => 'body' # one of 'body', 'head', 'foot'. + }; foreach my $key (keys %$configuration) { @@ -359,8 +364,10 @@ Valid values for the parameter are: =item 3 - Border at the left and right sides of the cell. +=item 4 - Border around groups (colgroups as well as thead/tfoot/tbody). -=over -2 + +=back =head3 Examples: @@ -430,11 +437,11 @@ sub theme { =pod -=head 2 width +=head2 width Gets and optionally sets the width of the table. -=head 3 Examples: +=head3 Examples: my $newwidth = $table->width("10cm"); # 10cm width returns "10cm". @@ -499,7 +506,7 @@ sub start_row { } - my $rows = $self->{'rows'}; + my $rows = $self->{'rows'}->{$self->{'part'}}; push(@$rows, $row_hash); $self->{"row_open"} = 1; # Row is now open and ready for business. @@ -525,8 +532,10 @@ sub end_row { # Mostly we need to determine if this row has the maximum # cell count of any row in existence in the table: - - my $row = $self->{'rows'}->[-1]; + + &Apache::lonnet::logthis($self->{'part'}); + &Apache::lonnet::logthis(Dumper($self->{'rows'})); + my $row = $self->{'rows'}->{$self->{'part'}}->[-1]; my $cells = $row->{'cells'}; if ($row->{'cell_width'} > $self->{'column_count'}) { @@ -574,7 +583,7 @@ sub configure_row { $self->start_row(); } - my $row = $self->{'rows'}[-1]; + my $row = $self->{'rows'}->{$self->{'part'}}->[-1]; foreach my $config_item (keys %$config) { $row->{$config_item} = $config->{$config_item}; } @@ -629,6 +638,8 @@ Note as well that if width specification =back +=back + =cut sub add_cell { @@ -641,7 +652,7 @@ sub add_cell { if (!$self->{'row_open'}) { $self->start_row(); } - my $rows = $self->{'rows'}; + my $rows = $self->{'rows'}->{$self->{'part'}}; my $current_row = $rows->[-1]; my $current_cells = $current_row->{'cells'}; my $last_coord = $current_row->{'cell_width'}; @@ -754,14 +765,178 @@ sub append_cell_text { my ($this, $text) = @_; if($tracing) {&Apache::lonnet::logthis("append_cell_text: $text"); } - my $rows = $this->{'rows'}; + my $rows = $this->{'rows'}->{$this->{'part'}}; my $current_row = $rows->[-1]; my $cells = $current_row->{'cells'}; my $current_cell = $cells->[-1]; $current_cell->{'contents'} .= $text; } +#-------------------------- Support for row/column groups. ---- + +=pod + +=head2 start_head + +starts the table head. This corresponds to the tag in +html/xml. All rows defined in this group will be +collected and placed at the front of the table come rendering time. +Furthermore, if the table has group borders enabled, a rule will be +rendered following and preceding this group of rows. + +=cut + +sub start_head { + my ($this) = @_; + if ($tracing) { &Apache::lonnet::logthis("start_head"); } + $this->{'part'} = 'head'; +} + +=pod + +=head2 end_head + +Ends a table head. This corresponds to the + closing tag in html/xml. + +=cut + +sub end_head { + my ($this) = @_; + if ($tracing) { &Apache::lonnet::logthis("end_head"); } + $this->{'part'} = 'body'; +} + +=pod + +=head2 start_foot + +Starts the table footer. All of the rows generated in the footer will +be rendered at the bottom of the table. This sub corresponds to the + tag in html/xml. If the table has group borders enabled, a rule +will be rendered at the top and bottom of the set of columns in this +group + +=cut + +sub start_foot { + my ($this) = @_; + if ($tracing) { &Apache::lonnet::logthis("start_foot"); } + $this->{'part'} = 'foot'; +} + +=pod + +=head2 end_foot + +Ends the set of rows in the table footer. This corresponds to the + end tag in xml/html. + +=cut + +sub end_foot { + my ($this) = @_; + if ($tracing) { &Apache::lonnet::logthis("end_foot") } + $this->{'part'} = 'body'; +} + +=pod + +=head2 start_body + +Starts the set of rows that will be in the table body. Note that if +we are not in the header or footer, body rows are implied. +This correspondes to the presence of a tag in html/xml. +If group borders are on, a rule will be rendered at the top and bottom +of the body rows. + +=cut + +sub start_body { + my ($this) = @_; + if ($tracing) { &Apache::lonnet::logthis("start_body"); } + $this->{'part'} = 'body'; +} + +=pod + +=head2 end_body + +Ends the set of rows in a table body. Note that in the event we are not +in the header or footer groups this code assumes we are in the body +group. I believe this is a good match to how mot browsers render. + +=cut + +sub end_body { + my ($this) = @_; + if ($tracing) { &Apache::lonnet::logthis("end_body"); } + +} + +=pod + +=head2 define_colgroup + +Define a column group a column group corresponds to the + tag in Html/Xml. A column group as we implement it has +the following properties tht will be shared amongst all cells in the +columns in the group unless overidden in the specific oell definition: + +=over 2 + +=item span + +The number of columns in the column group. This defaults to 1. + +=item halign + +Horizontal alignment of the cells. This defaults to left. +Other values are left, center, right (justify and char are +accepted but treated as left). + +=item valign + +Vertical alignment of the cells. This defaults to middle. +Other values are top middle, bottom, (baseline is accepted and +treated as top). + +=back + +If group borders are turned on, a rule will be rendered +at the left and right side of the column group. + +=head3 parameters + +=over 2 + +=item definition + +This is a hash that contains any of the keys described above that +define the column group. + +=back + + +=head3 Example + + $table->define_colgroup({ + 'span' => 2, + 'halign' => 'center' + }) + + + +=cut + +sub define_colgroup { + my ($this, $attributes) = @_; + if ($tracing) { &Apache::lonnet::logthis("col_group"); } + + +} +#------------------------- Render the table --------------------- =pod @@ -774,6 +949,7 @@ a certain amount of testing to be done o The caller can then ask the table object to generate LaTeX. =cut + sub generate { my ($this) = @_; my $useP = 0; @@ -784,7 +960,14 @@ sub generate { if($tracing) {&Apache::lonnet::logthis("generate"); } my $table = Apache::lonlatextable->new(); + my $inner_border = $this->{'inner_border'}; + my $outer_border = $this->{'outer_border'}; + my $column_count = $this->{'column_count'}; + my $cell_ul_border = (($inner_border == 1) || ($inner_border == 2)) ? 1 : 0; + my $cell_lr_border = (($inner_border == 1) || ($inner_border == 3)) ? 1 : 0; + my $part_border = ($inner_border == 4); + # Add the caption if supplied. if ($this->{'caption'} ne "") { @@ -848,13 +1031,93 @@ sub generate { } } - + if ($tracing) { &Apache::lonnet::logthis("rendering head"); } + $this->render_part('head', $table, $useP, $default_width); + if ($tracing) { &Apache::lonnet::logthis("rendering body"); } + $this->render_part('body', $table, $useP, $default_width); + if ($tracing) { &Apache::lonnet::logthis("rendering footer"); } + $this->render_part('foot', $table, $useP, $default_width); + + + + + + my $coldef = ""; + if ($outer_border || $cell_lr_border) { + $coldef .= '|'; + } + for (my $i =0; $i < $column_count; $i++) { + if ($useP) { + $coldef .= "p{$default_width $colunits}"; + } else { + $coldef .= 'l'; + } + if ($cell_lr_border || + ($outer_border && ($i == $column_count-1))) { + $coldef .= '|'; + } + } + $table->{'coldef'} = $coldef; + + # Return the table: + + if ($tracing) { &Apache::lonnet::logthis("Leaving generate"); } + + + return $table; + +} + + +#--------------------------------------------------------------------------- +# +# Private methods: +# + +# +# Convert size with units -> size in cm. +# The resulting size is floating point with no units so that it can be used in +# computation. Note that an illegal or missing unit is treated silently as +# cm for now. +# +sub size_to_cm { + my ($this, $size_spec) = @_; + my ($size, $units) = split(/ /, $size_spec); + if (lc($units) eq 'mm') { + return $size / 10.0; + } + if (lc($units) eq 'in') { + return $size * 2.54; + } + + return $size; # Default is cm. +} + +# +# Render a part of the table. The valid table parts are +# head, body and foot. These corresopnd to the set of rows +# define within , and +# respectively. +# +sub render_part { + my ($this, $part, $table, $useP, $default_width) = @_; + + if ($tracing) { &Apache::lonnet::logthis("render_part: $part") }; + + # Do nothing if that part of the table is empty: + + &Apache::lonnet::logthis(Dumper($this->{'rows'})); + if ($this->{'rows'}->{$part} == undef) { + if ($tracing) {&Apache::lonnet::logthis("$part is empty"); } + return; + } # Build up the data: my @data; - my $rows = $this->{'rows'}; + my $colwidths = $this->{'col_widths'}; + my $rows = $this->{'rows'}->{$part}; # TODO: Render header, body footer as groups. my $row_count = scalar(@$rows); my $inner_border = $this->{'inner_border'}; my $outer_border = $this->{'outer_border'}; @@ -862,10 +1125,14 @@ sub generate { my $cell_ul_border = (($inner_border == 1) || ($inner_border == 2)) ? 1 : 0; my $cell_lr_border = (($inner_border == 1) || ($inner_border == 3)) ? 1 : 0; - + my $part_border = ($inner_border == 4); + my $colunits = 'cm'; # All units in cm. + # Add a top line if the outer or inner border is enabled: + # or if group rules are on. + # - if ($outer_border || $cell_ul_border) { + if ($outer_border || $cell_ul_border || $part_border) { push(@data, ["\\cline{1-$column_count}"]); } @@ -974,60 +1241,13 @@ sub generate { # Add bottom border if necessary: if the inner border was on, the loops above # will have done a bottom line under the last cell. # - if ($outer_border && !$cell_ul_border) { + if (($outer_border || $part_border) && !$cell_ul_border) { push(@data, ["\\cline{1-$column_count}"]); } - $table->set_data(\@data); - - my $coldef = ""; - if ($outer_border || $cell_lr_border) { - $coldef .= '|'; - } - for (my $i =0; $i < $column_count; $i++) { - if ($useP) { - $coldef .= "p{$default_width $colunits}"; - } else { - $coldef .= 'l'; - } - if ($cell_lr_border || - ($outer_border && ($i == $column_count-1))) { - $coldef .= '|'; - } - } - $table->{'coldef'} = $coldef; - - # Return the table: - - if ($tracing) { &Apache::lonnet::logthis("Leaving generate"); } - - - return $table; - + $table->set_data(\@data); } -#--------------------------------------------------------------------------- -# -# Private methods: -# -# -# Convert size with units -> size in cm. -# The resulting size is floating point with no units so that it can be used in -# computation. Note that an illegal or missing unit is treated silently as -# cm for now. -# -sub size_to_cm { - my ($this, $size_spec) = @_; - my ($size, $units) = split(/ /, $size_spec); - if (lc($units) eq 'mm') { - return $size / 10.0; - } - if (lc($units) eq 'in') { - return $size * 2.54; - } - - return $size; # Default is cm. -} #---------------------------------------------------------------------------- # The following methods allow for testability. @@ -1042,7 +1262,7 @@ sub get_row { my ($self, $row) = @_; if ($tracing > 1) { &Apache::lonnet::logthis("get_row"); } - my $rows = $self->{'rows'}; # ref to an array.... + my $rows = $self->{'rows'}->{$self->{'part'}}; # ref to an array.... return $rows->[$row]; # ref to the row hash for the selected row. } @@ -1052,4 +1272,4 @@ BEGIN{ 1; __END__ - +