Annotation of loncom/xml/lontable.pm, revision 1.5

1.1       foxr        1: # The LearningOnline Network with CAPA
                      2: #  Generating TeX tables.
                      3: #
1.5     ! foxr        4: # $Id: lontable.pm,v 1.4 2008/12/02 11:57:25 foxr Exp $
1.1       foxr        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
1.5     ! foxr       42: # the print generator must have added the following to the LaTeX 
1.1       foxr       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: 
1.3       foxr       92: =item add_cell
                     93: 
                     94: Add and configure a cell to the current row.6
                     95: 
1.1       foxr       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 
1.3       foxr      185: 0
1.1       foxr      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: 
1.3       foxr      199: =item header
                    200: 
                    201: If present, the row is a 'header' that is it was made via the
                    202: <th> tag.
                    203: 
1.1       foxr      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,
1.2       foxr      240: 	inner_border  => 0,
1.1       foxr      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: 
1.3       foxr      257: 
1.1       foxr      258: #-------------------------------------------------------------------------
                    259: #
                    260: #  Methods that get/set table global configuration.
1.2       foxr      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)) {
1.5     ! foxr      281: 	$self->{'alignment'} = $new_value;
1.2       foxr      282:     }
1.5     ! foxr      283:     return $self->{'alignment'};
1.2       foxr      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)) {
1.5     ! foxr      306: 	$self->{'outer_border'} = $new_value;
1.2       foxr      307:     }
1.5     ! foxr      308:     return $self->{'outer_border'};
1.2       foxr      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: 
1.4       foxr      323:  my $cell_border = $table->cell_border(); # ask if cell borders are requested.
1.2       foxr      324:  $table->cell_border(1);	# Request cell borders.
                    325: 
                    326: =cut
                    327: 
1.4       foxr      328: sub cell_border {
1.2       foxr      329:     my ($self, $new_value) = @_;
                    330: 
                    331:     if (defined($new_value)) {
1.5     ! foxr      332: 	$self->{'inner_border'} = $new_value;
1.2       foxr      333:     }
1.5     ! foxr      334:     return $self->{'inner_border'};
1.2       foxr      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)) {
1.5     ! foxr      356: 	$self->{'caption'} = $new_value;
1.2       foxr      357:     }
                    358: 
