Annotation of loncom/xml/lontable.pm, revision 1.17
1.1 foxr 1: # The LearningOnline Network with CAPA
2: # Generating TeX tables.
3: #
1.17 ! foxr 4: # $Id: lontable.pm,v 1.16 2011/04/05 10:02:58 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.17 ! foxr 60: use Data::Dumper;
1.1 foxr 61:
1.17 ! foxr 62: my $tracing = 1; # 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:
! 536: &Apache::lonnet::logthis($self->{'part'});
! 537: &Apache::lonnet::logthis(Dumper($self->{'rows'}));
! 538: my $row = $self->{'rows'}->{$self->{'part'}}->[-1];
1.5 foxr 539: my $cells = $row->{'cells'};
1.3 foxr 540:
1.6 foxr 541: if ($row->{'cell_width'} > $self->{'column_count'}) {
542: $self->{'column_count'} = $row->{'cell_width'};
1.2 foxr 543: }
544:
1.5 foxr 545: $self->{'row_open'} = 0;;
1.2 foxr 546: }
547: }
548:
549: =pod
550:
1.3 foxr 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:
1.2 foxr 556:
1.3 foxr 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:
1.2 foxr 560:
1.3 foxr 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:
1.14 foxr 574:
1.3 foxr 575: =back
1.2 foxr 576:
577: =cut
578:
1.3 foxr 579: sub configure_row {
580: my ($self, $config) = @_;
1.8 foxr 581: if($tracing) {&Apache::lonnet::logthis("configure_row");}
1.5 foxr 582: if (!$self->{'row_open'}) {
1.3 foxr 583: $self->start_row();
584: }
585:
1.17 ! foxr 586: my $row = $self->{'rows'}->{$self->{'part'}}->[-1];
1.3 foxr 587: foreach my $config_item (keys %$config) {
588: $row->{$config_item} = $config->{$config_item};
589: }
1.2 foxr 590: }
1.1 foxr 591:
592:
1.3 foxr 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:
1.14 foxr 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:
1.3 foxr 639: =back
640:
1.17 ! foxr 641: =back
! 642:
1.3 foxr 643: =cut
644:
645: sub add_cell {
646: my ($self, $text, $config) = @_;
647:
1.8 foxr 648: if($tracing) {&Apache::lonnet::logthis("add_cell : $text"); }
649:
1.3 foxr 650: # If a row is not open, we must open it:
651:
1.5 foxr 652: if (!$self->{'row_open'}) {
1.3 foxr 653: $self->start_row();
654: }
1.17 ! foxr 655: my $rows = $self->{'rows'}->{$self->{'part'}};
1.6 foxr 656: my $current_row = $rows->[-1];
1.5 foxr 657: my $current_cells = $current_row->{'cells'};
1.6 foxr 658: my $last_coord = $current_row->{'cell_width'};
1.3 foxr 659:
1.6 foxr 660: # We have to worry about row spans if there is a prior row:
1.3 foxr 661:
1.6 foxr 662: if (scalar(@$rows) > 1) {
1.3 foxr 663:
1.6 foxr 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];
1.8 foxr 676: if (!defined($prior_cell)) {
677: last;
678: }
1.6 foxr 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: }
1.3 foxr 696: }
1.6 foxr 697:
1.3 foxr 698: }
1.6 foxr 699:
1.3 foxr 700: #
701: # Now we're ready to build up our cell:
702:
703: my $cell = {
704: rowspan => 1,
705: colspan => 1,
1.6 foxr 706: start_col => $last_coord,
1.3 foxr 707: contents => $text
708: };
709:
710: if (defined($config)) {
711: foreach my $key (keys(%$config)) {
1.15 raeburn 712: if ($key eq 'colspan') {
713: next if ($config->{$key} == 0);
714: }
1.3 foxr 715: $cell->{$key} = $config->{$key};
716: }
717: }
1.14 foxr 718:
1.6 foxr 719: $current_row->{'cell_width'} += $cell->{'colspan'};
720:
1.14 foxr 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:
1.3 foxr 746: push(@$current_cells, $cell);
1.8 foxr 747:
748: if ($tracing) { &Apache::lonnet::logthis("add_cell done"); }
1.3 foxr 749: }
750:
1.8 foxr 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"); }
1.17 ! foxr 768: my $rows = $this->{'rows'}->{$this->{'part'}};
1.8 foxr 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: }
1.17 ! foxr 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: }
1.8 foxr 938:
1.17 ! foxr 939: #------------------------- Render the table ---------------------
1.8 foxr 940:
1.6 foxr 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
1.17 ! foxr 952:
1.6 foxr 953: sub generate {
954: my ($this) = @_;
1.8 foxr 955: my $useP = 0;
1.14 foxr 956:
957: my $colunits = 'cm'; # All widths get normalized to cm.
958: my $tablewidth;
1.6 foxr 959:
1.8 foxr 960: if($tracing) {&Apache::lonnet::logthis("generate"); }
1.12 foxr 961: my $table = Apache::lonlatextable->new();
962:
1.17 ! foxr 963: my $inner_border = $this->{'inner_border'};
! 964: my $outer_border = $this->{'outer_border'};
! 965: my $column_count = $this->{'column_count'};
1.8 foxr 966:
1.17 ! foxr 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:
1.7 foxr 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:
1.14 foxr 979: my $default_width;
980: my $colwidths = $this->{'col_widths'};
1.7 foxr 981: if (defined ($this->{'width'})) {
1.14 foxr 982: $tablewidth = $this->{'width'};
983: $tablewidth = $this->size_to_cm($tablewidth);
984:
1.8 foxr 985: $useP = 1;
986:
1.14 foxr 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: }
1.7 foxr 1032: }
1033:
1.17 ! foxr 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: }
1.14 foxr 1114:
1115:
1.6 foxr 1116: # Build up the data:
1117:
1118: my @data;
1.17 ! foxr 1119: my $colwidths = $this->{'col_widths'};
! 1120: my $rows = $this->{'rows'}->{$part}; # TODO: Render header, body footer as groups.
1.6 foxr 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:
1.16 foxr 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;
1.17 ! foxr 1128: my $part_border = ($inner_border == 4);
! 1129: my $colunits = 'cm'; # All units in cm.
! 1130:
1.11 foxr 1131: # Add a top line if the outer or inner border is enabled:
1.17 ! foxr 1132: # or if group rules are on.
! 1133: #
1.11 foxr 1134:
1.17 ! foxr 1135: if ($outer_border || $cell_ul_border || $part_border) {
1.11 foxr 1136: push(@data, ["\\cline{1-$column_count}"]);
1137:
1138: }
1139:
1.6 foxr 1140: for (my $row = 0; $row < $row_count; $row++) {
1141: my @row;
1142: my $cells = $rows->[$row]->{'cells'};
1.7 foxr 1143: my $def_halign = $rows->[$row]->{'default_halign'};
1.6 foxr 1144: my $cell_count = scalar(@$cells);
1145: my $startcol = 1;
1146: my @underlines; # Array of \cline cells if cellborder on.
1147:
1.8 foxr 1148:
1.11 foxr 1149:
1.6 foxr 1150: for (my $cell = 0; $cell < $cell_count; $cell++) {
1151: my $contents = $cells->[$cell]->{'contents'};
1.7 foxr 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';
1.8 foxr 1167: my $embeddedAlignStart = "";
1168: my $embeddedAlignEnd = "";
1169:
1.7 foxr 1170: if ($halign eq 'right') {
1171: $col_align = 'r';
1.13 raeburn 1172: $embeddedAlignStart = '\raggedleft';
1.7 foxr 1173: }
1174: if ($halign eq 'center') {
1175: $col_align = 'c';
1.8 foxr 1176: $embeddedAlignStart = '\begin{center}';
1177: $embeddedAlignEnd = '\end{center}';
1.7 foxr 1178: }
1.8 foxr 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) {
1.14 foxr 1184: my $cw;
1185: if (defined($colwidths->{$cell})) {
1186: $cw = $colwidths->{$cell};
1187: } else {
1188: $cw = $default_width;
1189: }
1.15 raeburn 1190: $cw = $cw * $cells->[$cell]->{'colspan'};
1.8 foxr 1191: $col_align = "p{$cw $colunits}";
1192: $contents = $embeddedAlignStart . $contents . $embeddedAlignEnd;
1193: }
1194:
1.16 foxr 1195: if ($cell_lr_border || ($outer_border && ($cell == 0))) {
1.7 foxr 1196: $col_align = '|'.$col_align;
1197: }
1.16 foxr 1198: if ($cell_lr_border || ($outer_border && ($cell == ($cell_count -1)))) {
1.7 foxr 1199: $col_align = $col_align.'|';
1200: }
1201:
1202: #factor in spans:
1203:
1.6 foxr 1204: my $cspan = $cells->[$cell]->{'colspan'};
1205: my $nextcol = $startcol + $cspan;
1.8 foxr 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: }
1.16 foxr 1222: if ($cell_ul_border && ($cells->[$cell]->{'rowspan'} == 1)) {
1.6 foxr 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);
1.16 foxr 1233: if ($cell_ul_border) {
1.6 foxr 1234: for (my $i =0; $i < scalar(@underlines); $i++) {
1235: push(@data, [$underlines[$i]]);
1236: }
1237: }
1238:
1239: }
1.11 foxr 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: #
1.17 ! foxr 1244: if (($outer_border || $part_border) && !$cell_ul_border) {
1.11 foxr 1245: push(@data, ["\\cline{1-$column_count}"]);
1246:
1247: }
1.17 ! foxr 1248: $table->set_data(\@data);
1.6 foxr 1249: }
1.14 foxr 1250:
1.6 foxr 1251: #----------------------------------------------------------------------------
1.5 foxr 1252: # The following methods allow for testability.
1.4 foxr 1253:
1254:
1255: sub get_object_attribute {
1256: my ($self, $attribute) = @_;
1.8 foxr 1257: if ($tracing > 1) { &Apache::lonnet::logthis("get_object_attribute: $attribute"); }
1.4 foxr 1258: return $self->{$attribute};
1259: }
1260:
1.5 foxr 1261: sub get_row {
1262: my ($self, $row) = @_;
1.8 foxr 1263: if ($tracing > 1) { &Apache::lonnet::logthis("get_row"); }
1264:
1.17 ! foxr 1265: my $rows = $self->{'rows'}->{$self->{'part'}}; # ref to an array....
1.5 foxr 1266: return $rows->[$row]; # ref to the row hash for the selected row.
1267: }
1.14 foxr 1268:
1.1 foxr 1269: # Mandatory initialization.
1.4 foxr 1270: BEGIN{
1271: }
1.1 foxr 1272:
1273: 1;
1274: __END__
1.17 ! foxr 1275:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>