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