Diff for /loncom/xml/lontable.pm between versions 1.5 and 1.12

version 1.5, 2008/12/09 11:50:08 version 1.12, 2010/08/27 09:42:49
Line 38 Line 38
 #  #
   
 # This module is a support packkage that helps londefdef generate  # This module is a support packkage that helps londefdef generate
 # LaTeX tables using the LaTeX::Table package.  A prerequisite is that  # LaTeX tables using the Apache::lonlatextable package.  A prerequisite is that
 # the print generator must have added the following to the LaTeX   # the print generator must have added the following to the LaTeX 
 #  #
 #  \usepackage{xtab}  #  \usepackage{xtab}
Line 55 Line 55
   
 package Apache::lontable;  package Apache::lontable;
 use strict;  use strict;
 use LaTeX::Table;  use Apache::lonlatextable;
   use Apache::lonnet; # for trace logging.
   
   my $tracing = 1; # Set to 1 to enable log tracing. 2 for local sub tracing.
   
 =pod  =pod
   
 =head1  lontable Table generation assistant for the LaTeX target  =head1  lontable Table generation assistant for the LaTeX target
   
 This module contains support software for generating tables in LaTeX output mode   This module contains support software for generating tables in LaTeX output mode 
 In this implementation, we use the LaTeX::Table package to do the actual final formatting.  In this implementation, we use the Apache::lonlatextable package to do the actual final formatting.
 Each table creates a new object.  Table objects can have global properties configured.  Each table creates a new object.  Table objects can have global properties configured.
 The main operations on a table object are:  The main operations on a table object are:
   
Line 108  modified by this.  These configuration i Line 110  modified by this.  These configuration i
   
 =over3  =over3
   
   
 =item alignment  =item alignment
   
 Table alignment.  Some table styles support this but not all.  Table alignment.  Some table styles support this but not all.
Line 128  The table caption text. Line 131  The table caption text.
   
 The theme of the table to use.  Defaults to Zurich.  Themes we know about are:  The theme of the table to use.  Defaults to Zurich.  Themes we know about are:
 NYC, NYC2, Zurich, Berlin, Dresden, Houston, Miami, plain, Paris.  Other themes can be added  NYC, NYC2, Zurich, Berlin, Dresden, Houston, Miami, plain, Paris.  Other themes can be added
 to the LaTeX::Table package, and they will become supported automatically, as theme names are  to the Apache::lonlatextable package, and they will become supported automatically, as theme names are
 not error checked.  Any use of a non-existent theme is reported by the LaTeX::Table package  not error checked.  Any use of a non-existent theme is reported by the Apache::lonlatextable package
 when the table text is generated.  when the table text is generated.
   
   =item width
   
   The width of the table.   in any
   TeX unit measure e.g.  10.8cm  This forces the table to the
   tabularx environment.  It also forces the declarations for
   cells to be paragraph mode which supports more internal formatting.
   
 =back  =back
   
 =head3 Member data  =head3 Member data
