--- loncom/xml/lontable.pm 2008/11/24 11:56:53 1.1 +++ loncom/xml/lontable.pm 2008/12/09 11:50:08 1.5 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Generating TeX tables. # -# $Id: lontable.pm,v 1.1 2008/11/24 11:56:53 foxr Exp $ +# $Id: lontable.pm,v 1.5 2008/12/09 11:50:08 foxr Exp $ # # # Copyright Michigan State University Board of Trustees @@ -39,7 +39,7 @@ # This module is a support packkage that helps londefdef generate # LaTeX tables using the LaTeX::Table package. A prerequisite is that -# the print generator must have added the following to the LaTeX header: +# the print generator must have added the following to the LaTeX # # \usepackage{xtab} # \usepackage{booktabs} @@ -77,11 +77,6 @@ Opens a new table row. Closes a table row. -=item start_header - -Starts a new row that has the header attribute (e.g. tagged row). -header rows are ended with an end_row just like any ordinary row. - =item configure_row Modifies a configuration item in the currently open row. @@ -94,6 +89,10 @@ Returns the generated table string. Configures a table's global configuration. +=item add_cell + +Add and configure a cell to the current row.6 + =back =cut @@ -181,15 +180,9 @@ Each row of table data is an element of =over 3 -=item is_header - -True if the user wants to format this row like a header. This row will be used to generate -the table header. All header rows will be gathered together into the table header. If there -are multiple table headers interspersed with non table header data, this can lead to some -surprises. =item default_halign - +0 Default horizontal alignment for cells in this row. =item default_valign @@ -203,6 +196,11 @@ The contents of each element of this has =over 3 +=item header + +If present, the row is a 'header' that is it was made via the + tag. + =item halign If present, overrides the row default horizontal alignment. @@ -239,7 +237,7 @@ sub new { my $self = { alignment => "left", outer_border => 0, - inner_borders => 0, + inner_border => 0, caption => "", theme => "Zurich", column_count => 0, @@ -256,14 +254,389 @@ sub new { return $self; } + #------------------------------------------------------------------------- # # Methods that get/set table global configuration. +# + +=pod + +=head2 Gets/set alignment. + +If the method is passed a new alignment value, that replaces the current one. +Regardless, the current alignment is used: + +=head3 Examples: + + my $align = $table->alignment(); # Return current alignment + $table->alignment("center"); # Attempt centered alignment. + +=cut + +sub alignment { + my ($self, $new_value) = @_; + + if (defined($new_value)) { + $self->{'alignment'} = $new_value; + } + return $self->{'alignment'}; +} + +=pod + +=head2 table_border + +Set or get the presence of an outer border in the table. +If passed a parameter, that parameter replaces the current request +for or not for an outer border. Regardless, the function returns +the final value of the outer_border request. + +=head3 Examples: + + $table->table_border(1); # Request an outer border. + my $outer_requested = $table->table_border(); + +=cut + +sub table_border { + my ($self, $new_value) = @_; + + if (defined($new_value)) { + $self->{'outer_border'} = $new_value; + } + return $self->{'outer_border'}; +} + + +=pod + +=head2 cell_border + +Set or get the presence of a request for cells to have borders +drawn around them. If a paramter is passed, it will be treated as +a new value for the cell border configuration. Regardless,the final +value of that configuration parameter is returned. + +=head3 Examples: + + my $cell_border = $table->cell_border(); # ask if cell borders are requested. + $table->cell_border(1); # Request cell borders. + +=cut + +sub cell_border { + my ($self, $new_value) = @_; + + if (defined($new_value)) { + $self->{'inner_border'} = $new_value; + } + return $self->{'inner_border'}; +} + +=pod + +=head2 caption + +Gets and/or sets the caption string for the table. The caption string appears to label +the table. If a parameter is supplied it will become the new caption string.k + +=head3 Examples: + + + $my caption = $table->caption(); + $table->caption("This is the new table caption"); + +=cut + +sub caption { + my ($self, $new_value) = @_; + + if (defined($new_value)) { + $self->{'caption'} = $new_value; + } + + return $self->{'caption'}; +} + +=pod + +=head2 theme + +Gets and optionally sets the table theme. The table theme describes how the +table will be typset by the table package. If a parameter is supplied it +will be the new theme selection. + +=head3 Examples: + + my $theme = $table->theme(); + $table->theme("Dresden"); + +=cut + +sub theme { + my ($self, $new_value) = @_; + + if (defined($new_value)) { + $self->{'theme'} = $new_value; + } + return $self->{'theme'}; +} + +=pod + +=head2 start_row + +Begins a new row in the table. If a row is already open, that row is +closed off prior to starting the new row. Rows can have the following attributes +which are specified by an optional hash passed in to this function. + +=over 3 + +=item default_halign + +The default horizontal alignment of the row. This can be "left", "center", or "right" + +=item default_valign + +The default vertical alignment of the row. This can be "top", "center", or "bottom" + +=back + +=head3 Examples: + + $table_start_row(); # no attributes. + $table_start({default_halign => "center", + default_valign => "bottom"}); # Create setting the attrbutes. +=cut + +sub start_row { + my ($self, $config) = @_; + + if ($self->{'row_open'}) { + $self->end_row(); + } + my $row_hash = { + default_halign => "left", + default_valign => "top", + cells => [] + }; + + # Override the defaults if the config hash is present: + + if (defined($config)) { + foreach my $key (keys %$config) { + $row_hash->{$key} = $config->{$key}; + } + } + + + my $rows = $self->{'rows'}; + push(@$rows, $row_hash); + + $self->{"row_open"} = 1; # Row is now open and ready for business. +} + +=pod + +=head2 end_row + +Closes off a row. Once closed, cells cannot be added to this row again. + +=head3 Examples: + + $table->end_row(); + + +=cut + +sub end_row { + my ($self) = @_; + + if ($self->{'row_open'}) { + + # 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]; + my $cells = $row->{'cells'}; + my $raw_cell_count = scalar(@$cells); + + # Need to iterate through the columns as + # colspans affect the count: + # + my $cell_count = 0; + for (my $i =0; $i < $raw_cell_count; $i++) { + $cell_count = $cell_count + $cells->[$i]->{'colspan'}; + } + if ($cell_count > $self->{'column_count'}) { + $self->{'column_count'} = $cell_count; + } + + $self->{'row_open'} = 0;; + } +} + +=pod + +=head2 configure_row + +Modify the configuration of a row. If a row is not open, a new one will be opened. + +=head3 Parameters: + +config_hash - A hash that contains new values for the set of row confiuguration +items to be modified. There is currently no check/penalty for items that are not in +the set of defined configuration properties which are: + +=over 2 + +=item default_halign + +The default horizontal alignment for text in cells in the row. This can be any of: +"left", "right" or "center". + +=item default_valign + +The default vertical alignment for text in cells in the row. This can be any of: + +"top", "bottom" or "center" + +=back + +=cut + +sub configure_row { + my ($self, $config) = @_; + + if (!$self->{'row_open'}) { + $self->start_row(); + } + + my $row = $self->{'rows'}[-1]; + foreach my $config_item (keys %$config) { + $row->{$config_item} = $config->{$config_item}; + } +} + + +=pod + +=head2 add_cell + +Add a new cell to a row. If there is a row above us, we need to +watch out for row spans that may force additional blank cell entries +to fill in the span. + +=head3 Parameters: + +=over 2 + +=item text + +Text to put in the cell. + +=item cell_config + +Hash of configuration options that override the defaults. The recognized options, +and their defaults are: + +=over 2 + +=item halign + +If nonblank overrides the row's default for the cell's horizontal alignment. + +=item valign + +If nonblank, overrides the row's default for the cdell's vertical alignment. + +=item rowspan + +Number of rows the cell spans. +=item colspan + +Number of columns the cell spans. + +=back + +=cut + +sub add_cell { + my ($self, $text, $config) = @_; + + # If a row is not open, we must open it: + + if (!$self->{'row_open'}) { + $self->start_row(); + } + + my $current_row = $self->{'rows'}->[-1]; + my $current_cells = $current_row->{'cells'}; + # The way we handle row spans is to insert additional + # blank cells as needed to reach this column. Each + # cell that is inserted is empty, but has a row span decreased by one + # from the row above. Column spans are propagated down from the row above + # and handled when the table's LaTeX is generated. + # There must be at least two rows in the row table to need to do this: + + my $rows = $self->{'rows'}; + my $row_count = scalar(@$rows); + if ($row_count > 1) { + my $prior_row = $rows->[-2]; + my $cells = $current_row->{'cells'}; + my $prior_cells = $prior_row->{'cells'}; + my $curr_colcount = scalar(@$cells); + + my $prior_colcount = scalar(@$prior_cells); + + while (($curr_colcount < $prior_colcount) && + $prior_cells->[$curr_colcount]->{'rowspan'} > 1) { + my %cell; + my $prior_cell = $prior_cells->[$curr_colcount]; + %cell = %$prior_cell; + $cell{'rowspan'}--; + $cell{'contents'} = ""; + push(@$current_cells, \%cell); + $curr_colcount += $prior_cells->[$curr_colcount]->{'colspan'}; # could be a colspan too. + } + } + # + # Now we're ready to build up our cell: + + my $cell = { + rowspan => 1, + colspan => 1, + contents => $text + }; + + if (defined($config)) { + foreach my $key (keys(%$config)) { + $cell->{$key} = $config->{$key}; + } + } + push(@$current_cells, $cell); +} + +# The following methods allow for testability. + +sub get_object_attribute { + my ($self, $attribute) = @_; + return $self->{$attribute}; +} + +sub get_row { + my ($self, $row) = @_; + my $rows = $self->{'rows'}; # ref to an array.... + return $rows->[$row]; # ref to the row hash for the selected row. +} # Mandatory initialization. +BEGIN{ +} 1; __END__