File:  [LON-CAPA] / loncom / xml / lontable.pm
Revision 1.4: download - view: text, annotated - select for diffs
Tue Dec 2 11:57:25 2008 UTC (15 years, 5 months ago) by foxr
Branches: MAIN
CVS tags: HEAD
- Start writing tests for lontable.
- Fix some errors in lontable that the tests written so far pointed out.

    1: # The LearningOnline Network with CAPA
    2: #  Generating TeX tables.
    3: #
    4: # $Id: lontable.pm,v 1.4 2008/12/02 11:57:25 foxr Exp $
    5: # 
    6: #
    7: # Copyright Michigan State University Board of Trustees
    8: #
    9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   10: #
   11: # LON-CAPA is free software; you can redistribute it and/or modify
   12: # it under the terms of the GNU General Public License as published by
   13: # the Free Software Foundation; either version 2 of the License, or
   14: # (at your option) any later version.
   15: #
   16: # LON-CAPA is distributed in the hope that it will be useful,
   17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19: # GNU General Public License for more details.
   20: #
   21: # You should have received a copy of the GNU General Public License
   22: # along with LON-CAPA; if not, write to the Free Software
   23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   24: #
   25: # /home/httpd/html/adm/gpl.txt
   26: #
   27: # http://www.lon-capa.org/
   28: ## Copyright for TtHfunc and TtMfunc by Ian Hutchinson. 
   29: # TtHfunc and TtMfunc (the "Code") may be compiled and linked into 
   30: # binary executable programs or libraries distributed by the 
   31: # Michigan State University (the "Licensee"), but any binaries so 
   32: # distributed are hereby licensed only for use in the context
   33: # of a program or computational system for which the Licensee is the 
   34: # primary author or distributor, and which performs substantial 
   35: # additional tasks beyond the translation of (La)TeX into HTML.
   36: # The C source of the Code may not be distributed by the Licensee
   37: # to any other parties under any circumstances.
   38: #
   39: 
   40: # This module is a support packkage that helps londefdef generate
   41: # LaTeX tables using the LaTeX::Table package.  A prerequisite is that
   42: # the print generator must have added the following to the LaTeX header:
   43: #
   44: #  \usepackage{xtab}
   45: #  \usepackage{booktabs}
   46: #  \usepackage{array}
   47: #  \usepackage{colortbl}
   48: #  \usepackage{xcolor}
   49: #
   50: #  These packages are installed in the packaged LaTeX distributions we know of as of
   51: #  11/24/2008
   52: #
   53: 
   54: 
   55: 
   56: package Apache::lontable;
   57: use strict;
   58: use LaTeX::Table;
   59: 
   60: 
   61: =pod
   62: 
   63: =head1  lontable Table generation assistant for the LaTeX target
   64: 
   65: This module contains support software for generating tables in LaTeX output mode 
   66: In this implementation, we use the LaTeX::Table package to do the actual final formatting.
   67: Each table creates a new object.  Table objects can have global properties configured.
   68: The main operations on a table object are:
   69: 
   70: =over 3
   71: 
   72: =item start_row  
   73: 
   74: Opens a new table row.
   75: 
   76: =item end_row
   77: 
   78: Closes a table row.
   79: 
   80: =item configure_row
   81: 
   82: Modifies a configuration item in the currently open row.
   83: 
   84: =item generate
   85: 
   86: Returns the generated table string.
   87: 
   88: =item configure
   89: 
   90: Configures a table's global configuration.
   91: 
   92: =item add_cell
   93: 
   94: Add and configure a cell to the current row.6
   95: 
   96: =back
   97: 
   98: =cut
   99: 
  100: =pod
  101: 
  102: =head2 new - create a new object.
  103: 
  104: Create a new table object.  Any of the raw table configuration items can be
  105: modified by this.  These configuration items include:
  106: 
  107:   my $table = lontable::new(\%config_hash)
  108: 
  109: =over3
  110: 
  111: =item alignment
  112: 
  113: Table alignment.  Some table styles support this but not all.
  114: 
  115: =item tableborder
  116: 
  117: If true, a border is drawn around the table.
  118: 
  119: =item cellborder
  120: 
  121: If true, borders are drawn around the cells inside a table.
  122: 
  123: =item caption
  124: 
  125: The table caption text.
  126: 
  127: =item theme
  128: 
  129: The theme of the table to use.  Defaults to Zurich.  Themes we know about are:
  130: NYC, NYC2, Zurich, Berlin, Dresden, Houston, Miami, plain, Paris.  Other themes can be added
  131: to the LaTeX::Table package, and they will become supported automatically, as theme names are
  132: not error checked.  Any use of a non-existent theme is reported by the LaTeX::Table package
  133: when the table text is generated.
  134: 
  135: =back
  136: 
  137: =head3 Member data
  138: 
  139: The object hash has the following members:
  140: 
  141: =over 3
  142: 
  143: =item column_count 
  144: 
  145: Maintained internally, the number of colums in the widest row.
  146: 
  147: =item alignment
  148: 
  149: Table alignment (configurable) "left", "center", or "right".
  150: 
  151: =item outer_border
  152: 
  153: True if a border should be drawn around the entire table (configurable)
  154: 
  155: =item inner_borders
  156: 
  157: True if a border should be drawn around all cells (configurable).
  158: 
  159: =item caption
  160: 
  161: Table caption (configurable).
  162: 
  163: =item theme
  164: 
  165: Theme desired (configurable).
  166: 
  167: =item row_open 
  168: 
  169: True if a row is open and not yet closed.
  170: 
  171: =item rows
  172: 
  173: Array of row data. This is an array of hashes described below.
  174: 
  175: =back
  176: 
  177: =head3 Row data.
  178: 
  179: Each row of table data is an element of the rows hash array.  Hash elements are
  180: 
  181: =over 3
  182: 
  183: 
  184: =item default_halign 
  185: 0
  186: Default horizontal alignment for cells in this row.
  187: 
  188: =item default_valign
  189: 
  190: Default vertical alignment for cells in this row (may be ignored).
  191: 
  192: =item cells
  193: 
  194: Array of hashes where each element represents the data for a cell.
  195: The contents of each element of this hash are described below:
  196: 
  197: =over 3
  198: 
  199: =item header
  200: 
  201: If present, the row is a 'header' that is it was made via the
  202: <th> tag.
  203: 
  204: =item halign
  205: 
  206: If present, overrides the row default horizontal alignment.
  207: 
  208: =item valign
  209: 
  210: if present, override the row default vertical alignment.
  211: 
  212: =item rowspan
  213: 
  214: If present, indicates the number of rows this cell spans.
  215: 
  216: =item colspan
  217: 
  218: If present indicates the number of columns this cell spans.
  219: Note that a cell can span both rows and columns.
  220: 
  221: =item contents
  222: 
  223: The contents of the cell.
  224: 
  225: =back
  226: 
  227: =back
  228: 
  229: =cut
  230: 
  231: sub new {
  232:     my ($class, $configuration) = @_;
  233: 
  234:     #  Initialize the object member data with the default values
  235:     #  then override with any stuff in $configuration.
  236: 
  237:     my $self = {
  238: 	alignment      => "left",
  239: 	outer_border   => 0,
  240: 	inner_border  => 0,
  241: 	caption        => "",
  242: 	theme          => "Zurich",
  243: 	column_count   => 0,
  244: 	row_open       => 0,
  245: 	rows           => [],
  246:     };
  247: 
  248:     foreach my $key (keys %$configuration) {
  249: 	$self->{$key} = $$configuration{$key};
  250:     }
  251: 
  252:     bless($self, $class);
  253: 
  254:     return $self;
  255: }
  256: 
  257: 
  258: #-------------------------------------------------------------------------
  259: #
  260: #  Methods that get/set table global configuration.
  261: #
  262: 
  263: =pod
  264: 
  265: =head2 Gets/set alignment.  
  266: 
  267: If the method is passed a new alignment value, that replaces the current one.
  268: Regardless, the current alignment is used:
  269: 
  270: =head3 Examples:
  271: 
  272:  my $align = $table->alignment(); # Return current alignment
  273:  $table->alignment("center");     # Attempt centered alignment.
  274: 
  275: =cut
  276: 
  277: sub alignment {
  278:     my ($self, $new_value) = @_;
  279: 
  280:     if (defined($new_value)) {
  281: 	$self->{alignment} = $new_value;
  282:     }
  283:     return $self->{alignment};
  284: }
  285: 
  286: =pod
  287: 
  288: =head2 table_border
  289: 
  290: Set or get the presence of an outer border in the table.
  291: If passed a parameter, that parameter replaces the current request
  292: for or not for an outer border. Regardless, the function returns
  293: the final value of the outer_border request.
  294: 
  295: =head3 Examples:
  296: 
  297:   $table->table_border(1);      # Request an outer border.
  298:   my $outer_requested = $table->table_border();
  299: 
  300: =cut
  301: 
  302: sub table_border {
  303:     my ($self, $new_value) = @_;
  304: 
  305:     if (defined($new_value)) {
  306: 	$self->{outer_border} = $new_value;
  307:     }
  308:     return $self->{outer_border};
  309: }
  310: 
  311: 
  312: =pod
  313: 
  314: =head2 cell_border
  315: 
  316: Set or get the presence of a request for cells to have borders
  317: drawn around them.  If a paramter is passed, it will be treated as
  318: a new value for the cell border configuration.  Regardless,the final
  319: value of that configuration parameter is returned.
  320: 
  321: =head3 Examples:
  322: 
  323:  my $cell_border = $table->cell_border(); # ask if cell borders are requested.
  324:  $table->cell_border(1);	# Request cell borders.
  325: 
  326: =cut
  327: 
  328: sub cell_border {
  329:     my ($self, $new_value) = @_;
  330: 
  331:     if (defined($new_value)) {
  332: 	$self->{inner_border} = $new_value;
  333:     }
  334:     return $self->{inner_border};
  335: }
  336: 
  337: =pod
  338: 
  339: =head2 caption
  340: 
  341: Gets and/or sets the caption string for the table.  The caption string appears to label
  342: the table.  If a parameter is supplied it will become the new caption string.k
  343: 
  344: =head3 Examples:
  345: 
  346: 
  347:   $my caption = $table->caption();
  348:   $table->caption("This is the new table caption");
  349: 
  350: =cut
  351: 
  352: sub caption {
  353:     my ($self, $new_value) = @_;
  354: 
  355:     if (defined($new_value)) {
  356: 	$self->{caption} = $new_value;
  357:     }
  358: 
  359:     return $self->{caption};
  360: }
  361: 
  362: =pod
  363: 
  364: =head2 theme
  365: 
  366: Gets and optionally sets the table theme.  The table theme describes how the
  367: table will be typset by the table package.  If a parameter is supplied it
  368: will be the new theme selection.
  369: 
  370: =head3 Examples:
  371: 
  372:   my $theme = $table->theme();
  373:   $table->theme("Dresden");
  374: 
  375: =cut
  376: 
  377: sub theme {
  378:     my ($self, $new_value) = @_;
  379: 
  380:     if (defined($new_value)) {
  381: 	$self->{theme} = $new_value;
  382:     }
  383:     return $self->{theme};
  384: }
  385: 
  386: =pod
  387: 
  388: =head2 start_row
  389: 
  390: Begins a new row in the table.  If a row is already open, that row is
  391: closed off prior to starting the new row.  Rows can have the following attributes
  392: which are specified by an optional hash passed in to this function.
  393: 
  394: =over 3
  395: 
  396: =item default_halign
  397: 
  398: The default horizontal alignment of the row. This can be "left", "center", or "right"
  399: 
  400: =item default_valign
  401: 
  402: The default vertical alignment of the row.  This can be "top", "center", or "bottom"
  403: 
  404: =back
  405: 
  406: =head3 Examples:
  407: 
  408:   $table_start_row();                  # no attributes.
  409:   $table_start({default_halign => "center",
  410:                 default_valign => "bottom"}); # Create setting the attrbutes.
  411: 
  412: =cut
  413: 
  414: sub start_row {
  415:     my ($self, %config) = @_;
  416: 
  417:     if ($self->{row_open}) { 
  418: 	$self->end_row();
  419:     }
  420:     my $row_hash = {
  421: 	default_halign => "left",
  422: 	default_valign => "top",
  423: 	cells          => []
  424:     };
  425: 
  426:     # Override the defaults if the config hash is present:
  427: 
  428:     if (defined(%config)) {
  429: 	foreach my $key  (keys %config) {
  430: 	    $row_hash->{$key} = $config{$key};
  431: 	}
  432:     }
  433:     
  434:     my $rows = $self->{rows};
  435:     push(@$rows, $row_hash);
  436: 
  437:     $self->{row_open} = 1;	# Row is now open and ready for business.
  438: }
  439: 
  440: =pod
  441: 
  442: =head2  end_row 
  443: 
  444: Closes off a row.  Once closed, cells cannot be added to this row again.
  445: 
  446: =head3 Examples:
  447: 
  448:    $table->end_row();
  449: 
  450: 
  451: =cut
  452: 
  453: sub end_row {
  454:     my ($self) = @_;
  455: 
  456:     if ($self->{row_open}) {
  457: 	
  458: 	# Mostly we need to determine if this row has the maximum
  459: 	# cell count of any row in existence in the table:
  460: 
  461: 	my $row        = $self->{rows}[-1];
  462: 	my $cells      = $row->{cells};
  463: 	my $raw_cell_count = scalar(@$cells);
  464: 
  465: 	# Need to iterate through the columns as 
  466: 	# colspans affect the count:
  467: 	#
  468: 	my $cell_count = 0;
  469: 	for (my $i =0; $i < $raw_cell_count; $i++) {
  470: 	    $cell_count = $cell_count + $cells->[$i]->{colspan};
  471: 	}
  472: 	if ($cell_count > $self->{column_count}) {
  473: 	    $self->{column_count} = $cell_count;
  474: 	}
  475: 
  476: 	$self->{row_open} = 0;;
  477:     }
  478: }
  479: 
  480: =pod
  481: 
  482: =head2 configure_row
  483: 
  484: Modify the configuration of a row.   If a row is not open, a new one will be opened.
  485: 
  486: =head3 Parameters:
  487: 
  488: config_hash - A hash that contains new values for the set of row confiuguration 
  489: items to be modified.  There is currently no check/penalty for items that are not in
  490: the set of defined configuration properties which are:
  491: 
  492: =over 2
  493: 
  494: =item default_halign
  495: 
  496: The default horizontal alignment for text in  cells in the row.  This can be any of:
  497: "left", "right" or "center".
  498: 
  499: =item default_valign
  500: 
  501: The default vertical alignment for text in cells in the row.  This can be any of:
  502: 
  503: "top", "bottom" or "center"
  504: 
  505: =back 
  506: 
  507: =cut
  508: 
  509: sub configure_row {
  510:     my ($self, $config) = @_;
  511: 
  512:     if (!$self->{row_open}) {
  513: 	$self->start_row();
  514:     }
  515:     
  516:     my $row = $self->{rows}[-1];
  517:     foreach my $config_item (keys %$config) {
  518: 	$row->{$config_item} = $config->{$config_item};
  519:     }
  520: }
  521: 
  522: 
  523: =pod
  524: 
  525: =head2 add_cell
  526: 
  527: Add a new cell to a row.  If there is a row above us, we need to 
  528: watch out for row spans that may force additional blank cell entries
  529: to fill in the span. 
  530: 
  531: =head3 Parameters:
  532: 
  533: =over 2
  534: 
  535: =item text
  536: 
  537: Text to put in the cell.
  538: 
  539: =item cell_config
  540: 
  541: Hash of configuration options that override the defaults.   The recognized options,
  542: and their defaults are:
  543: 
  544: =over 2
  545: 
  546: =item halign 
  547: 
  548: If nonblank overrides the row's default for the cell's horizontal alignment.
  549: 
  550: =item valign
  551: 
  552: If nonblank, overrides the row's default for the cdell's vertical alignment.
  553: 
  554: =item rowspan
  555: 
  556: Number of rows the cell spans.
  557: 
  558: =item colspan
  559: 
  560: Number of columns the cell spans.
  561: 
  562: =back
  563: 
  564: =cut
  565: 
  566: sub add_cell {
  567:     my ($self, $text, $config) = @_;
  568: 
  569:     # If a row is not open, we must open it:
  570: 
  571:     if (!$self->{row_open}) {
  572: 	$self->start_row();
  573:     }
  574: 
  575:     my $current_row   = $self->{rows}->[-1];
  576:     my $current_cells = $current_row->{cells}; 
  577: 
  578:     # The way we handle row spans is to insert additional
  579:     # blank cells as needed to reach this column.  Each
  580:     # cell that is inserted is empty, but has a row span decreased by one
  581:     # from the row above.  Column spans are propagated down from the row above
  582:     # and handled when the table's LaTeX is generated.
  583:     # There must be at least two rows in the row table to need to do this:
  584: 
  585:     my $row_count = scalar(@$self->{rows});
  586:     if ($row_count > 1) {
  587: 	my $prior_row      = $self->{rows}->[-2];
  588: 	my $curr_colcount  = scaler(@$current_row->{cells});
  589: 	my $prior_colcount = scaler(@$prior_row->{cells});
  590: 
  591: 	while (($curr_colcount < $prior_colcount) &&
  592: 	       $prior_row->{cells}->[$curr_colcount]->{rowspan} > 1) {
  593: 	    my %cell = $prior_row->{cells}->[$curr_colcount];
  594: 	    %cell->{rowspan}--;
  595: 	    %cell->{contents} = "";
  596: 	    push(@$current_cells, \%cell);
  597: 	}
  598:     }
  599:     #
  600:     # Now we're ready to build up our cell:
  601: 
  602:     my $cell = {
  603: 	rowspan    => 1,
  604: 	colspan    => 1,
  605: 	contents   => $text
  606:     };
  607:     
  608:     if (defined($config)) {
  609: 	foreach my $key (keys(%$config)) {
  610: 	    $cell->{$key} = $config->{$key};
  611: 	}
  612:     }
  613:     push(@$current_cells, $cell);
  614: }
  615: 
  616: # The following method allows for testability.
  617: 
  618: 
  619: sub get_object_attribute {
  620:     my ($self, $attribute) = @_;
  621:     return $self->{$attribute};
  622: }
  623: 
  624: 
  625: #   Mandatory initialization.
  626: BEGIN{
  627: }
  628: 
  629: 1;
  630: __END__

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