Line 164  Table caption (configurable). Line 174  Table caption (configurable).
   
 Theme desired (configurable).  Theme desired (configurable).
   
   =item width
   
   If defined, the width of the table (should be supplied
   in fraction of column width e.g. .75 for 75%.
   
 =item row_open   =item row_open 
   
 True if a row is open and not yet closed.  True if a row is open and not yet closed.
Line 189  Default horizontal alignment for cells i Line 204  Default horizontal alignment for cells i
   
 Default vertical alignment for cells in this row (may be ignored).  Default vertical alignment for cells in this row (may be ignored).
   
   =item cell_width
    
   The width of the row in cells.  This is the sum of the column spans 
   of the cells in the row.
   
 =item cells  =item cells
   
 Array of hashes where each element represents the data for a cell.  Array of hashes where each element represents the data for a cell.
Line 218  If present, indicates the number of rows Line 238  If present, indicates the number of rows
 If present indicates the number of columns this cell spans.  If present indicates the number of columns this cell spans.
 Note that a cell can span both rows and columns.  Note that a cell can span both rows and columns.
   
   =item start_col
   
   The starting column of the cell in the table grid.
   
 =item contents  =item contents
   
 The contents of the cell.  The contents of the cell.
Line 231  The contents of the cell. Line 255  The contents of the cell.
 sub new {  sub new {
     my ($class, $configuration) = @_;      my ($class, $configuration) = @_;
   
       if($tracing) {&Apache::lonnet::logthis("new table object");}
   
     #  Initialize the object member data with the default values      #  Initialize the object member data with the default values
     #  then override with any stuff in $configuration.      #  then override with any stuff in $configuration.
   
Line 239  sub new { Line 265  sub new {
  outer_border   => 0,   outer_border   => 0,
  inner_border  => 0,   inner_border  => 0,
  caption        => "",   caption        => "",
  theme          => "Zurich",   theme          => "plain",
  column_count   => 0,   column_count   => 0,
  row_open       => 0,   row_open       => 0,
  rows           => [],   rows           => [],
Line 277  Regardless, the current alignment is use Line 303  Regardless, the current alignment is use
 sub alignment {  sub alignment {
     my ($self, $new_value) = @_;      my ($self, $new_value) = @_;
   
       if ($tracing) {&Apache::lonnet::logthis("alignment = $new_value");}
   
     if (defined($new_value)) {      if (defined($new_value)) {
  $self->{'alignment'} = $new_value;   $self->{'alignment'} = $new_value;
     }      }
Line 302  the final value of the outer_border requ Line 330  the final value of the outer_border requ
 sub table_border {  sub table_border {
     my ($self, $new_value) = @_;      my ($self, $new_value) = @_;
   
       if ($tracing) {&Apache::lonnet::logthis("table_border $new_value");}
   
     if (defined($new_value)) {      if (defined($new_value)) {
  $self->{'outer_border'} = $new_value;   $self->{'outer_border'} = $new_value;
     }      }
Line 327  value of that configuration parameter is Line 357  value of that configuration parameter is
   
 sub cell_border {  sub cell_border {
     my ($self, $new_value) = @_;      my ($self, $new_value) = @_;
       if($tracing) {&Apache::lonnet::logthis("cell_border: $new_value"); }
     if (defined($new_value)) {      if (defined($new_value)) {
  $self->{'inner_border'} = $new_value;   $self->{'inner_border'} = $new_value;
     }      }
Line 352  the table.  If a parameter is supplied i Line 382  the table.  If a parameter is supplied i
 sub caption {  sub caption {
     my ($self, $new_value) = @_;      my ($self, $new_value) = @_;
   
       if($tracing) {&Apache::lonnet::logthis("caption: $new_value"); }
     if (defined($new_value)) {      if (defined($new_value)) {
  $self->{'caption'} = $new_value;   $self->{'caption'} = $new_value;
     }      }
Line 376  will be the new theme selection. Line 407  will be the new theme selection.
   
 sub theme {  sub theme {
     my ($self, $new_value) = @_;      my ($self, $new_value) = @_;
       if($tracing) {&Apache::lonnet::logthis("theme $new_value"); }
     if (defined($new_value)) {      if (defined($new_value)) {
  $self->{'theme'} = $new_value;   $self->{'theme'} = $new_value;
     }      }
Line 385  sub theme { Line 416  sub theme {
   
 =pod  =pod
   
   =head 2 width
   
   Gets and optionally sets the width of the table.
   
   =head 3 Examples:
   
    my $newwidth = $table->width("10cm");   # 10cm width returns "10cm".
   
   =cut
   sub width {
       my ($self, $new_value) = @_;
       if($tracing) {&Apache::lonnet::logthis("width = $new_value"); }
   
       if (defined($new_value)) {
    $self->{'width'} = $new_value;
       }
       return $self->{'width'}; # Could be undef.
   }
   
   =pod
   
 =head2 start_row  =head2 start_row
   
 Begins a new row in the table.  If a row is already open, that row is  Begins a new row in the table.  If a row is already open, that row is
Line 413  The default vertical alignment of the ro Line 465  The default vertical alignment of the ro
   
 sub start_row {  sub start_row {
     my ($self, $config) = @_;      my ($self, $config) = @_;
       if($tracing) {&Apache::lonnet::logthis("start_row"); }
     if ($self->{'row_open'}) {       if ($self->{'row_open'}) { 
  $self->end_row();   $self->end_row();
     }      }
     my $row_hash = {      my $row_hash = {
  default_halign => "left",   default_halign => "left",
  default_valign => "top",   default_valign => "top",
    cell_width     =>  0,
  cells          => []   cells          => []
     };      };
   
Line 453  Closes off a row.  Once closed, cells ca Line 506  Closes off a row.  Once closed, cells ca
   
 sub end_row {  sub end_row {
     my ($self) = @_;      my ($self) = @_;
       if($tracing) {&Apache::lonnet::logthis("end_row"); }
     if ($self->{'row_open'}) {      if ($self->{'row_open'}) {
   
  # Mostly we need to determine if this row has the maximum   # Mostly we need to determine if this row has the maximum
  # cell count of any row in existence in the table:   # cell count of any row in existence in the table:
   
  my $row        = $self->{'rows'}[-1];   my $row        = $self->{'rows'}->[-1];
  my $cells      = $row->{'cells'};   my $cells      = $row->{'cells'};
  my $raw_cell_count = scalar(@$cells);  
   
  # Need to iterate through the columns as    if ($row->{'cell_width'} > $self->{'column_count'}) {
  # colspans affect the count:      $self->{'column_count'} = $row->{'cell_width'};
  #  
  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;;   $self->{'row_open'} = 0;;
Line 509  The default vertical alignment for text Line 554  The default vertical alignment for text
   
 sub configure_row {  sub configure_row {
     my ($self, $config) = @_;      my ($self, $config) = @_;
       if($tracing) {&Apache::lonnet::logthis("configure_row");}
     if (!$self->{'row_open'}) {      if (!$self->{'row_open'}) {
  $self->start_row();   $self->start_row();
     }      }
Line 567  Number of columns the cell spans. Line 612  Number of columns the cell spans.
 sub add_cell {  sub add_cell {
     my ($self, $text, $config) = @_;      my ($self, $text, $config) = @_;
   
       if($tracing) {&Apache::lonnet::logthis("add_cell : $text"); }
   
     # If a row is not open, we must open it:      # If a row is not open, we must open it:
   
     if (!$self->{'row_open'}) {      if (!$self->{'row_open'}) {
  $self->start_row();   $self->start_row();
     }      }
       my $rows          = $self->{'rows'};
     my $current_row   = $self->{'rows'}->[-1];      my $current_row   = $rows->[-1];
     my $current_cells = $current_row->{'cells'};       my $current_cells = $current_row->{'cells'}; 
       my $last_coord    = $current_row->{'cell_width'};
   
     # The way we handle row spans is to insert additional      #  We have to worry about row spans if there is a prior row:
     # 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'};      if (scalar(@$rows) > 1) {
     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) &&   my $last_row = $rows->[-2];
        $prior_cells->[$curr_colcount]->{'rowspan'} > 1) {   if ($last_coord < $last_row->{'cell_width'}) {
     my %cell;      my $prior_coord       = 0;
     my $prior_cell = $prior_cells->[$curr_colcount];      my $prior_cell_index  = 0;
     %cell = %$prior_cell;      while ($prior_coord <= $last_coord) {
     $cell{'rowspan'}--;  
     $cell{'contents'} = "";   # Pull a cell down if it's coord matches our start coord
     push(@$current_cells, \%cell);   # And there's a row span > 1.
     $curr_colcount += $prior_cells->[$curr_colcount]->{'colspan'}; # could be a colspan too.   # Having done so, we adjust our $last_coord to match the
    # end point of the pulled down cell.
   
    my $prior_cell = $last_row->{'cells'}->[$prior_cell_index];
    if (!defined($prior_cell)) {
       last;
    }
    if (($prior_cell->{'start_col'} == $last_coord) &&
       ($prior_cell->{'rowspan'}  > 1)) {
       
       #  Need to drop the cell down
   
       my %dropped_down_cell = %$prior_cell;
       $dropped_down_cell{'rowspan'}--;
       $dropped_down_cell{'contents'} = '';
   
       push(@$current_cells, \%dropped_down_cell);
       $last_coord += $dropped_down_cell{'colspan'};
       $current_row->{'cell_width'} = $last_coord;
       
    }
    $prior_coord += $prior_cell->{'colspan'};
    $prior_cell_index++;
       }
  }   }
   
     }      }
   
     #      #
     # Now we're ready to build up our cell:      # Now we're ready to build up our cell:
   
     my $cell = {      my $cell = {
  rowspan    => 1,   rowspan    => 1,
  colspan    => 1,   colspan    => 1,
    start_col  => $last_coord,
  contents   => $text   contents   => $text
     };      };
           
Line 618  sub add_cell { Line 679  sub add_cell {
     $cell->{$key} = $config->{$key};      $cell->{$key} = $config->{$key};
  }   }
     }      }
       $current_row->{'cell_width'} += $cell->{'colspan'};
   
     push(@$current_cells, $cell);      push(@$current_cells, $cell);
   
       if ($tracing) { &Apache::lonnet::logthis("add_cell done"); }
   }
   
   
   =pod
   
   =head2  append_cell_text
   
   Sometimes it's necessary to create/configure the cell and then later add text to it.
   This sub allows text to be appended to the most recently created cell.
   
   =head3 Parameters
   
   The text to add to the cell.
   
   =cut
   sub append_cell_text {
       my ($this, $text) = @_;
   
       if($tracing) {&Apache::lonnet::logthis("append_cell_text: $text"); }
       my $rows         = $this->{'rows'};
       my $current_row  = $rows->[-1];
       my $cells        = $current_row->{'cells'};
       my $current_cell = $cells->[-1];
       $current_cell->{'contents'} .= $text;
       
 }  }
   
   
   =pod
   
   =head2 generate
   
   Call this when the structures for the table have been built.
   This will generate and return the table object that can be used
   to generate the table.  Returning the table object allows for
   a certain amount of testing to be done on the generated table.
   The caller can then ask the table object to generate LaTeX.
   
   =cut
   sub generate {
       my ($this) = @_;
       my $useP   = 0;
       my $colwidth;
       my $colunits;
   
       if($tracing) {&Apache::lonnet::logthis("generate"); }
       my $table = Apache::lonlatextable->new();
   
   
   
       # Add the caption if supplied.
   
       if ($this->{'caption'} ne "") {
    $table->set_caption($this->caption);
       }
       
       # Set the width if defined:
   
       if (defined ($this->{'width'})) {
   # $table->set_width($this->{'width'});
   # $table->set_width_environment('tabularx');
    $useP = 1;
    ($colwidth, $colunits) = split(/ /, $this->{'width'});
    $colwidth = $colwidth/$this->{'column_count'};
   
       }
   
       # Build up the data:
   
       my @data;
       my $rows      = $this->{'rows'};
       my $row_count = scalar(@$rows);
       my $inner_border = $this->{'inner_border'};
       my $outer_border = $this->{'outer_border'};
       my $column_count = $this->{'column_count'};
   
       # Add a top line if the outer or inner border is enabled:
   
       if ($outer_border || $inner_border) {
    push(@data, ["\\cline{1-$column_count}"]);     
   
       }
   
       for (my $row = 0; $row < $row_count; $row++) {
    my @row;
    my $cells      = $rows->[$row]->{'cells'};
    my $def_halign = $rows->[$row]->{'default_halign'};
    my $cell_count = scalar(@$cells);
    my $startcol   = 1;
    my @underlines; # Array of \cline cells if cellborder on.
   
   
   
    for (my $cell  = 0; $cell < $cell_count; $cell++) {
       my $contents = $cells->[$cell]->{'contents'};
   
       #
       #  Cell alignment is the default alignment unless
       #  explicitly specified in the cell.
       #  NOTE: at this point I don't know how to do vert alignment.
       #
   
       my $halign   = $def_halign;
       if (defined ($cells->[$cell]->{'halign'})) {
    $halign = $cells->[$cell]->{'halign'};
       }
   
       # Create the horizontal alignment character:
   
       my $col_align = 'l';
       my $embeddedAlignStart = "";
       my $embeddedAlignEnd   = "";
   
       if ($halign eq 'right') {
    $col_align = 'r';
    $embeddedAlignStart = '\begin{flushright} ';
    $embeddedAlignEnd   = ' \end{flushright}';
       }
       if ($halign eq 'center') {
    $col_align = 'c';
    $embeddedAlignStart = '\begin{center}';
    $embeddedAlignEnd   = '\end{center}';
       }
   
       # If the width has been specified, turn these into
       # para mode; and wrap the contents in the start/stop stuff:
   
       if ($useP) {
    my $cw = $colwidth * $cells->[$cell]->{'colspan'};
    $col_align = "p{$cw $colunits}";
    $contents = $embeddedAlignStart . $contents .  $embeddedAlignEnd;
       }
   
       if ($inner_border || ($outer_border && ($cell == 0))) {
    $col_align = '|'.$col_align;
       }
       if ($inner_border || ($outer_border && ($cell == ($cell_count -1)))) {
    $col_align = $col_align.'|';
       }
   
       #factor in spans:
   
       my $cspan    = $cells->[$cell]->{'colspan'};
       my $nextcol  = $startcol + $cspan;
   
       # If we can avoid the \multicolumn directive that's best as
       # that makes some things like \parpic invalid in LaTeX which
               # screws everything up.
   
       if (($cspan > 1) || !($col_align =~ /l/)) {
   
    $contents = '\multicolumn{'.$cspan.'}{'.$col_align.'}{'.$contents.'}';
   
    # A nasty edge case.  If there's only one cell, the software will assume
    # we're in complete control of the row so we need to end the row ourselves.
   
    if ($cell_count == 1) {
       $contents .= '  \\\\';
    }
       }
       if ($inner_border && ($cells->[$cell]->{'rowspan'} == 1)) {
    my $lastcol = $nextcol -1;
    push(@underlines, "\\cline{$startcol-$lastcol}");
       }
       $startcol = $nextcol;
       # Rowspans should take care of themselves.
       
       push(@row, $contents);
   
    }
    push(@data, \@row);
    if ($inner_border) {
       for (my $i =0; $i < scalar(@underlines); $i++) {
    push(@data, [$underlines[$i]]);
       }
    }
   
       }
       #
       # 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 && !$inner_border) {
    push(@data, ["\\cline{1-$column_count}"]);     
   
       }
       $table->set_data(\@data);
       
       my $coldef = "";
       if ($outer_border || $inner_border) {
    $coldef .= '|';
       }
       for (my $i =0; $i < $column_count; $i++) {
    if ($useP) {
       $coldef .= "p{$colwidth $colunits}";
    } else {
       $coldef .= 'l';
    }
    if ($inner_border || 
       ($outer_border && ($i == $column_count-1))) {
       $coldef .= '|';
    }
       }
       $table->{'coldef'} = $coldef;
   
       # Return the table:
   
       if ($tracing) { &Apache::lonnet::logthis("Leaving generate"); }
   
   
       return $table;
   
   }
   #----------------------------------------------------------------------------
 # The following methods allow for testability.  # The following methods allow for testability.
   
   
 sub get_object_attribute {  sub get_object_attribute {
     my ($self, $attribute) = @_;      my ($self, $attribute) = @_;
       if ($tracing > 1) { &Apache::lonnet::logthis("get_object_attribute: $attribute"); }
     return $self->{$attribute};      return $self->{$attribute};
 }  }
   
 sub get_row {  sub get_row {
     my ($self, $row) = @_;      my ($self, $row) = @_;
       if ($tracing > 1) { &Apache::lonnet::logthis("get_row"); }
   
     my $rows = $self->{'rows'};  # ref to an array....      my $rows = $self->{'rows'};  # ref to an array....
     return $rows->[$row];         # ref to the row hash for the selected row.      return $rows->[$row];         # ref to the row hash for the selected row.
 }  }

Removed from v.1.5  
changed lines
  Added in v.1.12


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>