Annotation of loncom/xml/lontable.pm, revision 1.6
1.1 foxr 1: # The LearningOnline Network with CAPA
2: # Generating TeX tables.
3: #
1.6 ! foxr 4: # $Id: lontable.pm,v 1.5 2008/12/09 11:50:08 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:
1.6 ! foxr 167: =item width
! 168:
! 169: If defined, the width of the table (should be supplied
! 170: in fraction of column width e.g. .75 for 75%.
! 171:
1.1 foxr 172: =item row_open
173:
174: True if a row is open and not yet closed.
175:
176: =item rows
177:
178: Array of row data. This is an array of hashes described below.
179:
180: =back
181:
182: =head3 Row data.
183:
184: Each row of table data is an element of the rows hash array. Hash elements are
185:
186: =over 3
187:
188:
189: =item default_halign
1.3 foxr 190: 0
1.1 foxr 191: Default horizontal alignment for cells in this row.
192:
193: =item default_valign
194:
195: Default vertical alignment for cells in this row (may be ignored).
196:
1.6 ! foxr 197: =item cell_width
! 198:
! 199: The width of the row in cells. This is the sum of the column spans
! 200: of the cells in the row.
! 201:
1.1 foxr 202: =item cells
203:
204: Array of hashes where each element represents the data for a cell.
205: The contents of each element of this hash are described below:
206:
207: =over 3
208:
1.3 foxr 209: =item header
210:
211: If present, the row is a 'header' that is it was made via the
212: <th> tag.
213:
1.1 foxr 214: =item halign
215:
216: If present, overrides the row default horizontal alignment.
217:
218: =item valign
219:
220: if present, override the row default vertical alignment.
221:
222: =item rowspan
223:
224: If present, indicates the number of rows this cell spans.
225:
226: =item colspan
227:
228: If present indicates the number of columns this cell spans.
229: Note that a cell can span both rows and columns.
230:
1.6 ! foxr 231: =item start_col
! 232:
! 233: The starting column of the cell in the table grid.
! 234:
1.1 foxr 235: =item contents
236:
237: The contents of the cell.
238:
239: =back
240:
241: =back
242:
243: =cut
244:
245: sub new {
246: my ($class, $configuration) = @_;
247:
248: # Initialize the object member data with the default values
249: # then override with any stuff in $configuration.
250:
251: my $self = {
252: alignment => "left",
253: outer_border => 0,
1.2 foxr 254: inner_border => 0,
1.1 foxr 255: caption => "",
256: theme => "Zurich",
257: column_count => 0,
258: row_open => 0,
259: rows => [],
260: };
261:
262: foreach my $key (keys %$configuration) {
263: $self->{$key} = $$configuration{$key};
264: }
265:
266: bless($self, $class);
267:
268: return $self;
269: }
270:
1.3 foxr 271:
1.1 foxr 272: #-------------------------------------------------------------------------
273: #
274: # Methods that get/set table global configuration.
1.2 foxr 275: #
276:
277: =pod
278:
279: =head2 Gets/set alignment.
280:
281: If the method is passed a new alignment value, that replaces the current one.
282: Regardless, the current alignment is used:
283:
284: =head3 Examples:
285:
286: my $align = $table->alignment(); # Return current alignment
287: $table->alignment("center"); # Attempt centered alignment.
288:
289: =cut
290:
291: sub alignment {
292: my ($self, $new_value) = @_;
293:
294: if (defined($new_value)) {
1.5 foxr 295: $self->{'alignment'} = $new_value;
1.2 foxr 296: }
1.5 foxr 297: return $self->{'alignment'};
1.2 foxr 298: }
299:
300: =pod
301:
302: =head2 table_border
303:
304: Set or get the presence of an outer border in the table.
305: If passed a parameter, that parameter replaces the current request
306: for or not for an outer border. Regardless, the function returns
307: the final value of the outer_border request.
308:
309: =head3 Examples:
310:
311: $table->table_border(1); # Request an outer border.
312: my $outer_requested = $table->table_border();
313:
314: =cut
315:
316: sub table_border {
317: my ($self, $new_value) = @_;
318:
319: if (defined($new_value)) {
1.5 foxr 320: $self->{'outer_border'} = $new_value;
1.2 foxr 321: }
1.5 foxr 322: return $self->{'outer_border'};
1.2 foxr 323: }
324:
325:
326: =pod
327:
328: =head2 cell_border
329:
330: Set or get the presence of a request for cells to have borders
331: drawn around them. If a paramter is passed, it will be treated as
332: a new value for the cell border configuration. Regardless,the final
333: value of that configuration parameter is returned.
334:
335: =head3 Examples:
336:
1.4 foxr 337: my $cell_border = $table->cell_border(); # ask if cell borders are requested.
1.2 foxr 338: $table->cell_border(1); # Request cell borders.
339:
340: =cut
341:
1.4 foxr 342: sub cell_border {
1.2 foxr 343: my ($self, $new_value) = @_;
344:
345: if (defined($new_value)) {
1.5 foxr 346: $self->{'inner_border'} = $new_value;
1.2 foxr 347: }
1.5 foxr 348: return $self->{'inner_border'};
1.2 foxr 349: }
350:
351: =pod
352:
353: =head2 caption
354:
355: Gets and/or sets the caption string for the table. The caption string appears to label
356: the table. If a parameter is supplied it will become the new caption string.k
357:
358: =head3 Examples:
359:
360:
361: $my caption = $table->caption();
362: $table->caption("This is the new table caption");
363:
364: =cut
365:
366: sub caption {
367: my ($self, $new_value) = @_;
368:
369: if (defined($new_value)) {
1.5 foxr 370: $self->{'caption'} = $new_value;
1.2 foxr 371: }
372:
1.5 foxr 373: return $self->{'caption'};
1.2 foxr 374: }
375:
376: =pod
377:
378: =head2 theme
379:
380: Gets and optionally sets the table theme. The table theme describes how the
381: table will be typset by the table package. If a parameter is supplied it
382: will be the new theme selection.
383:
384: =head3 Examples:
1.1 foxr 385:
1.2 foxr 386: my $theme = $table->theme();
387: $table->theme("Dresden");
388:
389: =cut
390:
391: sub theme {
392: my ($self, $new_value) = @_;
393:
394: if (defined($new_value)) {
1.5 foxr 395: $self->{'theme'} = $new_value;
1.2 foxr 396: }
1.5 foxr 397: return $self->{'theme'};
1.2 foxr 398: }
399:
400: =pod
401:
402: =head2 start_row
403:
404: Begins a new row in the table. If a row is already open, that row is
405: closed off prior to starting the new row. Rows can have the following attributes
406: which are specified by an optional hash passed in to this function.
407:
408: =over 3
409:
410: =item default_halign
411:
412: The default horizontal alignment of the row. This can be "left", "center", or "right"
413:
414: =item default_valign
415:
416: The default vertical alignment of the row. This can be "top", "center", or "bottom"
417:
418: =back
419:
420: =head3 Examples:
421:
422: $table_start_row(); # no attributes.
423: $table_start({default_halign => "center",
424: default_valign => "bottom"}); # Create setting the attrbutes.
425:
426: =cut
427:
428: sub start_row {
1.5 foxr 429: my ($self, $config) = @_;
1.2 foxr 430:
1.5 foxr 431: if ($self->{'row_open'}) {
1.4 foxr 432: $self->end_row();
1.2 foxr 433: }
434: my $row_hash = {
435: default_halign => "left",
436: default_valign => "top",
1.6 ! foxr 437: cell_width => 0,
1.2 foxr 438: cells => []
439: };
440:
441: # Override the defaults if the config hash is present:
442:
1.5 foxr 443: if (defined($config)) {
444: foreach my $key (keys %$config) {
445: $row_hash->{$key} = $config->{$key};
1.2 foxr 446: }
447: }
1.5 foxr 448:
1.2 foxr 449:
1.5 foxr 450: my $rows = $self->{'rows'};
1.2 foxr 451: push(@$rows, $row_hash);
452:
1.5 foxr 453: $self->{"row_open"} = 1; # Row is now open and ready for business.
1.2 foxr 454: }
455:
456: =pod
457:
458: =head2 end_row
459:
460: Closes off a row. Once closed, cells cannot be added to this row again.
461:
462: =head3 Examples:
463:
1.4 foxr 464: $table->end_row();
1.2 foxr 465:
466:
467: =cut
468:
1.4 foxr 469: sub end_row {
1.2 foxr 470: my ($self) = @_;
471:
1.5 foxr 472: if ($self->{'row_open'}) {
1.2 foxr 473:
474: # Mostly we need to determine if this row has the maximum
475: # cell count of any row in existence in the table:
476:
1.6 ! foxr 477: my $row = $self->{'rows'}->[-1];
1.5 foxr 478: my $cells = $row->{'cells'};
1.3 foxr 479:
1.6 ! foxr 480: if ($row->{'cell_width'} > $self->{'column_count'}) {
! 481: $self->{'column_count'} = $row->{'cell_width'};
1.2 foxr 482: }
483:
1.5 foxr 484: $self->{'row_open'} = 0;;
1.2 foxr 485: }
486: }
487:
488: =pod
489:
1.3 foxr 490: =head2 configure_row
491:
492: Modify the configuration of a row. If a row is not open, a new one will be opened.
493:
494: =head3 Parameters:
1.2 foxr 495:
1.3 foxr 496: config_hash - A hash that contains new values for the set of row confiuguration
497: items to be modified. There is currently no check/penalty for items that are not in
498: the set of defined configuration properties which are:
1.2 foxr 499:
1.3 foxr 500: =over 2
501:
502: =item default_halign
503:
504: The default horizontal alignment for text in cells in the row. This can be any of:
505: "left", "right" or "center".
506:
507: =item default_valign
508:
509: The default vertical alignment for text in cells in the row. This can be any of:
510:
511: "top", "bottom" or "center"
512:
513: =back
1.2 foxr 514:
515: =cut
516:
1.3 foxr 517: sub configure_row {
518: my ($self, $config) = @_;
1.2 foxr 519:
1.5 foxr 520: if (!$self->{'row_open'}) {
1.3 foxr 521: $self->start_row();
522: }
523:
1.5 foxr 524: my $row = $self->{'rows'}[-1];
1.3 foxr 525: foreach my $config_item (keys %$config) {
526: $row->{$config_item} = $config->{$config_item};
527: }
1.2 foxr 528: }
1.1 foxr 529:
530:
1.3 foxr 531: =pod
532:
533: =head2 add_cell
534:
535: Add a new cell to a row. If there is a row above us, we need to
536: watch out for row spans that may force additional blank cell entries
537: to fill in the span.
538:
539: =head3 Parameters:
540:
541: =over 2
542:
543: =item text
544:
545: Text to put in the cell.
546:
547: =item cell_config
548:
549: Hash of configuration options that override the defaults. The recognized options,
550: and their defaults are:
551:
552: =over 2
553:
554: =item halign
555:
556: If nonblank overrides the row's default for the cell's horizontal alignment.
557:
558: =item valign
559:
560: If nonblank, overrides the row's default for the cdell's vertical alignment.
561:
562: =item rowspan
563:
564: Number of rows the cell spans.
565:
566: =item colspan
567:
568: Number of columns the cell spans.
569:
570: =back
571:
572: =cut
573:
574: sub add_cell {
575: my ($self, $text, $config) = @_;
576:
577: # If a row is not open, we must open it:
578:
1.5 foxr 579: if (!$self->{'row_open'}) {
1.3 foxr 580: $self->start_row();
581: }
1.6 ! foxr 582: my $rows = $self->{'rows'};
! 583: my $current_row = $rows->[-1];
1.5 foxr 584: my $current_cells = $current_row->{'cells'};
1.6 ! foxr 585: my $last_coord = $current_row->{'cell_width'};
1.3 foxr 586:
1.6 ! foxr 587: # We have to worry about row spans if there is a prior row:
1.3 foxr 588:
1.6 ! foxr 589: if (scalar(@$rows) > 1) {
1.3 foxr 590:
1.6 ! foxr 591: my $last_row = $rows->[-2];
! 592: if ($last_coord < $last_row->{'cell_width'}) {
! 593: my $prior_coord = 0;
! 594: my $prior_cell_index = 0;
! 595: while ($prior_coord <= $last_coord) {
! 596:
! 597: # Pull a cell down if it's coord matches our start coord
! 598: # And there's a row span > 1.
! 599: # Having done so, we adjust our $last_coord to match the
! 600: # end point of the pulled down cell.
! 601:
! 602: my $prior_cell = $last_row->{'cells'}->[$prior_cell_index];
! 603: if (($prior_cell->{'start_col'} == $last_coord) &&
! 604: ($prior_cell->{'rowspan'} > 1)) {
! 605:
! 606: # Need to drop the cell down
! 607:
! 608: my %dropped_down_cell = %$prior_cell;
! 609: $dropped_down_cell{'rowspan'}--;
! 610: $dropped_down_cell{'contents'} = '';
! 611:
! 612: push(@$current_cells, \%dropped_down_cell);
! 613: $last_coord += $dropped_down_cell{'colspan'};
! 614: $current_row->{'cell_width'} = $last_coord;
! 615:
! 616: }
! 617: $prior_coord += $prior_cell->{'colspan'};
! 618: $prior_cell_index++;
! 619: }
1.3 foxr 620: }
1.6 ! foxr 621:
1.3 foxr 622: }
1.6 ! foxr 623:
1.3 foxr 624: #
625: # Now we're ready to build up our cell:
626:
627: my $cell = {
628: rowspan => 1,
629: colspan => 1,
1.6 ! foxr 630: start_col => $last_coord,
1.3 foxr 631: contents => $text
632: };
633:
634: if (defined($config)) {
635: foreach my $key (keys(%$config)) {
636: $cell->{$key} = $config->{$key};
637: }
638: }
1.6 ! foxr 639: $current_row->{'cell_width'} += $cell->{'colspan'};
! 640:
1.3 foxr 641: push(@$current_cells, $cell);
642: }
643:
1.6 ! foxr 644: =pod
! 645:
! 646: =head2 generate
! 647:
! 648: Call this when the structures for the table have been built.
! 649: This will generate and return the table object that can be used
! 650: to generate the table. Returning the table object allows for
! 651: a certain amount of testing to be done on the generated table.
! 652: The caller can then ask the table object to generate LaTeX.
! 653:
! 654: =cut
! 655: sub generate {
! 656: my ($this) = @_;
! 657:
! 658: my $table = LaTeX::Table->new();
! 659:
! 660: # Build up the data:
! 661:
! 662: my @data;
! 663: my $rows = $this->{'rows'};
! 664: my $row_count = scalar(@$rows);
! 665: my $inner_border = $this->{'inner_border'};
! 666: my $outer_border = $this->{'outer_border'};
! 667: my $column_count = $this->{'column_count'};
! 668:
! 669: for (my $row = 0; $row < $row_count; $row++) {
! 670: my @row;
! 671: my $cells = $rows->[$row]->{'cells'};
! 672: my $cell_count = scalar(@$cells);
! 673: my $startcol = 1;
! 674: my @underlines; # Array of \cline cells if cellborder on.
! 675:
! 676: for (my $cell = 0; $cell < $cell_count; $cell++) {
! 677: my $contents = $cells->[$cell]->{'contents'};
! 678: my $cspan = $cells->[$cell]->{'colspan'};
! 679: my $nextcol = $startcol + $cspan;
! 680: if ($cspan > 1) {
! 681: $contents = '\multicolumn{'.$cspan.'}{|l|}{'.$contents.'}';
! 682: }
! 683: if ($inner_border && ($cells->[$cell]->{'rowspan'} == 1)) {
! 684: my $lastcol = $nextcol -1;
! 685: push(@underlines, "\\cline{$startcol-$lastcol}");
! 686: }
! 687: $startcol = $nextcol;
! 688: # Rowspans should take care of themselves.
! 689:
! 690:
! 691: push(@row, $contents);
! 692:
! 693: }
! 694: push(@data, \@row);
! 695: if ($inner_border) {
! 696: for (my $i =0; $i < scalar(@underlines); $i++) {
! 697: push(@data, [$underlines[$i]]);
! 698: }
! 699: }
! 700:
! 701: }
! 702: $table->set_data(\@data);
! 703:
! 704: my $coldef = "";
! 705: if ($outer_border || $inner_border) {
! 706: $coldef .= '|';
! 707: }
! 708: for (my $i =0; $i < $column_count; $i++) {
! 709: $coldef .= 'l';
! 710: if ($inner_border ||
! 711: ($outer_border && ($i == $column_count-1))) {
! 712: $coldef .= '|';
! 713: }
! 714: }
! 715: $table->{'coldef'} = $coldef;
! 716:
! 717: # Return the table:
! 718:
! 719: return $table;
! 720:
! 721: }
! 722: #----------------------------------------------------------------------------
1.5 foxr 723: # The following methods allow for testability.
1.4 foxr 724:
725:
726: sub get_object_attribute {
727: my ($self, $attribute) = @_;
728: return $self->{$attribute};
729: }
730:
1.5 foxr 731: sub get_row {
732: my ($self, $row) = @_;
733: my $rows = $self->{'rows'}; # ref to an array....
734: return $rows->[$row]; # ref to the row hash for the selected row.
735: }
1.1 foxr 736: # Mandatory initialization.
1.4 foxr 737: BEGIN{
738: }
1.1 foxr 739:
740: 1;
741: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>