File:
[LON-CAPA] /
loncom /
xml /
lontable.pm
Revision
1.17:
download - view:
text,
annotated -
select for diffs
Wed Apr 13 10:08:06 2011 UTC (13 years, 5 months ago) by
foxr
Branches:
MAIN
CVS tags:
HEAD
BZ 6317 - NOTE This commit contains over-extensive debugging output and should not be used in production.
- This commit includes initial work to support the rule="groups" option in
<table> for printing. Commit because some drastic re-factoring and some data
structure re-engineering was requuired to capture some of the group information.
Anticipate the next commit will support <thead><tbody><tfoot> properly
both with respect to the order in which they appear in the output and
with respect to rules="groups"...if this version does not already do that
correctly...at that time the debug spew will be removed as well.
1: # The LearningOnline Network with CAPA
2: # Generating TeX tables.
3: #
4: # $Id: lontable.pm,v 1.17 2011/04/13 10:08:06 foxr Exp $
5: #
6: #
7: # Copyright Michigan State University Board of Trustees
8: #
9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
10: #
11: # LON-CAPA is free software; you can redistribute it and/or modify
12: # it under the terms of the GNU General Public License as published by
13: # the Free Software Foundation; either version 2 of the License, or
14: # (at your option) any later version.
15: #
16: # LON-CAPA is distributed in the hope that it will be useful,
17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19: # GNU General Public License for more details.
20: #
21: # You should have received a copy of the GNU General Public License
22: # along with LON-CAPA; if not, write to the Free Software
23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24: #
25: # /home/httpd/html/adm/gpl.txt
26: #
27: # http://www.lon-capa.org/
28: ## Copyright for TtHfunc and TtMfunc by Ian Hutchinson.
29: # TtHfunc and TtMfunc (the "Code") may be compiled and linked into
30: # binary executable programs or libraries distributed by the
31: # Michigan State University (the "Licensee"), but any binaries so
32: # distributed are hereby licensed only for use in the context
33: # of a program or computational system for which the Licensee is the
34: # primary author or distributor, and which performs substantial
35: # additional tasks beyond the translation of (La)TeX into HTML.
36: # The C source of the Code may not be distributed by the Licensee
37: # to any other parties under any circumstances.
38: #
39:
40: # This module is a support packkage that helps londefdef generate
41: # LaTeX tables using the Apache::lonlatextable package. A prerequisite is that
42: # the print generator must have added the following to the LaTeX
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 Apache::lonlatextable;
59: use Apache::lonnet; # for trace logging.
60: use Data::Dumper;
61:
62: my $tracing = 1; # Set to 1 to enable log tracing. 2 for local sub tracing.
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
69: In this implementation, we use the Apache::lonlatextable package to do the actual final formatting.
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:
95: =item add_cell
96:
97: Add and configure a cell to the current row.6
98:
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:
112: =over 3
113:
114:
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
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
137: when the table text is generated.
138:
139: =item width
140:
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.
145:
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:
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:
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
201: 0
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:
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:
213: =item cells
214:
215:
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:
219: =item header
220:
221: If present, the row is a 'header' that is it was made via the
222: <th> tag.
223:
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:
241: =item start_col
242:
243: The starting column of the cell in the table grid.
244:
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:
257: if ($tracing) {&Apache::lonnet::logthis("new table"); }
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,
264: inner_border => 0,
265: caption => "",
266: theme => "plain",
267: column_count => 0,
268: row_open => 0,
269: rows => {
270: 'body' => [],
271: 'head' => [],
272: 'foot' => []
273: },
274: col_widths => {},
275: part => 'body' # one of 'body', 'head', 'foot'.
276:
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:
288:
289: #-------------------------------------------------------------------------
290: #
291: # Methods that get/set table global configuration.
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:
311: if ($tracing) {&Apache::lonnet::logthis("alignment = $new_value");}
312:
313: if (defined($new_value)) {
314: $self->{'alignment'} = $new_value;
315: }
316: return $self->{'alignment'};
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:
338: if ($tracing) {&Apache::lonnet::logthis("table_border $new_value");}
339:
340: if (defined($new_value)) {
341: $self->{'outer_border'} = $new_value;
342: }
343: return $self->{'outer_border'};
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.
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:
367: =item 4 - Border around groups (colgroups as well as thead/tfoot/tbody).
368:
369:
370: =back
371:
372: =head3 Examples:
373:
374: my $cell_border = $table->cell_border(); # ask if cell borders are requested.
375: $table->cell_border(1); # Request cell borders.
376:
377: =cut
378:
379: sub cell_border {
380: my ($self, $new_value) = @_;
381: if($tracing) {&Apache::lonnet::logthis("cell_border: $new_value"); }
382: if (defined($new_value)) {
383: $self->{'inner_border'} = $new_value;
384: }
385: return $self->{'inner_border'};
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:
406: if($tracing) {&Apache::lonnet::logthis("caption: $new_value"); }
407: if (defined($new_value)) {
408: $self->{'caption'} = $new_value;
409: }
410:
411: return $self->{'caption'};
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:
423:
424: my $theme = $table->theme();
425: $table->theme("Dresden");
426:
427: =cut
428:
429: sub theme {
430: my ($self, $new_value) = @_;
431: if($tracing) {&Apache::lonnet::logthis("theme $new_value"); }
432: if (defined($new_value)) {
433: $self->{'theme'} = $new_value;
434: }
435: return $self->{'theme'};
436: }
437:
438: =pod
439:
440: =head2 width
441:
442: Gets and optionally sets the width of the table.
443:
444: =head3 Examples:
445:
446: my $newwidth = $table->width("10cm"); # 10cm width returns "10cm".
447:
448: =cut
449: sub width {
450: my ($self, $new_value) = @_;
451: if($tracing) {&Apache::lonnet::logthis("width = $new_value"); }
452:
453: if (defined($new_value)) {
454: $self->{'width'} = $new_value;
455: }
456: return $self->{'width'}; # Could be undef.
457: }
458:
459: =pod
460:
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 {
488: my ($self, $config) = @_;
489: if($tracing) {&Apache::lonnet::logthis("start_row"); }
490: if ($self->{'row_open'}) {
491: $self->end_row();
492: }
493: my $row_hash = {
494: default_halign => "left",
495: default_valign => "top",
496: cell_width => 0,
497: cells => []
498: };
499:
500: # Override the defaults if the config hash is present:
501:
502: if (defined($config)) {
503: foreach my $key (keys %$config) {
504: $row_hash->{$key} = $config->{$key};
505: }
506: }
507:
508:
509: my $rows = $self->{'rows'}->{$self->{'part'}};
510: push(@$rows, $row_hash);
511:
512: $self->{"row_open"} = 1; # Row is now open and ready for business.
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:
523: $table->end_row();
524:
525:
526: =cut
527:
528: sub end_row {
529: my ($self) = @_;
530: if($tracing) {&Apache::lonnet::logthis("end_row"); }
531: if ($self->{'row_open'}) {
532:
533: # Mostly we need to determine if this row has the maximum
534: # cell count of any row in existence in the table:
535:
536: &Apache::lonnet::logthis($self->{'part'});
537: &Apache::lonnet::logthis(Dumper($self->{'rows'}));
538: my $row = $self->{'rows'}->{$self->{'part'}}->[-1];
539: my $cells = $row->{'cells'};
540:
541: if ($row->{'cell_width'} > $self->{'column_count'}) {
542: $self->{'column_count'} = $row->{'cell_width'};
543: }
544:
545: $self->{'row_open'} = 0;;
546: }
547: }
548:
549: =pod
550:
551: =head2 configure_row
552:
553: Modify the configuration of a row. If a row is not open, a new one will be opened.
554:
555: =head3 Parameters:
556:
557: config_hash - A hash that contains new values for the set of row confiuguration
558: items to be modified. There is currently no check/penalty for items that are not in
559: the set of defined configuration properties which are:
560:
561: =over 2
562:
563: =item default_halign
564:
565: The default horizontal alignment for text in cells in the row. This can be any of:
566: "left", "right" or "center".
567:
568: =item default_valign
569:
570: The default vertical alignment for text in cells in the row. This can be any of:
571:
572: "top", "bottom" or "center"
573:
574:
575: =back
576:
577: =cut
578:
579: sub configure_row {
580: my ($self, $config) = @_;
581: if($tracing) {&Apache::lonnet::logthis("configure_row");}
582: if (!$self->{'row_open'}) {
583: $self->start_row();
584: }
585:
586: my $row = $self->{'rows'}->{$self->{'part'}}->[-1];
587: foreach my $config_item (keys %$config) {
588: $row->{$config_item} = $config->{$config_item};
589: }
590: }
591:
592:
593: =pod
594:
595: =head2 add_cell
596:
597: Add a new cell to a row. If there is a row above us, we need to
598: watch out for row spans that may force additional blank cell entries
599: to fill in the span.
600:
601: =head3 Parameters:
602:
603: =over 2
604:
605: =item text
606:
607: Text to put in the cell.
608:
609: =item cell_config
610:
611: Hash of configuration options that override the defaults. The recognized options,
612: and their defaults are:
613:
614: =over 2
615:
616: =item halign
617:
618: If nonblank overrides the row's default for the cell's horizontal alignment.
619:
620: =item valign
621:
622: If nonblank, overrides the row's default for the cdell's vertical alignment.
623:
624: =item rowspan
625:
626: Number of rows the cell spans.
627:
628: =item colspan
629:
630: Number of columns the cell spans.
631:
632: =item width
633:
634: LaTeX specification of the width of the cell.
635: Note that if there is a colspan this width is going to be equally divided
636: over the widths of the columnsn in the span.
637: Note as well that if width specification conflict, the last one specified wins...silently.
638:
639: =back
640:
641: =back
642:
643: =cut
644:
645: sub add_cell {
646: my ($self, $text, $config) = @_;
647:
648: if($tracing) {&Apache::lonnet::logthis("add_cell : $text"); }
649:
650: # If a row is not open, we must open it:
651:
652: if (!$self->{'row_open'}) {
653: $self->start_row();
654: }
655: my $rows = $self->{'rows'}->{$self->{'part'}};
656: my $current_row = $rows->[-1];
657: my $current_cells = $current_row->{'cells'};
658: my $last_coord = $current_row->{'cell_width'};
659:
660: # We have to worry about row spans if there is a prior row:
661:
662: if (scalar(@$rows) > 1) {
663:
664: my $last_row = $rows->[-2];
665: if ($last_coord < $last_row->{'cell_width'}) {
666: my $prior_coord = 0;
667: my $prior_cell_index = 0;
668: while ($prior_coord <= $last_coord) {
669:
670: # Pull a cell down if it's coord matches our start coord
671: # And there's a row span > 1.
672: # Having done so, we adjust our $last_coord to match the
673: # end point of the pulled down cell.
674:
675: my $prior_cell = $last_row->{'cells'}->[$prior_cell_index];
676: if (!defined($prior_cell)) {
677: last;
678: }
679: if (($prior_cell->{'start_col'} == $last_coord) &&
680: ($prior_cell->{'rowspan'} > 1)) {
681:
682: # Need to drop the cell down
683:
684: my %dropped_down_cell = %$prior_cell;
685: $dropped_down_cell{'rowspan'}--;
686: $dropped_down_cell{'contents'} = '';
687:
688: push(@$current_cells, \%dropped_down_cell);
689: $last_coord += $dropped_down_cell{'colspan'};
690: $current_row->{'cell_width'} = $last_coord;
691:
692: }
693: $prior_coord += $prior_cell->{'colspan'};
694: $prior_cell_index++;
695: }
696: }
697:
698: }
699:
700: #
701: # Now we're ready to build up our cell:
702:
703: my $cell = {
704: rowspan => 1,
705: colspan => 1,
706: start_col => $last_coord,
707: contents => $text
708: };
709:
710: if (defined($config)) {
711: foreach my $key (keys(%$config)) {
712: if ($key eq 'colspan') {
713: next if ($config->{$key} == 0);
714: }
715: $cell->{$key} = $config->{$key};
716: }
717: }
718:
719: $current_row->{'cell_width'} += $cell->{'colspan'};
720:
721:
722: #
723: # Process the width if it exists. If supplied it must be of the form:
724: # float units
725: # Where units can be in, cm or mm.
726: # Regardless of the supplied units we will normalize to cm.
727: # This allows computation on units at final table generation time.
728: #
729:
730: if (exists($cell->{'width'})) {
731: my $width;
732: my $widthcm;
733: $width = $config->{'width'};
734: $widthcm = $self->size_to_cm($width);
735:
736: # If there's a column span, the actual width is divided by the span
737: # and applied to each of the columns in the span.
738:
739: $widthcm = $widthcm / $cell->{'colspan'};
740: for (my $i = $last_coord; $i < $last_coord + $cell->{'colspan'}; $i++) {
741: $self->{'col_widths'}->{$i} = $widthcm;
742: }
743:
744: }
745:
746: push(@$current_cells, $cell);
747:
748: if ($tracing) { &Apache::lonnet::logthis("add_cell done"); }
749: }
750:
751:
752: =pod
753:
754: =head2 append_cell_text
755:
756: Sometimes it's necessary to create/configure the cell and then later add text to it.
757: This sub allows text to be appended to the most recently created cell.
758:
759: =head3 Parameters
760:
761: The text to add to the cell.
762:
763: =cut
764: sub append_cell_text {
765: my ($this, $text) = @_;
766:
767: if($tracing) {&Apache::lonnet::logthis("append_cell_text: $text"); }
768: my $rows = $this->{'rows'}->{$this->{'part'}};
769: my $current_row = $rows->[-1];
770: my $cells = $current_row->{'cells'};
771: my $current_cell = $cells->[-1];
772: $current_cell->{'contents'} .= $text;
773:
774: }
775: #-------------------------- Support for row/column groups. ----
776:
777: =pod
778:
779: =head2 start_head
780:
781: starts the table head. This corresponds to the <thead> tag in
782: html/xml. All rows defined in this group will be
783: collected and placed at the front of the table come rendering time.
784: Furthermore, if the table has group borders enabled, a rule will be
785: rendered following and preceding this group of rows.
786:
787: =cut
788:
789: sub start_head {
790: my ($this) = @_;
791: if ($tracing) { &Apache::lonnet::logthis("start_head"); }
792: $this->{'part'} = 'head';
793: }
794:
795: =pod
796:
797: =head2 end_head
798:
799: Ends a table head. This corresponds to the
800: </thead> closing tag in html/xml.
801:
802: =cut
803:
804: sub end_head {
805: my ($this) = @_;
806: if ($tracing) { &Apache::lonnet::logthis("end_head"); }
807: $this->{'part'} = 'body';
808: }
809:
810: =pod
811:
812: =head2 start_foot
813:
814: Starts the table footer. All of the rows generated in the footer will
815: be rendered at the bottom of the table. This sub corresponds to the
816: <tfoot> tag in html/xml. If the table has group borders enabled, a rule
817: will be rendered at the top and bottom of the set of columns in this
818: group
819:
820: =cut
821:
822: sub start_foot {
823: my ($this) = @_;
824: if ($tracing) { &Apache::lonnet::logthis("start_foot"); }
825: $this->{'part'} = 'foot';
826: }
827:
828: =pod
829:
830: =head2 end_foot
831:
832: Ends the set of rows in the table footer. This corresponds to the
833: </tfoot> end tag in xml/html.
834:
835: =cut
836:
837: sub end_foot {
838: my ($this) = @_;
839: if ($tracing) { &Apache::lonnet::logthis("end_foot") }
840: $this->{'part'} = 'body';
841: }
842:
843: =pod
844:
845: =head2 start_body
846:
847: Starts the set of rows that will be in the table body. Note that if
848: we are not in the header or footer, body rows are implied.
849: This correspondes to the presence of a <tbody> tag in html/xml.
850: If group borders are on, a rule will be rendered at the top and bottom
851: of the body rows.
852:
853: =cut
854:
855: sub start_body {
856: my ($this) = @_;
857: if ($tracing) { &Apache::lonnet::logthis("start_body"); }
858: $this->{'part'} = 'body';
859: }
860:
861: =pod
862:
863: =head2 end_body
864:
865: Ends the set of rows in a table body. Note that in the event we are not
866: in the header or footer groups this code assumes we are in the body
867: group. I believe this is a good match to how mot browsers render.
868:
869: =cut
870:
871: sub end_body {
872: my ($this) = @_;
873: if ($tracing) { &Apache::lonnet::logthis("end_body"); }
874:
875: }
876:
877: =pod
878:
879: =head2 define_colgroup
880:
881: Define a column group a column group corresponds to the
882: <cgroup> tag in Html/Xml. A column group as we implement it has
883: the following properties tht will be shared amongst all cells in the
884: columns in the group unless overidden in the specific oell definition:
885:
886: =over 2
887:
888: =item span
889:
890: The number of columns in the column group. This defaults to 1.
891:
892: =item halign
893:
894: Horizontal alignment of the cells. This defaults to left.
895: Other values are left, center, right (justify and char are
896: accepted but treated as left).
897:
898: =item valign
899:
900: Vertical alignment of the cells. This defaults to middle.
901: Other values are top middle, bottom, (baseline is accepted and
902: treated as top).
903:
904: =back
905:
906: If group borders are turned on, a rule will be rendered
907: at the left and right side of the column group.
908:
909: =head3 parameters
910:
911: =over 2
912:
913: =item definition
914:
915: This is a hash that contains any of the keys described above that
916: define the column group.
917:
918: =back
919:
920:
921: =head3 Example
922:
923: $table->define_colgroup({
924: 'span' => 2,
925: 'halign' => 'center'
926: })
927:
928:
929:
930: =cut
931:
932: sub define_colgroup {
933: my ($this, $attributes) = @_;
934: if ($tracing) { &Apache::lonnet::logthis("col_group"); }
935:
936:
937: }
938:
939: #------------------------- Render the table ---------------------
940:
941: =pod
942:
943: =head2 generate
944:
945: Call this when the structures for the table have been built.
946: This will generate and return the table object that can be used
947: to generate the table. Returning the table object allows for
948: a certain amount of testing to be done on the generated table.
949: The caller can then ask the table object to generate LaTeX.
950:
951: =cut
952:
953: sub generate {
954: my ($this) = @_;
955: my $useP = 0;
956:
957: my $colunits = 'cm'; # All widths get normalized to cm.
958: my $tablewidth;
959:
960: if($tracing) {&Apache::lonnet::logthis("generate"); }
961: my $table = Apache::lonlatextable->new();
962:
963: my $inner_border = $this->{'inner_border'};
964: my $outer_border = $this->{'outer_border'};
965: my $column_count = $this->{'column_count'};
966:
967: my $cell_ul_border = (($inner_border == 1) || ($inner_border == 2)) ? 1 : 0;
968: my $cell_lr_border = (($inner_border == 1) || ($inner_border == 3)) ? 1 : 0;
969: my $part_border = ($inner_border == 4);
970:
971: # Add the caption if supplied.
972:
973: if ($this->{'caption'} ne "") {
974: $table->set_caption($this->caption);
975: }
976:
977: # Set the width if defined:
978:
979: my $default_width;
980: my $colwidths = $this->{'col_widths'};
981: if (defined ($this->{'width'})) {
982: $tablewidth = $this->{'width'};
983: $tablewidth = $this->size_to_cm($tablewidth);
984:
985: $useP = 1;
986:
987: # Figure out the default width for a column with unspecified
988: # We take the initially specified widths and sum them up.
989: # This is subtracted from total width above.
990: # If the result is negative we're going to allow a minimum of 2.54cm for
991: # each column and make the table spill appropriately.
992: # This (like a riot) is an ugly thing but I'm open to suggestions about
993: # how to handle it better (e.g. scaling down requested widths?).
994:
995: my $specified_width = 0.0;
996: my $specified_cols = 0;
997: foreach my $col (keys %$colwidths) {
998: $specified_width = $specified_width + $colwidths->{$col};
999: $specified_cols++;
1000: }
1001: my $unspecified_cols = $this->{'column_count'} - $specified_cols;
1002:
1003: # If zero unspecified cols, we are pretty much done... just have to
1004: # adjust the total width to be specified width. Otherwise we
1005: # must figure out the default width and total width:
1006: #
1007: my $total_width;
1008: if($unspecified_cols == 0) {
1009: $total_width = $specified_width;
1010: } else {
1011: $default_width = ($tablewidth - $specified_width)/$unspecified_cols; # Could be negative....
1012: $total_width = $default_width * $unspecified_cols + $specified_width;
1013: }
1014:
1015: # if the default_width is < 0.0 the user has oversubscribed the width of the table with the individual
1016: # column. In this case, we're going to maintain the desired proportions of the user's columns, but
1017: # ensure that the unspecified columns get a fair share of the width..where a fair share is defined as
1018: # the total width of the table / unspecified column count.
1019: # We figure out what this means in terms of reducing the specified widths by dividing by a constant proportionality.
1020: # Note that this cannot happen if the user hasn't specified anywidths as the computation above would then
1021: # just make all columns equal fractions of the total table width.
1022:
1023: if ($default_width < 0) {
1024: $default_width = ($tablewidth/$unspecified_cols); # 'fair' default width.
1025: my $width_remaining = $tablewidth - $default_width*$unspecified_cols; # What's left for the specified cols.
1026: my $reduction = $tablewidth/$width_remaining; # Reduction fraction for specified cols
1027: foreach my $col (keys %$colwidths) {
1028: $colwidths->{$col} = $colwidths->{$col}/$reduction;
1029: }
1030:
1031: }
1032: }
1033:
1034: if ($tracing) { &Apache::lonnet::logthis("rendering head"); }
1035: $this->render_part('head', $table, $useP, $default_width);
1036: if ($tracing) { &Apache::lonnet::logthis("rendering body"); }
1037: $this->render_part('body', $table, $useP, $default_width);
1038: if ($tracing) { &Apache::lonnet::logthis("rendering footer"); }
1039: $this->render_part('foot', $table, $useP, $default_width);
1040:
1041:
1042:
1043:
1044:
1045: my $coldef = "";
1046: if ($outer_border || $cell_lr_border) {
1047: $coldef .= '|';
1048: }
1049: for (my $i =0; $i < $column_count; $i++) {
1050: if ($useP) {
1051: $coldef .= "p{$default_width $colunits}";
1052: } else {
1053: $coldef .= 'l';
1054: }
1055: if ($cell_lr_border ||
1056: ($outer_border && ($i == $column_count-1))) {
1057: $coldef .= '|';
1058: }
1059: }
1060: $table->{'coldef'} = $coldef;
1061:
1062: # Return the table:
1063:
1064: if ($tracing) { &Apache::lonnet::logthis("Leaving generate"); }
1065:
1066:
1067: return $table;
1068:
1069: }
1070:
1071:
1072: #---------------------------------------------------------------------------
1073: #
1074: # Private methods:
1075: #
1076:
1077: #
1078: # Convert size with units -> size in cm.
1079: # The resulting size is floating point with no units so that it can be used in
1080: # computation. Note that an illegal or missing unit is treated silently as
1081: # cm for now.
1082: #
1083: sub size_to_cm {
1084: my ($this, $size_spec) = @_;
1085: my ($size, $units) = split(/ /, $size_spec);
1086: if (lc($units) eq 'mm') {
1087: return $size / 10.0;
1088: }
1089: if (lc($units) eq 'in') {
1090: return $size * 2.54;
1091: }
1092:
1093: return $size; # Default is cm.
1094: }
1095:
1096: #
1097: # Render a part of the table. The valid table parts are
1098: # head, body and foot. These corresopnd to the set of rows
1099: # define within <thead></thead>, <tbody></tbody> and <tfoot></tfoot>
1100: # respectively.
1101: #
1102: sub render_part {
1103: my ($this, $part, $table, $useP, $default_width) = @_;
1104:
1105: if ($tracing) { &Apache::lonnet::logthis("render_part: $part") };
1106:
1107: # Do nothing if that part of the table is empty:
1108:
1109: &Apache::lonnet::logthis(Dumper($this->{'rows'}));
1110: if ($this->{'rows'}->{$part} == undef) {
1111: if ($tracing) {&Apache::lonnet::logthis("$part is empty"); }
1112: return;
1113: }
1114:
1115:
1116: # Build up the data:
1117:
1118: my @data;
1119: my $colwidths = $this->{'col_widths'};
1120: my $rows = $this->{'rows'}->{$part}; # TODO: Render header, body footer as groups.
1121: my $row_count = scalar(@$rows);
1122: my $inner_border = $this->{'inner_border'};
1123: my $outer_border = $this->{'outer_border'};
1124: my $column_count = $this->{'column_count'};
1125:
1126: my $cell_ul_border = (($inner_border == 1) || ($inner_border == 2)) ? 1 : 0;
1127: my $cell_lr_border = (($inner_border == 1) || ($inner_border == 3)) ? 1 : 0;
1128: my $part_border = ($inner_border == 4);
1129: my $colunits = 'cm'; # All units in cm.
1130:
1131: # Add a top line if the outer or inner border is enabled:
1132: # or if group rules are on.
1133: #
1134:
1135: if ($outer_border || $cell_ul_border || $part_border) {
1136: push(@data, ["\\cline{1-$column_count}"]);
1137:
1138: }
1139:
1140: for (my $row = 0; $row < $row_count; $row++) {
1141: my @row;
1142: my $cells = $rows->[$row]->{'cells'};
1143: my $def_halign = $rows->[$row]->{'default_halign'};
1144: my $cell_count = scalar(@$cells);
1145: my $startcol = 1;
1146: my @underlines; # Array of \cline cells if cellborder on.
1147:
1148:
1149:
1150: for (my $cell = 0; $cell < $cell_count; $cell++) {
1151: my $contents = $cells->[$cell]->{'contents'};
1152:
1153: #
1154: # Cell alignment is the default alignment unless
1155: # explicitly specified in the cell.
1156: # NOTE: at this point I don't know how to do vert alignment.
1157: #
1158:
1159: my $halign = $def_halign;
1160: if (defined ($cells->[$cell]->{'halign'})) {
1161: $halign = $cells->[$cell]->{'halign'};
1162: }
1163:
1164: # Create the horizontal alignment character:
1165:
1166: my $col_align = 'l';
1167: my $embeddedAlignStart = "";
1168: my $embeddedAlignEnd = "";
1169:
1170: if ($halign eq 'right') {
1171: $col_align = 'r';
1172: $embeddedAlignStart = '\raggedleft';
1173: }
1174: if ($halign eq 'center') {
1175: $col_align = 'c';
1176: $embeddedAlignStart = '\begin{center}';
1177: $embeddedAlignEnd = '\end{center}';
1178: }
1179:
1180: # If the width has been specified, turn these into
1181: # para mode; and wrap the contents in the start/stop stuff:
1182:
1183: if ($useP) {
1184: my $cw;
1185: if (defined($colwidths->{$cell})) {
1186: $cw = $colwidths->{$cell};
1187: } else {
1188: $cw = $default_width;
1189: }
1190: $cw = $cw * $cells->[$cell]->{'colspan'};
1191: $col_align = "p{$cw $colunits}";
1192: $contents = $embeddedAlignStart . $contents . $embeddedAlignEnd;
1193: }
1194:
1195: if ($cell_lr_border || ($outer_border && ($cell == 0))) {
1196: $col_align = '|'.$col_align;
1197: }
1198: if ($cell_lr_border || ($outer_border && ($cell == ($cell_count -1)))) {
1199: $col_align = $col_align.'|';
1200: }
1201:
1202: #factor in spans:
1203:
1204: my $cspan = $cells->[$cell]->{'colspan'};
1205: my $nextcol = $startcol + $cspan;
1206:
1207: # If we can avoid the \multicolumn directive that's best as
1208: # that makes some things like \parpic invalid in LaTeX which
1209: # screws everything up.
1210:
1211: if (($cspan > 1) || !($col_align =~ /l/)) {
1212:
1213: $contents = '\multicolumn{'.$cspan.'}{'.$col_align.'}{'.$contents.'}';
1214:
1215: # A nasty edge case. If there's only one cell, the software will assume
1216: # we're in complete control of the row so we need to end the row ourselves.
1217:
1218: if ($cell_count == 1) {
1219: $contents .= ' \\\\';
1220: }
1221: }
1222: if ($cell_ul_border && ($cells->[$cell]->{'rowspan'} == 1)) {
1223: my $lastcol = $nextcol -1;
1224: push(@underlines, "\\cline{$startcol-$lastcol}");
1225: }
1226: $startcol = $nextcol;
1227: # Rowspans should take care of themselves.
1228:
1229: push(@row, $contents);
1230:
1231: }
1232: push(@data, \@row);
1233: if ($cell_ul_border) {
1234: for (my $i =0; $i < scalar(@underlines); $i++) {
1235: push(@data, [$underlines[$i]]);
1236: }
1237: }
1238:
1239: }
1240: #
1241: # Add bottom border if necessary: if the inner border was on, the loops above
1242: # will have done a bottom line under the last cell.
1243: #
1244: if (($outer_border || $part_border) && !$cell_ul_border) {
1245: push(@data, ["\\cline{1-$column_count}"]);
1246:
1247: }
1248: $table->set_data(\@data);
1249: }
1250:
1251: #----------------------------------------------------------------------------
1252: # The following methods allow for testability.
1253:
1254:
1255: sub get_object_attribute {
1256: my ($self, $attribute) = @_;
1257: if ($tracing > 1) { &Apache::lonnet::logthis("get_object_attribute: $attribute"); }
1258: return $self->{$attribute};
1259: }
1260:
1261: sub get_row {
1262: my ($self, $row) = @_;
1263: if ($tracing > 1) { &Apache::lonnet::logthis("get_row"); }
1264:
1265: my $rows = $self->{'rows'}->{$self->{'part'}}; # ref to an array....
1266: return $rows->[$row]; # ref to the row hash for the selected row.
1267: }
1268:
1269: # Mandatory initialization.
1270: BEGIN{
1271: }
1272:
1273: 1;
1274: __END__
1275:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>