Annotation of loncom/xml/lontable.pm, revision 1.18
1.1 foxr 1: # The LearningOnline Network with CAPA
2: # Generating TeX tables.
3: #
1.18 ! foxr 4: # $Id: lontable.pm,v 1.17 2011/04/13 10:08:06 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
1.12 foxr 41: # LaTeX tables using the Apache::lonlatextable 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;
1.12 foxr 58: use Apache::lonlatextable;
1.8 foxr 59: use Apache::lonnet; # for trace logging.
1.1 foxr 60:
1.18 ! foxr 61:
! 62: my $tracing = 0; # Set to 1 to enable log tracing. 2 for local sub tracing.
1.1 foxr 63:
64: =pod
65:
66: =head1 lontable Table generation assistant for the LaTeX target
67:
68: This module contains support software for generating tables in LaTeX output mode
1.12 foxr 69: In this implementation, we use the Apache::lonlatextable package to do the actual final formatting.
1.1 foxr 70: Each table creates a new object. Table objects can have global properties configured.
71: The main operations on a table object are:
72:
73: =over 3
74:
75: =item start_row
76:
77: Opens a new table row.
78:
79: =item end_row
80:
81: Closes a table row.
82:
83: =item configure_row
84:
85: Modifies a configuration item in the currently open row.
86:
87: =item generate
88:
89: Returns the generated table string.
90:
91: =item configure
92:
93: Configures a table's global configuration.
94:
1.3 foxr 95: =item add_cell
96:
97: Add and configure a cell to the current row.6
98:
1.1 foxr 99: =back
100:
101: =cut
102:
103: =pod
104:
105: =head2 new - create a new object.
106:
107: Create a new table object. Any of the raw table configuration items can be
108: modified by this. These configuration items include:
109:
110: my $table = lontable::new(\%config_hash)
111:
1.17 foxr 112: =over 3
1.1 foxr 113:
1.7 foxr 114:
1.1 foxr 115: =item alignment
116:
117: Table alignment. Some table styles support this but not all.
118:
119: =item tableborder
120:
121: If true, a border is drawn around the table.
122:
123: =item cellborder
124:
125: If true, borders are drawn around the cells inside a table.
126:
127: =item caption
128:
129: The table caption text.
130:
131: =item theme
132:
133: The theme of the table to use. Defaults to Zurich. Themes we know about are:
134: NYC, NYC2, Zurich, Berlin, Dresden, Houston, Miami, plain, Paris. Other themes can be added
1.12 foxr 135: to the Apache::lonlatextable package, and they will become supported automatically, as theme names are
136: not error checked. Any use of a non-existent theme is reported by the Apache::lonlatextable package
1.1 foxr 137: when the table text is generated.
138:
1.7 foxr 139: =item width
140:
1.8 foxr 141: The width of the table. in any
142: TeX unit measure e.g. 10.8cm This forces the table to the
143: tabularx environment. It also forces the declarations for
144: cells to be paragraph mode which supports more internal formatting.
1.7 foxr 145:
1.1 foxr 146: =back
147:
148: =head3 Member data
149:
150: The object hash has the following members:
151:
152: =over 3
153:
154: =item column_count
155:
156: Maintained internally, the number of colums in the widest row.
157:
158: =item alignment
159:
160: Table alignment (configurable) "left", "center", or "right".
161:
162: =item outer_border
163:
164: True if a border should be drawn around the entire table (configurable)
165:
166: =item inner_borders
167:
168: True if a border should be drawn around all cells (configurable).
169:
170: =item caption
171:
172: Table caption (configurable).
173:
174: =item theme
175:
176: Theme desired (configurable).
177:
1.6 foxr 178: =item width
179:
180: If defined, the width of the table (should be supplied
181: in fraction of column width e.g. .75 for 75%.
182:
1.1 foxr 183: =item row_open
184:
185: True if a row is open and not yet closed.
186:
187: =item rows
188:
189: Array of row data. This is an array of hashes described below.
190:
191: =back
192:
193: =head3 Row data.
194:
195: Each row of table data is an element of the rows hash array. Hash elements are
196:
197: =over 3
198:
199:
200: =item default_halign
1.3 foxr 201: 0
1.1 foxr 202: Default horizontal alignment for cells in this row.
203:
204: =item default_valign
205:
206: Default vertical alignment for cells in this row (may be ignored).
207:
1.6 foxr 208: =item cell_width
209:
210: The width of the row in cells. This is the sum of the column spans
211: of the cells in the row.
212:
1.1 foxr 213: =item cells
214:
1.17 foxr 215:
1.1 foxr 216: Array of hashes where each element represents the data for a cell.
217: The contents of each element of this hash are described below:
218:
1.3 foxr 219: =item header
220:
221: If present, the row is a 'header' that is it was made via the
222: <th> tag.
223:
1.1 foxr 224: =item halign
225:
226: If present, overrides the row default horizontal alignment.
227:
228: =item valign
229:
230: if present, override the row default vertical alignment.
231:
232: =item rowspan
233:
234: If present, indicates the number of rows this cell spans.
235:
236: =item colspan
237:
238: If present indicates the number of columns this cell spans.
239: Note that a cell can span both rows and columns.
240:
1.6 foxr 241: =item start_col
242:
243: The starting column of the cell in the table grid.
244:
1.1 foxr 245: =item contents
246:
247: The contents of the cell.
248:
249: =back
250:
251:
252: =cut
253:
254: sub new {
255: my ($class, $configuration) = @_;
256:
1.17 foxr 257: if ($tracing) {&Apache::lonnet::logthis("new table"); }
1.1 foxr 258: # Initialize the object member data with the default values
259: # then override with any stuff in $configuration.
260:
261: my $self = {
262: alignment => "left",
263: outer_border => 0,
1.2 foxr 264: inner_border => 0,
1.1 foxr 265: caption => "",
1.11 foxr 266: theme => "plain",
1.1 foxr 267: column_count => 0,
268: row_open => 0,
1.17 foxr 269: rows => {
270: 'body' => [],
271: 'head' => [],
272: 'foot' => []
273: },
274: col_widths => {},
275: part => 'body' # one of 'body', 'head', 'foot'.
276:
1.1 foxr 277: };
278:
279: foreach my $key (keys %$configuration) {
280: $self->{$key} = $$configuration{$key};
281: }
282:
283: bless($self, $class);
284:
285: return $self;
286: }
287:
1.3 foxr 288:
1.1 foxr 289: #-------------------------------------------------------------------------
290: #
291: # Methods that get/set table global configuration.
1.2 foxr 292: #
293:
294: =pod
295:
296: =head2 Gets/set alignment.
297:
298: If the method is passed a new alignment value, that replaces the current one.
299: Regardless, the current alignment is used:
300:
301: =head3 Examples:
302:
303: my $align = $table->alignment(); # Return current alignment
304: $table->alignment("center"); # Attempt centered alignment.
305:
306: =cut
307:
308: sub alignment {
309: my ($self, $new_value) = @_;
310:
1.8 foxr 311: if ($tracing) {&Apache::lonnet::logthis("alignment = $new_value");}
312:
1.2 foxr 313: if (defined($new_value)) {
1.5 foxr 314: $self->{'alignment'} = $new_value;
1.2 foxr 315: }
1.5 foxr 316: return $self->{'alignment'};
1.2 foxr 317: }
318:
319: =pod
320:
321: =head2 table_border
322:
323: Set or get the presence of an outer border in the table.
324: If passed a parameter, that parameter replaces the current request
325: for or not for an outer border. Regardless, the function returns
326: the final value of the outer_border request.
327:
328: =head3 Examples:
329:
330: $table->table_border(1); # Request an outer border.
331: my $outer_requested = $table->table_border();
332:
333: =cut
334:
335: sub table_border {
336: my ($self, $new_value) = @_;
337:
1.8 foxr 338: if ($tracing) {&Apache::lonnet::logthis("table_border $new_value");}
339:
1.2 foxr 340: if (defined($new_value)) {
1.5 foxr 341: $self->{'outer_border'} = $new_value;
1.2 foxr 342: }
1.5 foxr 343: return $self->{'outer_border'};
1.2 foxr 344: }
345:
346:
347: =pod
348:
349: =head2 cell_border
350:
351: Set or get the presence of a request for cells to have borders
352: drawn around them. If a paramter is passed, it will be treated as
353: a new value for the cell border configuration. Regardless,the final
354: value of that configuration parameter is returned.
1.16 foxr 355: Valid values for the parameter are:
356:
357: =over 2
358:
359: =item 0 - no borders present.
360:
361: =item 1 - All borders (borders around all four sides of the cell.
362:
363: =item 2 - Border at top and bottom of the cell.
364:
365: =item 3 - Border at the left and right sides of the cell.
366:
1.17 foxr 367: =item 4 - Border around groups (colgroups as well as thead/tfoot/tbody).
1.16 foxr 368:
1.17 foxr 369:
370: =back
1.2 foxr 371:
372: =head3 Examples:
373:
1.4 foxr 374: my $cell_border = $table->cell_border(); # ask if cell borders are requested.
1.2 foxr 375: $table->cell_border(1); # Request cell borders.
376:
377: =cut
378:
1.4 foxr 379: sub cell_border {
1.2 foxr 380: my ($self, $new_value) = @_;
1.8 foxr 381: if($tracing) {&Apache::lonnet::logthis("cell_border: $new_value"); }
1.2 foxr 382: if (defined($new_value)) {
1.5 foxr 383: $self->{'inner_border'} = $new_value;
1.2 foxr 384: }
1.5 foxr 385: return $self->{'inner_border'};
1.2 foxr 386: }
387:
388: =pod
389:
390: =head2 caption
391:
392: Gets and/or sets the caption string for the table. The caption string appears to label
393: the table. If a parameter is supplied it will become the new caption string.k
394:
395: =head3 Examples:
396:
397:
398: $my caption = $table->caption();
399: $table->caption("This is the new table caption");
400:
401: =cut
402:
403: sub caption {
404: my ($self, $new_value) = @_;
405:
1.8 foxr 406: if($tracing) {&Apache::lonnet::logthis("caption: $new_value"); }
1.2 foxr 407: if (defined($new_value)) {
1.5 foxr 408: $self->{'caption'} = $new_value;
1.2 foxr 409: }
410:
1.5 foxr 411: return $self->{'caption'};
1.2 foxr 412: }
413:
414: =pod
415:
416: =head2 theme
417:
418: Gets and optionally sets the table theme. The table theme describes how the
419: table will be typset by the table package. If a parameter is supplied it
420: will be the new theme selection.
421:
422: =head3 Examples:
1.1 foxr 423:
1.2 foxr 424: my $theme = $table->theme();
425: $table->theme("Dresden");
426:
427: =cut
428:
429: sub theme {
430: my ($self, $new_value) = @_;
1.8 foxr 431: if($tracing) {&Apache::lonnet::logthis("theme $new_value"); }
1.2 foxr 432: if (defined($new_value)) {
1.5 foxr 433: $self->{'theme'} = $new_value;
1.2 foxr 434: }
1.5 foxr 435: return $self->{'theme'};
1.2 foxr 436: }
437:
438: =pod
439:
1.17 foxr 440: =head2 width
1.7 foxr 441:
442: Gets and optionally sets the width of the table.
443:
1.17 foxr 444: =head3 Examples:
1.7 foxr 445:
446: my $newwidth = $table->width("10cm"); # 10cm width returns "10cm".
447:
448: =cut
449: sub width {
450: my ($self, $new_value) = @_;
1.8 foxr 451: if($tracing) {&Apache::lonnet::logthis("width = $new_value"); }
452:
1.7 foxr 453: if (defined($new_value)) {
454: $self->{'width'} = $new_value;
455: }
456: return $self->{'width'}; # Could be undef.
457: }
458:
459: =pod
460:
1.2 foxr 461: =head2 start_row
462:
463: Begins a new row in the table. If a row is already open, that row is
464: closed off prior to starting the new row. Rows can have the following attributes
465: which are specified by an optional hash passed in to this function.
466:
467: =over 3
468:
469: =item default_halign
470:
471: The default horizontal alignment of the row. This can be "left", "center", or "right"
472:
473: =item default_valign
474:
475: The default vertical alignment of the row. This can be "top", "center", or "bottom"
476:
477: =back
478:
479: =head3 Examples:
480:
481: $table_start_row(); # no attributes.
482: $table_start({default_halign => "center",
483: default_valign => "bottom"}); # Create setting the attrbutes.
484:
485: =cut
486:
487: sub start_row {
1.5 foxr 488: my ($self, $config) = @_;
1.8 foxr 489: if($tracing) {&Apache::lonnet::logthis("start_row"); }
1.5 foxr 490: if ($self->{'row_open'}) {
1.4 foxr 491: $self->end_row();
1.2 foxr 492: }
493: my $row_hash = {
494: default_halign => "left",
495: default_valign => "top",
1.6 foxr 496: cell_width => 0,
1.2 foxr 497: cells => []
498: };
499:
500: # Override the defaults if the config hash is present:
501:
1.5 foxr 502: if (defined($config)) {
503: foreach my $key (keys %$config) {
504: $row_hash->{$key} = $config->{$key};
1.2 foxr 505: }
506: }
1.5 foxr 507:
1.2 foxr 508:
1.17 foxr 509: my $rows = $self->{'rows'}->{$self->{'part'}};
1.2 foxr 510: push(@$rows, $row_hash);
511:
1.5 foxr 512: $self->{"row_open"} = 1; # Row is now open and ready for business.
1.2 foxr 513: }
514:
515: =pod
516:
517: =head2 end_row
518:
519: Closes off a row. Once closed, cells cannot be added to this row again.
520:
521: =head3 Examples:
522:
1.4 foxr 523: $table->end_row();
1.2 foxr 524:
525:
526: =cut
527:
1.4 foxr 528: sub end_row {
1.2 foxr 529: my ($self) = @_;
1.8 foxr 530: if($tracing) {&Apache::lonnet::logthis("end_row"); }
1.5 foxr 531: if ($self->{'row_open'}) {
1.2 foxr 532:
533: # Mostly we need to determine if this row has the maximum
534: # cell count of any row in existence in the table:
1.17 foxr 535:
1.18 ! foxr 536:
1.17 foxr 537: my $row = $self->{'rows'}->{$self->{'part'}}->[-1];
1.5 foxr 538: my $cells = $row->{'cells'};
1.3 foxr 539:
1.6 foxr 540: if ($row->{'cell_width'} > $self->{'column_count'}) {
541: $self->{'column_count'} = $row->{'cell_width'};
1.2 foxr 542: }
543:
1.5 foxr 544: $self->{'row_open'} = 0;;
1.2 foxr 545: }
546: }
547:
548: =pod
549:
1.3 foxr 550: =head2 configure_row
551:
552: Modify the configuration of a row. If a row is not open, a new one will be opened.
553:
554: =head3 Parameters:
1.2 foxr 555:
1.3 foxr 556: config_hash - A hash that contains new values for the set of row confiuguration
557: items to be modified. There is currently no check/penalty for items that are not in
558: the set of defined configuration properties which are:
1.2 foxr 559:
1.3 foxr 560: =over 2
561:
562: =item default_halign
563:
564: The default horizontal alignment for text in cells in the row. This can be any of:
565: "left", "right" or "center".
566:
567: =item default_valign
568:
569: The default vertical alignment for text in cells in the row. This can be any of:
570:
571: "top", "bottom" or "center"
572:
1.14 foxr 573:
1.3 foxr 574: =back
1.2 foxr 575:
576: =cut
577:
1.3 foxr 578: sub configure_row {
579: my ($self, $config) = @_;
1.8 foxr 580: if($tracing) {&Apache::lonnet::logthis("configure_row");}
1.5 foxr 581: if (!$self->{'row_open'}) {
1.3 foxr 582: $self->start_row();
583: }
584:
1.17 foxr 585: my $row = $self->{'rows'}->{$self->{'part'}}->[-1];
1.3 foxr 586: foreach my $config_item (keys %$config) {
587: $row->{$config_item} = $config->{$config_item};
588: }
1.2 foxr 589: }
1.1 foxr 590:
591:
1.3 foxr 592: =pod
593:
594: =head2 add_cell
595:
596: Add a new cell to a row. If there is a row above us, we need to
597: watch out for row spans that may force additional blank cell entries
598: to fill in the span.
599:
600: =head3 Parameters:
601:
602: =over 2
603:
604: =item text
605:
606: Text to put in the cell.
607:
608: =item cell_config
609:
610: Hash of configuration options that override the defaults. The recognized options,
611: and their defaults are:
612:
613: =over 2
614:
615: =item halign
616:
617: If nonblank overrides the row's default for the cell's horizontal alignment.
618:
619: =item valign
620:
621: If nonblank, overrides the row's default for the cdell's vertical alignment.
622:
623: =item rowspan
624:
625: Number of rows the cell spans.
626:
627: =item colspan
628:
629: Number of columns the cell spans.
630:
1.14 foxr 631: =item width
632:
633: LaTeX specification of the width of the cell.
634: Note that if there is a colspan this width is going to be equally divided
635: over the widths of the columnsn in the span.
636: Note as well that if width specification conflict, the last one specified wins...silently.
637:
1.3 foxr 638: =back
639:
1.17 foxr 640: =back
641:
1.3 foxr 642: =cut
643:
644: sub add_cell {
645: my ($self, $text, $config) = @_;
646:
1.8 foxr 647: if($tracing) {&Apache::lonnet::logthis("add_cell : $text"); }
648:
1.3 foxr 649: # If a row is not open, we must open it:
650:
1.5 foxr 651: if (!$self->{'row_open'}) {
1.3 foxr 652: $self->start_row();
653: }
1.17 foxr 654: my $rows = $self->{'rows'}->{$self->{'part'}};
1.6 foxr 655: my $current_row = $rows->[-1];
1.5 foxr 656: my $current_cells = $current_row->{'cells'};
1.6 foxr 657: my $last_coord = $current_row->{'cell_width'};
1.3 foxr 658:
1.6 foxr 659: # We have to worry about row spans if there is a prior row:
1.3 foxr 660:
1.6 foxr 661: if (scalar(@$rows) > 1) {
1.3 foxr 662:
1.6 foxr 663: my $last_row = $rows->[-2];
664: if ($last_coord < $last_row->{'cell_width'}) {
665: my $prior_coord = 0;
666: my $prior_cell_index = 0;
667: while ($prior_coord <= $last_coord) {
668:
669: # Pull a cell down if it's coord matches our start coord
670: # And there's a row span > 1.
671: # Having done so, we adjust our $last_coord to match the
672: # end point of the pulled down cell.
673:
674: my $prior_cell = $last_row->{'cells'}->[$prior_cell_index];
1.8 foxr 675: if (!defined($prior_cell)) {
676: last;
677: }
1.6 foxr 678: if (($prior_cell->{'start_col'} == $last_coord) &&
679: ($prior_cell->{'rowspan'} > 1)) {
680:
681: # Need to drop the cell down
682:
683: my %dropped_down_cell = %$prior_cell;
684: $dropped_down_cell{'rowspan'}--;
685: $dropped_down_cell{'contents'} = '';
686:
687: push(@$current_cells, \%dropped_down_cell);
688: $last_coord += $dropped_down_cell{'colspan'};
689: $current_row->{'cell_width'} = $last_coord;
690:
691: }
692: $prior_coord += $prior_cell->{'colspan'};
693: $prior_cell_index++;
694: }
1.3 foxr 695: }
1.6 foxr 696:
1.3 foxr 697: }
1.6 foxr 698:
1.3 foxr 699: #
700: # Now we're ready to build up our cell:
701:
702: my $cell = {
703: rowspan => 1,
704: colspan => 1,
1.6 foxr 705: start_col => $last_coord,
1.3 foxr 706: contents => $text
707: };
708:
709: if (defined($config)) {
710: foreach my $key (keys(%$config)) {
1.15 raeburn 711: if ($key eq 'colspan') {
712: next if ($config->{$key} == 0);
713: }
1.3 foxr 714: $cell->{$key} = $config->{$key};
715: }
716: }
1.14 foxr 717:
1.6 foxr 718: $current_row->{'cell_width'} += $cell->{'colspan'};
719:
1.14 foxr 720:
721: #
722: # Process the width if it exists. If supplied it must be of the form:
723: # float units
724: # Where units can be in, cm or mm.
725: # Regardless of the supplied units we will normalize to cm.
726: # This allows computation on units at final table generation time.
727: #
728:
729: if (exists($cell->{'width'})) {
730: my $width;
731: my $widthcm;
732: $width = $config->{'width'};
733: $widthcm = $self->size_to_cm($width);
734:
735: # If there's a column span, the actual width is divided by the span
736: # and applied to each of the columns in the span.
737:
738: $widthcm = $widthcm / $cell->{'colspan'};
739: for (my $i = $last_coord; $i < $last_coord + $cell->{'colspan'}; $i++) {
740: $self->{'col_widths'}->{$i} = $widthcm;
741: }
742:
743: }
744:
1.3 foxr 745: push(@$current_cells, $cell);
1.8 foxr 746:
747: if ($tracing) { &Apache::lonnet::logthis("add_cell done"); }
1.3 foxr 748: }
749:
1.8 foxr 750:
751: =pod
752:
753: =head2 append_cell_text
754:
755: Sometimes it's necessary to create/configure the cell and then later add text to it.
756: This sub allows text to be appended to the most recently created cell.
757:
758: =head3 Parameters
759:
760: The text to add to the cell.
761:
762: =cut
763: sub append_cell_text {
764: my ($this, $text) = @_;
765:
766: if($tracing) {&Apache::lonnet::logthis("append_cell_text: $text"); }
1.17 foxr 767: my $rows = $this->{'rows'}->{$this->{'part'}};
1.8 foxr 768: my $current_row = $rows->[-1];
769: my $cells = $current_row->{'cells'};
770: my $current_cell = $cells->[-1];
771: $current_cell->{'contents'} .= $text;
772:
773: }
1.17 foxr 774: #-------------------------- Support for row/column groups. ----
775:
776: =pod
777:
778: =head2 start_head
779:
780: starts the table head. This corresponds to the <thead> tag in
781: html/xml. All rows defined in this group will be
782: collected and placed at the front of the table come rendering time.
783: Furthermore, if the table has group borders enabled, a rule will be
784: rendered following and preceding this group of rows.
785:
786: =cut
787:
788: sub start_head {
789: my ($this) = @_;
790: if ($tracing) { &Apache::lonnet::logthis("start_head"); }
791: $this->{'part'} = 'head';
792: }
793:
794: =pod
795:
796: =head2 end_head
797:
798: Ends a table head. This corresponds to the
799: </thead> closing tag in html/xml.
800:
801: =cut
802:
803: sub end_head {
804: my ($this) = @_;
805: if ($tracing) { &Apache::lonnet::logthis("end_head"); }
806: $this->{'part'} = 'body';
807: }
808:
809: =pod
810:
811: =head2 start_foot
812:
813: Starts the table footer. All of the rows generated in the footer will
814: be rendered at the bottom of the table. This sub corresponds to the
815: <tfoot> tag in html/xml. If the table has group borders enabled, a rule
816: will be rendered at the top and bottom of the set of columns in this
817: group
818:
819: =cut
820:
821: sub start_foot {
822: my ($this) = @_;
823: if ($tracing) { &Apache::lonnet::logthis("start_foot"); }
824: $this->{'part'} = 'foot';
825: }
826:
827: =pod
828:
829: =head2 end_foot
830:
831: Ends the set of rows in the table footer. This corresponds to the
832: </tfoot> end tag in xml/html.
833:
834: =cut
835:
836: sub end_foot {
837: my ($this) = @_;
838: if ($tracing) { &Apache::lonnet::logthis("end_foot") }
839: $this->{'part'} = 'body';
840: }
841:
842: =pod
843:
844: =head2 start_body
845:
846: Starts the set of rows that will be in the table body. Note that if
847: we are not in the header or footer, body rows are implied.
848: This correspondes to the presence of a <tbody> tag in html/xml.
849: If group borders are on, a rule will be rendered at the top and bottom
850: of the body rows.
851:
852: =cut
853:
854: sub start_body {
855: my ($this) = @_;
856: if ($tracing) { &Apache::lonnet::logthis("start_body"); }
857: $this->{'part'} = 'body';
858: }
859:
860: =pod
861:
862: =head2 end_body
863:
864: Ends the set of rows in a table body. Note that in the event we are not
865: in the header or footer groups this code assumes we are in the body
866: group. I believe this is a good match to how mot browsers render.
867:
868: =cut
869:
870: sub end_body {
871: my ($this) = @_;
872: if ($tracing) { &Apache::lonnet::logthis("end_body"); }
873:
874: }
875:
876: =pod
877:
878: =head2 define_colgroup
879:
880: Define a column group a column group corresponds to the
881: <cgroup> tag in Html/Xml. A column group as we implement it has
882: the following properties tht will be shared amongst all cells in the
883: columns in the group unless overidden in the specific oell definition:
884:
885: =over 2
886:
887: =item span
888:
889: The number of columns in the column group. This defaults to 1.
890:
891: =item halign
892:
893: Horizontal alignment of the cells. This defaults to left.
894: Other values are left, center, right (justify and char are
895: accepted but treated as left).
896:
897: =item valign
898:
899: Vertical alignment of the cells. This defaults to middle.
900: Other values are top middle, bottom, (baseline is accepted and
901: treated as top).
902:
903: =back
904:
905: If group borders are turned on, a rule will be rendered
906: at the left and right side of the column group.
907:
908: =head3 parameters
909:
910: =over 2
911:
912: =item definition
913:
914: This is a hash that contains any of the keys described above that
915: define the column group.
916:
917: =back
918:
919:
920: =head3 Example
921:
922: $table->define_colgroup({
923: 'span' => 2,
924: 'halign' => 'center'
925: })
926:
927:
928:
929: =cut
930:
931: sub define_colgroup {
932: my ($this, $attributes) = @_;
933: if ($tracing) { &Apache::lonnet::logthis("col_group"); }
934:
935:
936: }
1.8 foxr 937:
1.17 foxr 938: #------------------------- Render the table ---------------------
1.8 foxr 939:
1.6 foxr 940: =pod
941:
942: =head2 generate
943:
944: Call this when the structures for the table have been built.
945: This will generate and return the table object that can be used
946: to generate the table. Returning the table object allows for
947: a certain amount of testing to be done on the generated table.
948: The caller can then ask the table object to generate LaTeX.
949:
950: =cut
1.17 foxr 951:
1.6 foxr 952: sub generate {
953: my ($this) = @_;
1.8 foxr 954: my $useP = 0;
1.14 foxr 955:
956: my $colunits = 'cm'; # All widths get normalized to cm.
957: my $tablewidth;
1.6 foxr 958:
1.8 foxr 959: if($tracing) {&Apache::lonnet::logthis("generate"); }
1.12 foxr 960: my $table = Apache::lonlatextable->new();
961:
1.17 foxr 962: my $inner_border = $this->{'inner_border'};
963: my $outer_border = $this->{'outer_border'};
964: my $column_count = $this->{'column_count'};
1.8 foxr 965:
1.17 foxr 966: my $cell_ul_border = (($inner_border == 1) || ($inner_border == 2)) ? 1 : 0;
967: my $cell_lr_border = (($inner_border == 1) || ($inner_border == 3)) ? 1 : 0;
968: my $part_border = ($inner_border == 4);
969:
1.7 foxr 970: # Add the caption if supplied.
971:
972: if ($this->{'caption'} ne "") {
973: $table->set_caption($this->caption);
974: }
975:
976: # Set the width if defined:
977:
1.14 foxr 978: my $default_width;
979: my $colwidths = $this->{'col_widths'};
1.7 foxr 980: if (defined ($this->{'width'})) {
1.14 foxr 981: $tablewidth = $this->{'width'};
982: $tablewidth = $this->size_to_cm($tablewidth);
983:
1.8 foxr 984: $useP = 1;
985:
1.14 foxr 986: # Figure out the default width for a column with unspecified
987: # We take the initially specified widths and sum them up.
988: # This is subtracted from total width above.
989: # If the result is negative we're going to allow a minimum of 2.54cm for
990: # each column and make the table spill appropriately.
991: # This (like a riot) is an ugly thing but I'm open to suggestions about
992: # how to handle it better (e.g. scaling down requested widths?).
993:
994: my $specified_width = 0.0;
995: my $specified_cols = 0;
996: foreach my $col (keys %$colwidths) {
997: $specified_width = $specified_width + $colwidths->{$col};
998: $specified_cols++;
999: }
1000: my $unspecified_cols = $this->{'column_count'} - $specified_cols;
1001:
1002: # If zero unspecified cols, we are pretty much done... just have to
1003: # adjust the total width to be specified width. Otherwise we
1004: # must figure out the default width and total width:
1005: #
1006: my $total_width;
1007: if($unspecified_cols == 0) {
1008: $total_width = $specified_width;
1009: } else {
1010: $default_width = ($tablewidth - $specified_width)/$unspecified_cols; # Could be negative....
1011: $total_width = $default_width * $unspecified_cols + $specified_width;
1012: }
1013:
1014: # if the default_width is < 0.0 the user has oversubscribed the width of the table with the individual
1015: # column. In this case, we're going to maintain the desired proportions of the user's columns, but
1016: # ensure that the unspecified columns get a fair share of the width..where a fair share is defined as
1017: # the total width of the table / unspecified column count.
1018: # We figure out what this means in terms of reducing the specified widths by dividing by a constant proportionality.
1019: # Note that this cannot happen if the user hasn't specified anywidths as the computation above would then
1020: # just make all columns equal fractions of the total table width.
1021:
1022: if ($default_width < 0) {
1023: $default_width = ($tablewidth/$unspecified_cols); # 'fair' default width.
1024: my $width_remaining = $tablewidth - $default_width*$unspecified_cols; # What's left for the specified cols.
1025: my $reduction = $tablewidth/$width_remaining; # Reduction fraction for specified cols
1026: foreach my $col (keys %$colwidths) {
1027: $colwidths->{$col} = $colwidths->{$col}/$reduction;
1028: }
1029:
1030: }
1.7 foxr 1031: }
1032:
1.17 foxr 1033: if ($tracing) { &Apache::lonnet::logthis("rendering head"); }
1034: $this->render_part('head', $table, $useP, $default_width);
1035: if ($tracing) { &Apache::lonnet::logthis("rendering body"); }
1036: $this->render_part('body', $table, $useP, $default_width);
1037: if ($tracing) { &Apache::lonnet::logthis("rendering footer"); }
1038: $this->render_part('foot', $table, $useP, $default_width);
1039:
1040:
1041:
1042:
1043:
1044: my $coldef = "";
1045: if ($outer_border || $cell_lr_border) {
1046: $coldef .= '|';
1047: }
1048: for (my $i =0; $i < $column_count; $i++) {
1049: if ($useP) {
1050: $coldef .= "p{$default_width $colunits}";
1051: } else {
1052: $coldef .= 'l';
1053: }
1054: if ($cell_lr_border ||
1055: ($outer_border && ($i == $column_count-1))) {
1056: $coldef .= '|';
1057: }
1058: }
1059: $table->{'coldef'} = $coldef;
1060:
1061: # Return the table:
1062:
1063: if ($tracing) { &Apache::lonnet::logthis("Leaving generate"); }
1064:
1065:
1066: return $table;
1067:
1068: }
1069:
1070:
1071: #---------------------------------------------------------------------------
1072: #
1073: # Private methods:
1074: #
1075:
1076: #
1077: # Convert size with units -> size in cm.
1078: # The resulting size is floating point with no units so that it can be used in
1079: # computation. Note that an illegal or missing unit is treated silently as
1080: # cm for now.
1081: #
1082: sub size_to_cm {
1083: my ($this, $size_spec) = @_;
1084: my ($size, $units) = split(/ /, $size_spec);
1085: if (lc($units) eq 'mm') {
1086: return $size / 10.0;
1087: }
1088: if (lc($units) eq 'in') {
1089: return $size * 2.54;
1090: }
1091:
1092: return $size; # Default is cm.
1093: }
1094:
1095: #
1096: # Render a part of the table. The valid table parts are
1097: # head, body and foot. These corresopnd to the set of rows
1098: # define within <thead></thead>, <tbody></tbody> and <tfoot></tfoot>
1099: # respectively.
1100: #
1101: sub render_part {
1102: my ($this, $part, $table, $useP, $default_width) = @_;
1103:
1104: if ($tracing) { &Apache::lonnet::logthis("render_part: $part") };
1105:
1106: # Do nothing if that part of the table is empty:
1107:
1108: if ($this->{'rows'}->{$part} == undef) {
1109: if ($tracing) {&Apache::lonnet::logthis("$part is empty"); }
1110: return;
1111: }
1.14 foxr 1112:
1113:
1.6 foxr 1114: # Build up the data:
1115:
1116: my @data;
1.17 foxr 1117: my $colwidths = $this->{'col_widths'};
1118: my $rows = $this->{'rows'}->{$part}; # TODO: Render header, body footer as groups.
1.6 foxr 1119: my $row_count = scalar(@$rows);
1120: my $inner_border = $this->{'inner_border'};
1121: my $outer_border = $this->{'outer_border'};
1122: my $column_count = $this->{'column_count'};
1123:
1.16 foxr 1124: my $cell_ul_border = (($inner_border == 1) || ($inner_border == 2)) ? 1 : 0;
1125: my $cell_lr_border = (($inner_border == 1) || ($inner_border == 3)) ? 1 : 0;
1.17 foxr 1126: my $part_border = ($inner_border == 4);
1127: my $colunits = 'cm'; # All units in cm.
1128:
1.11 foxr 1129: # Add a top line if the outer or inner border is enabled:
1.17 foxr 1130: # or if group rules are on.
1131: #
1.11 foxr 1132:
1.17 foxr 1133: if ($outer_border || $cell_ul_border || $part_border) {
1.11 foxr 1134: push(@data, ["\\cline{1-$column_count}"]);
1135:
1136: }
1137:
1.6 foxr 1138: for (my $row = 0; $row < $row_count; $row++) {
1139: my @row;
1140: my $cells = $rows->[$row]->{'cells'};
1.7 foxr 1141: my $def_halign = $rows->[$row]->{'default_halign'};
1.6 foxr 1142: my $cell_count = scalar(@$cells);
1143: my $startcol = 1;
1144: my @underlines; # Array of \cline cells if cellborder on.
1145:
1.8 foxr 1146:
1.11 foxr 1147:
1.6 foxr 1148: for (my $cell = 0; $cell < $cell_count; $cell++) {
1149: my $contents = $cells->[$cell]->{'contents'};
1.7 foxr 1150:
1151: #
1152: # Cell alignment is the default alignment unless
1153: # explicitly specified in the cell.
1154: # NOTE: at this point I don't know how to do vert alignment.
1155: #
1156:
1157: my $halign = $def_halign;
1158: if (defined ($cells->[$cell]->{'halign'})) {
1159: $halign = $cells->[$cell]->{'halign'};
1160: }
1161:
1162: # Create the horizontal alignment character:
1163:
1164: my $col_align = 'l';
1.8 foxr 1165: my $embeddedAlignStart = "";
1166: my $embeddedAlignEnd = "";
1167:
1.7 foxr 1168: if ($halign eq 'right') {
1169: $col_align = 'r';
1.13 raeburn 1170: $embeddedAlignStart = '\raggedleft';
1.7 foxr 1171: }
1172: if ($halign eq 'center') {
1173: $col_align = 'c';
1.8 foxr 1174: $embeddedAlignStart = '\begin{center}';
1175: $embeddedAlignEnd = '\end{center}';
1.7 foxr 1176: }
1.8 foxr 1177:
1178: # If the width has been specified, turn these into
1179: # para mode; and wrap the contents in the start/stop stuff:
1180:
1181: if ($useP) {
1.14 foxr 1182: my $cw;
1183: if (defined($colwidths->{$cell})) {
1184: $cw = $colwidths->{$cell};
1185: } else {
1186: $cw = $default_width;
1187: }
1.15 raeburn 1188: $cw = $cw * $cells->[$cell]->{'colspan'};
1.8 foxr 1189: $col_align = "p{$cw $colunits}";
1190: $contents = $embeddedAlignStart . $contents . $embeddedAlignEnd;
1191: }
1192:
1.16 foxr 1193: if ($cell_lr_border || ($outer_border && ($cell == 0))) {
1.7 foxr 1194: $col_align = '|'.$col_align;
1195: }
1.16 foxr 1196: if ($cell_lr_border || ($outer_border && ($cell == ($cell_count -1)))) {
1.7 foxr 1197: $col_align = $col_align.'|';
1198: }
1199:
1200: #factor in spans:
1201:
1.6 foxr 1202: my $cspan = $cells->[$cell]->{'colspan'};
1203: my $nextcol = $startcol + $cspan;
1.8 foxr 1204:
1205: # If we can avoid the \multicolumn directive that's best as
1206: # that makes some things like \parpic invalid in LaTeX which
1207: # screws everything up.
1208:
1209: if (($cspan > 1) || !($col_align =~ /l/)) {
1210:
1211: $contents = '\multicolumn{'.$cspan.'}{'.$col_align.'}{'.$contents.'}';
1212:
1213: # A nasty edge case. If there's only one cell, the software will assume
1214: # we're in complete control of the row so we need to end the row ourselves.
1215:
1216: if ($cell_count == 1) {
1217: $contents .= ' \\\\';
1218: }
1219: }
1.16 foxr 1220: if ($cell_ul_border && ($cells->[$cell]->{'rowspan'} == 1)) {
1.6 foxr 1221: my $lastcol = $nextcol -1;
1222: push(@underlines, "\\cline{$startcol-$lastcol}");
1223: }
1224: $startcol = $nextcol;
1225: # Rowspans should take care of themselves.
1226:
1227: push(@row, $contents);
1228:
1229: }
1230: push(@data, \@row);
1.16 foxr 1231: if ($cell_ul_border) {
1.6 foxr 1232: for (my $i =0; $i < scalar(@underlines); $i++) {
1233: push(@data, [$underlines[$i]]);
1234: }
1235: }
1236:
1237: }
1.11 foxr 1238: #
1239: # Add bottom border if necessary: if the inner border was on, the loops above
1240: # will have done a bottom line under the last cell.
1241: #
1.17 foxr 1242: if (($outer_border || $part_border) && !$cell_ul_border) {
1.11 foxr 1243: push(@data, ["\\cline{1-$column_count}"]);
1244:
1245: }
1.17 foxr 1246: $table->set_data(\@data);
1.6 foxr 1247: }
1.14 foxr 1248:
1.6 foxr 1249: #----------------------------------------------------------------------------
1.5 foxr 1250: # The following methods allow for testability.
1.4 foxr 1251:
1252:
1253: sub get_object_attribute {
1254: my ($self, $attribute) = @_;
1.8 foxr 1255: if ($tracing > 1) { &Apache::lonnet::logthis("get_object_attribute: $attribute"); }
1.4 foxr 1256: return $self->{$attribute};
1257: }
1258:
1.5 foxr 1259: sub get_row {
1260: my ($self, $row) = @_;
1.8 foxr 1261: if ($tracing > 1) { &Apache::lonnet::logthis("get_row"); }
1262:
1.17 foxr 1263: my $rows = $self->{'rows'}->{$self->{'part'}}; # ref to an array....
1.5 foxr 1264: return $rows->[$row]; # ref to the row hash for the selected row.
1265: }
1.14 foxr 1266:
1.1 foxr 1267: # Mandatory initialization.
1.4 foxr 1268: BEGIN{
1269: }
1.1 foxr 1270:
1271: 1;
1272: __END__
1.17 foxr 1273:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>