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