1.5     ! foxr      359:     return $self->{'caption'};
1.2       foxr      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:
1.1       foxr      371: 
1.2       foxr      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)) {
1.5     ! foxr      381: 	$self->{'theme'} = $new_value;
1.2       foxr      382:     }
1.5     ! foxr      383:     return $self->{'theme'};
1.2       foxr      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 {
1.5     ! foxr      415:     my ($self, $config) = @_;
1.2       foxr      416: 
1.5     ! foxr      417:     if ($self->{'row_open'}) { 
1.4       foxr      418: 	$self->end_row();
1.2       foxr      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: 
1.5     ! foxr      428:     if (defined($config)) {
        !           429: 	foreach my $key  (keys %$config) {
        !           430: 	    $row_hash->{$key} = $config->{$key};
1.2       foxr      431: 	}
                    432:     }
1.5     ! foxr      433: 
1.2       foxr      434:     
1.5     ! foxr      435:     my $rows = $self->{'rows'};
1.2       foxr      436:     push(@$rows, $row_hash);
                    437: 
1.5     ! foxr      438:     $self->{"row_open"} = 1;	# Row is now open and ready for business.
1.2       foxr      439: }
                    440: 
                    441: =pod
                    442: 
                    443: =head2  end_row 
                    444: 
                    445: Closes off a row.  Once closed, cells cannot be added to this row again.
                    446: 
                    447: =head3 Examples:
                    448: 
1.4       foxr      449:    $table->end_row();
1.2       foxr      450: 
                    451: 
                    452: =cut
                    453: 
1.4       foxr      454: sub end_row {
1.2       foxr      455:     my ($self) = @_;
                    456: 
1.5     ! foxr      457:     if ($self->{'row_open'}) {
1.2       foxr      458: 	
                    459: 	# Mostly we need to determine if this row has the maximum
                    460: 	# cell count of any row in existence in the table:
                    461: 
1.5     ! foxr      462: 	my $row        = $self->{'rows'}[-1];
        !           463: 	my $cells      = $row->{'cells'};
1.3       foxr      464: 	my $raw_cell_count = scalar(@$cells);
                    465: 
                    466: 	# Need to iterate through the columns as 
                    467: 	# colspans affect the count:
                    468: 	#
                    469: 	my $cell_count = 0;
                    470: 	for (my $i =0; $i < $raw_cell_count; $i++) {
1.5     ! foxr      471: 	    $cell_count = $cell_count + $cells->[$i]->{'colspan'};
1.3       foxr      472: 	}
1.5     ! foxr      473: 	if ($cell_count > $self->{'column_count'}) {
        !           474: 	    $self->{'column_count'} = $cell_count;
1.2       foxr      475: 	}
                    476: 
1.5     ! foxr      477: 	$self->{'row_open'} = 0;;
1.2       foxr      478:     }
                    479: }
                    480: 
                    481: =pod
                    482: 
1.3       foxr      483: =head2 configure_row
                    484: 
                    485: Modify the configuration of a row.   If a row is not open, a new one will be opened.
                    486: 
                    487: =head3 Parameters:
1.2       foxr      488: 
1.3       foxr      489: config_hash - A hash that contains new values for the set of row confiuguration 
                    490: items to be modified.  There is currently no check/penalty for items that are not in
                    491: the set of defined configuration properties which are:
1.2       foxr      492: 
1.3       foxr      493: =over 2
                    494: 
                    495: =item default_halign
                    496: 
                    497: The default horizontal alignment for text in  cells in the row.  This can be any of:
                    498: "left", "right" or "center".
                    499: 
                    500: =item default_valign
                    501: 
                    502: The default vertical alignment for text in cells in the row.  This can be any of:
                    503: 
                    504: "top", "bottom" or "center"
                    505: 
                    506: =back 
1.2       foxr      507: 
                    508: =cut
                    509: 
1.3       foxr      510: sub configure_row {
                    511:     my ($self, $config) = @_;
1.2       foxr      512: 
1.5     ! foxr      513:     if (!$self->{'row_open'}) {
1.3       foxr      514: 	$self->start_row();
                    515:     }
                    516:     
1.5     ! foxr      517:     my $row = $self->{'rows'}[-1];
1.3       foxr      518:     foreach my $config_item (keys %$config) {
                    519: 	$row->{$config_item} = $config->{$config_item};
                    520:     }
1.2       foxr      521: }
1.1       foxr      522: 
                    523: 
1.3       foxr      524: =pod
                    525: 
                    526: =head2 add_cell
                    527: 
                    528: Add a new cell to a row.  If there is a row above us, we need to 
                    529: watch out for row spans that may force additional blank cell entries
                    530: to fill in the span. 
                    531: 
                    532: =head3 Parameters:
                    533: 
                    534: =over 2
                    535: 
                    536: =item text
                    537: 
                    538: Text to put in the cell.
                    539: 
                    540: =item cell_config
                    541: 
                    542: Hash of configuration options that override the defaults.   The recognized options,
                    543: and their defaults are:
                    544: 
                    545: =over 2
                    546: 
                    547: =item halign 
                    548: 
                    549: If nonblank overrides the row's default for the cell's horizontal alignment.
                    550: 
                    551: =item valign
                    552: 
                    553: If nonblank, overrides the row's default for the cdell's vertical alignment.
                    554: 
                    555: =item rowspan
                    556: 
                    557: Number of rows the cell spans.
                    558: 
                    559: =item colspan
                    560: 
                    561: Number of columns the cell spans.
                    562: 
                    563: =back
                    564: 
                    565: =cut
                    566: 
                    567: sub add_cell {
                    568:     my ($self, $text, $config) = @_;
                    569: 
                    570:     # If a row is not open, we must open it:
                    571: 
1.5     ! foxr      572:     if (!$self->{'row_open'}) {
1.3       foxr      573: 	$self->start_row();
                    574:     }
                    575: 
1.5     ! foxr      576:     my $current_row   = $self->{'rows'}->[-1];
        !           577:     my $current_cells = $current_row->{'cells'}; 
1.3       foxr      578: 
                    579:     # The way we handle row spans is to insert additional
                    580:     # blank cells as needed to reach this column.  Each
                    581:     # cell that is inserted is empty, but has a row span decreased by one
                    582:     # from the row above.  Column spans are propagated down from the row above
                    583:     # and handled when the table's LaTeX is generated.
                    584:     # There must be at least two rows in the row table to need to do this:
                    585: 
1.5     ! foxr      586:     my $rows = $self->{'rows'};
        !           587:     my $row_count = scalar(@$rows);
1.3       foxr      588:     if ($row_count > 1) {
1.5     ! foxr      589: 	my $prior_row      = $rows->[-2];
        !           590: 	my $cells          = $current_row->{'cells'};
        !           591: 	my $prior_cells    = $prior_row->{'cells'};
        !           592: 	my $curr_colcount  = scalar(@$cells);
        !           593: 	
        !           594: 	my $prior_colcount = scalar(@$prior_cells);
1.3       foxr      595: 
                    596: 	while (($curr_colcount < $prior_colcount) &&
1.5     ! foxr      597: 	       $prior_cells->[$curr_colcount]->{'rowspan'} > 1) {
        !           598: 	    my %cell;
        !           599: 	    my $prior_cell = $prior_cells->[$curr_colcount];
        !           600: 	    %cell = %$prior_cell;
        !           601: 	    $cell{'rowspan'}--;
        !           602: 	    $cell{'contents'} = "";
1.3       foxr      603: 	    push(@$current_cells, \%cell);
1.5     ! foxr      604: 	    $curr_colcount += $prior_cells->[$curr_colcount]->{'colspan'}; # could be a colspan too.
1.3       foxr      605: 	}
                    606:     }
                    607:     #
                    608:     # Now we're ready to build up our cell:
                    609: 
                    610:     my $cell = {
                    611: 	rowspan    => 1,
                    612: 	colspan    => 1,
                    613: 	contents   => $text
                    614:     };
                    615:     
                    616:     if (defined($config)) {
                    617: 	foreach my $key (keys(%$config)) {
                    618: 	    $cell->{$key} = $config->{$key};
                    619: 	}
                    620:     }
                    621:     push(@$current_cells, $cell);
                    622: }
                    623: 
1.5     ! foxr      624: # The following methods allow for testability.
1.4       foxr      625: 
                    626: 
                    627: sub get_object_attribute {
                    628:     my ($self, $attribute) = @_;
                    629:     return $self->{$attribute};
                    630: }
                    631: 
1.5     ! foxr      632: sub get_row {
        !           633:     my ($self, $row) = @_;
        !           634:     my $rows = $self->{'rows'};	  # ref to an array....
        !           635:     return $rows->[$row];         # ref to the row hash for the selected row.
        !           636: }
1.1       foxr      637: #   Mandatory initialization.
1.4       foxr      638: BEGIN{
                    639: }
1.1       foxr      640: 
                    641: 1;
                    642: __END__

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