1 # This file is part of reLyX
2 # Copyright (c) 1998-9 Amir Karger karger@post.harvard.edu
3 # You are free to use and modify this code under the terms of
4 # the GNU General Public Licence version 2 or later.
8 # This is a package to read LaTeX tables and print out LyX tables
11 # We declare here the sub-packages found in this package.
12 # This allows the parser to understand "indirect object" form of subroutines
14 package RelyxTable::Table;
15 package RelyxTable::Column;
16 package RelyxTable::Row;
21 # Variables used by other packages
22 use vars qw(@table_array $TableBeginString $TableEndString);
23 # @table_array is the list of all arrays
24 # $TableBeginString is a string to write during one pass so that a later
25 # pass knows to put the table info there
26 # $TableEndString is written at the end of the table so that we know
28 $TableBeginString = '%%%%%Insert reLyX table here!';
29 $TableEndString = '%%%%%End of reLyX table!';
34 # Are we currently inside a table?
35 # If we are, return the table
37 return "" unless defined(@table_array); # no tables exist
38 my $thistable = $table_array[-1];
39 if ($thistable->{"active"}) {
40 return (bless $thistable, "RelyxTable::Table");
47 # Global variables###############
48 # LyX' enums corresponding to table alignments
49 my %TableAlignments = ("l" => 2, "r" => 4, "c" => 8);
50 # LyX' enums corresponding to multicol types
51 # normal (non-multicol) cell, beginning of a multicol, part of a multicol
52 my %MulticolumnTypes = ("normal" => 0, "begin" => 1, "part" => 2);
54 # Subroutines used by tables and rows, e.g.
56 # parse a table's columns' description
57 # Returns an array where each element is one column description
58 # arg0 is the description -- a Text::TeX::Group
60 my (@cols, @new_cols);
61 my ($tok, $description, $i);
63 # tokens in the group, not including '{' and '}'
64 my @group = $groupref->contents;
66 # Loop over the token(s) in the group
67 my $first = ""; my $tempfirst;
71 # Each $tok will consist of /^[clr|]*[p*@]?$/
72 # (Except first may have | and/or @ expressions before it)
73 # p*@ will end the $tok since after it comes a group in braces
74 # @ will be a TT::Token, everything else will be in TT::Text
75 $description = $tok->print;
77 # Chop off left lines for first column if any
78 ($tempfirst = $description) =~ s/(\|*).*/$1/;
79 if ($#cols == -1) { # |'s before any column description
82 $cols[-1] .= $tempfirst; # add it to end of current col
85 # Greedy searches, so only 0th column can possibly have left line
86 @new_cols = ($description =~ /[clr]\|*/g);
87 push @cols, @new_cols;
89 # parse a p or * or @ if necessary
90 # use exact_print in case there's weird stuff in the @ descriptions
91 $description = substr($description,-1);
92 # if ($description eq 'p') {
93 # The m and p descriptors have identical form.
94 if ($description =~ /^[mp]$/) {
96 my $pdes = $description . $tok->exact_print; # "p{foo}"
99 } elsif ($description eq '@') {
100 $tok = shift(@group);
101 my $atdes = $description . $tok->exact_print;
102 if ($#cols == -1) { # it's an @ before any column description
105 $cols[-1] .= $atdes; # add it to end of current col
108 } elsif ($description eq '*') {
110 $tok = shift(@group); # TT::Group with number of repeats in it
111 my $rep = $tok->contents->print;
112 $tok = shift(@group); # Group to repeat $rep times
113 @new_cols = &parse_cols($tok);
114 foreach $i (1 .. $rep) {
115 push @cols, @new_cols;
118 } # end loop over description tokens
120 # this handles description like {|*{3}{c}}
121 $cols[0] = $first . $cols[0];
124 } # end sub parse_cols
131 return ' ' . $name . '="' . $s . '"';
139 write_string $name, "true";
147 write_string $name, $i;
150 ################################################################################
151 # This package handles tables for reLyX
154 package RelyxTable::Table;
157 # columns - array containing references to RelyxTable::Columns
158 # rows - array containing references to RelyxTable::Rows
159 # active - are we currently reading this table?
160 # Fields for printout
169 # Subroutines to read and create the table
171 # 'new' takes an argument containing the LaTeX table description string,
172 # which is a Text::TeX::Group token
174 my $class = shift; # should be "table"
175 my $description = shift;
177 # This seems like a convenient place to declare this...
178 $debug_on= (defined($main::opt_d) && $main::opt_d);
180 # Initialize fields - including ones we don't support yet
181 $thistable->{"is_long_table"} = 0;
182 $thistable->{"rotate"} = 0;
183 $thistable->{"endhead"} = 0;
184 $thistable->{"end_first_head"} = 0;
185 $thistable->{"endfoot"} = 0;
186 $thistable->{"end_last_foot"} = 0;
187 $thistable->{"active"} = 1;
189 bless $thistable, $class;
191 # Parse the column descriptions: return an array, where each
192 # element is a (regular text) single column description
193 my @cols = &RelyxTable::parse_cols($description);
196 foreach $col_description (@cols) {
197 $colref = new RelyxTable::Column $col_description;
198 push @{$thistable->{"columns"}}, $colref;
200 # put the table into the table array
201 push @RelyxTable::table_array, $thistable;
204 # Now that it's blessed, put the 0th row into the table
211 # add a row to the table
212 # Since we're starting the row, we're in the 0th column
213 my $thistable = shift;
214 my $row = new RelyxTable::Row;
215 push (@{$thistable->{"rows"}}, $row);
217 # Also initialize the cells for this row
219 foreach $col (@{$thistable->{"columns"}}) {
220 push (@{$row->{"cells"}}, RelyxTable::Cell->new($row, $col));
225 # Go to next column - this just involves calling RT::Row->nextcol
227 my $thistable = shift;
228 my $row = $thistable->current_row;
230 } # end of sub nextcol
233 # interpret an '\hline' or '\cline' command
234 # (It's cline if there's an arg1)
236 # Add a bottom line to the row *before* the current row, unless it's
237 # the top row. In that case, add a top line to the current (top) row
238 # Change the row and all the cells that make up the row
240 # Change the cells from the row in the range given in arg1
241 my $thistable = shift;
243 my $is_cline = defined($range);
244 my ($rownum, $line_str, $lastrow, $cell);
246 if ($lastrow = $thistable->numrows - 1) { # not top row
247 $rownum = $lastrow - 1;
248 $line_str = "bottom_line";
251 $line_str = "top_line";
254 my $row = $thistable->{"rows"}[$rownum];
255 # Add a row line (only) if it's a \hline command
257 $row->{"$line_str"} +=1;
258 if (defined($main::opt_d) && $row->{"$line_str"} == 2) {
259 print "\nToo many \\hline's";
263 # Figure out which rows to change
266 $range =~ /(\d+)-(\d+)/ or warn "weird \\cline range";
267 # LaTeX numbers columns from 1, we number from 0
268 ($r1, $r2) = ($1 - 1, $2 - 1);
271 $r2 = $thistable->numcols - 1;
275 foreach $i ($r1 .. $r2) {
276 $cell = $row->{"cells"}[$i];
277 $cell->{"$line_str"} +=1; # change the cells in the row
282 # interpret a \multicolumn command
283 # This really just needs to call RT::Row->multicolumn for the correct row
284 my $thistable = shift;
285 my $row = $thistable->current_row;
286 $row->multicolumn(@_);
287 } # end sub multicolumn
290 # Finished reading a table
291 my $thistable = shift;
292 # If we just had \hlines at the end, it's not a real row
293 # But if numcols==1, curr_col *has* to be zero!
294 # HACK HACK HACK. If numcols==1 but we need to subtract a row, we
295 # won't know until LastLyX. At that point, we'll subtract a row.
296 my $row = $thistable->current_row;
297 if ($thistable->numcols > 1 && $row->{"curr_col"} == 0) {
298 pop @{$thistable->{"rows"}}
301 # We're no longer reading this table
302 $thistable->{"active"} = 0;
305 print "\nDone with table ",$#RelyxTable::table_array,", which has ",
306 $thistable->numrows," rows and ",
307 $thistable->numcols," columns";
308 print"\nNumber of rows may be 1 too high" if $thistable->numcols==1;
310 } # end sub done_reading
313 # Subroutine to print out the table once it's created
318 # Subroutine to print out the table in \lyxformat 221
319 my $thistable = shift;
322 $to_print .= "\n<lyxtabular" .
323 RelyxTable::write_int("version", 3) .
324 RelyxTable::write_int("rows", $thistable->numrows) .
325 RelyxTable::write_int("columns", $thistable->numcols) .
327 # global longtable options
328 $to_print .= "<features" .
329 RelyxTable::write_int ("rotate", $thistable->{"rotate"}) .
330 RelyxTable::write_bool("islongtable", $thistable->{"is_long_table"}) .
331 RelyxTable::write_int ("firstHeadTopDL", 0) .
332 RelyxTable::write_int ("firstHeadBottomDL", 0) .
333 RelyxTable::write_bool("firstHeadEmpty", 0) .
334 RelyxTable::write_int ("headTopDL", 0) .
335 RelyxTable::write_int ("headBottomDL", 0) .
336 RelyxTable::write_int ("footTopDL", 0) .
337 RelyxTable::write_int ("footBottomDL", 0) .
338 RelyxTable::write_int ("lastFootTopDL", 0) .
339 RelyxTable::write_int ("lastFootBottomDL", 0) .
340 RelyxTable::write_bool("lastFootEmpty", 0) .
344 foreach $col (@{$thistable->{"columns"}}) {
345 $to_print .= $col->print_info_221;
350 foreach $row (@{$thistable->{"rows"}}) {
351 $to_print .= $row->print_info_221;
353 foreach $col (@{$thistable->{"columns"}}) {
354 $cell = $row->{"cells"}[$count];
356 $to_print .= $cell->print_info_221;
358 $to_print .= "</row>\n";
360 $to_print .= "</lyxtabular>\n";
362 } # end sub print_info_221
365 # Subroutine to print out the table in \lyxformat 215
366 # print the header information for this table
367 my $thistable = shift;
369 $to_print .= "\n\\LyXTable\nmulticol5\n";
370 my @arr = ($thistable->numrows,
372 $thistable->{"is_long_table"},
373 $thistable->{"rotate"},
374 $thistable->{"endhead"},
375 $thistable->{"end_first_head"},
376 $thistable->{"endfoot"},
377 $thistable->{"end_last_foot"}
379 $to_print .= join(" ",@arr);
384 foreach $row (@{$thistable->{"rows"}}) {
385 $to_print .= $row->print_info;
390 foreach $col (@{$thistable->{"columns"}}) {
391 $to_print .= $col->print_info;
396 foreach $row (@{$thistable->{"rows"}}) {
398 foreach $col (@{$thistable->{"columns"}}) {
399 $cell = $row->{"cells"}[$count];
401 $to_print .= $cell->print_info;
408 } # end sub print_info_215
410 # Convenient subroutines
412 my $thistable = shift;
413 return $#{$thistable->{"rows"}} + 1;
417 my $thistable = shift;
418 return $#{$thistable->{"columns"}} + 1;
422 # Return the current row blessed as an RT::Row
423 my $thistable = shift;
424 my $row = $thistable->{"rows"}[-1];
425 bless $row, "RelyxTable::Row"; #... and return it
426 } # end sub current_row
428 } # end package RelyxTable::Table
430 ################################################################################
434 package RelyxTable::Column;
437 # alignment - left, right, or center (l, r, or c)
438 # right_line- How many lines this column has to its right
439 # left_line - How many lines this column has to its left
440 # (only first column can have left lines!)
441 # pwidth - width argument to a 'p' alignment command -- e.g., 10cm
442 # special - special column description that lyx can't handle
446 my $description = shift;
449 # Initially zero everything, since we set different
450 # fields for @ and non-@ columns
451 $col->{"alignment"} = "c"; # default
452 $col->{"left_line"} = 0;
453 $col->{"right_line"} = 0;
454 $col->{"pwidth"} = "";
455 $col->{"special"} = "";
457 # Any special (@) column should be handled differently
458 if ($description =~ /\@/ || $description =~ /^m/ ) {
459 # Just put the whole description in "special" field --- this
460 # corresponds the the "extra" field in LyX table popup
461 # Note that LyX ignores alignment, r/l lines for a special column
462 $col->{"special"} = $description;
463 print "\n'$description' column won't display WYSIWYG in LyX\n"
466 # It's not a special @ column
470 $description =~ s/^\|*//;
471 $col->{"left_line"} = length($&);
473 # main column description
474 $description =~ s/^[clrp]//;
476 $description =~ s/^\{(.+)\}//; # eat the width
477 $col->{"pwidth"} = $1; # width without braces
478 # note: alignment is not applicable for 'p' columns
480 $col->{"alignment"} = $&;
484 $description =~ s/^\|*//;
485 $col->{"right_line"} = length($&);
488 bless $col, $class; #... and return it
492 # print out header information for this column
493 # Note that we need to put "" around pwidth and special for multicol5 format
496 my @arr = ($TableAlignments{$col->{"alignment"}},
498 $col->{"right_line"},
499 '"' . $col->{"pwidth"} . '"',
500 '"' . $col->{"special"} . '"'
502 $to_print .= join(" ",@arr);
509 # print out header information for this column
513 $to_print = "<column" .
514 # RelyxTable::write_attribute("alignment", $TableAlignments{$col->{"alignment"}) .
515 # RelyxTable::write_attribute("valignment", 0) .
516 # RelyxTable::write_attribute("leftline", $col->{"left_line"}) .
517 # RelyxTable::write_attribute("rightline", $col->{"right_line"} .
518 # RelyxTable::write_length("width", $col->{"pwidth"}) .
519 RelyxTable::write_string("special", $col->{"special"}) .
524 } # end package RelyxTable::Column
526 ################################################################################
529 package RelyxTable::Row;
531 # top_line - does this row have a top line?
532 # bottom_line - does this row have a bottom line?
533 # curr_col - which column we're currently dealing with
534 # cells - array containing references to this row's cells
539 $row->{"top_line"} = 0;
540 $row->{"bottom_line"} = 0;
541 $row->{"is_cont_row"} = 0;
542 $row->{"newpage"} = 0;
543 $row->{"curr_col"} = 0;
549 # Go to next column on the current row
551 my $i = $row->{"curr_col"};
554 # What if it was a multicolumn?
555 # $rcells holds a reference to the array of cells
556 my $rcells = \@{$row->{"cells"}};
557 # Paranoia check that we're not attempting to access beyond the
558 # end of the array in case reLyX failed to parse the number of
560 $i++ while ($i < @{$rcells} &&
561 ${$rcells}[$i]->{"multicolumn"} eq "part");
563 $row->{"curr_col"} = $i;
564 } # end of sub nextcol
567 # interpret a \multicolumn command
568 # Arg0 is the row that the multicolumn is in
569 # Arg 1 is the first argument to \multicolumn, simply a number (no braces)
570 # Arg 2 is the second argument, which is a TT::Group column specification
572 my ($num_cols, $coldes) = (shift, shift);
574 # parse_cols warns about @{} expressions, which aren't WYSIWYG
575 # and turns the description into a simple string
576 my @dum = &RelyxTable::parse_cols($coldes);
577 # LaTeX multicolumn description can only describe one column...
578 warn "Strange multicolumn description $coldes" if $#dum;
579 my $description = $dum[0];
582 my $firstcell = $row->{"curr_col"};
583 my $cell = $row->{"cells"}[$firstcell];
584 $cell->{"multicolumn"} = "begin";
585 # Simple descriptions use alignment field, others use special
586 # Special isn't WYSIWYG in LyX -- currently, LyX can't display
587 # '|' or @{} stuff in multicolumns
588 if ($description =~ /^[clr]$/) {
589 $cell->{"alignment"} = $description;
591 $cell->{"special"} = $description;
592 print "\n'$description' multicolumn won't display WYSIWYG in LyX\n"
598 foreach $i (1 .. $num_cols-1) {
599 $cell = $row->{"cells"}[$firstcell + $i];
600 $cell->{"multicolumn"} = "part";
603 } # end sub multicolumn
606 # print information for this column
609 my @arr = ($row->{"top_line"},
610 $row->{"bottom_line"},
611 $row->{"is_cont_row"},
614 $to_print .= join(" ",@arr);
618 } # end sub print_info
621 # print out header information for this column
626 # RelyxTable::write_attribute("topline", $row->{"top_line"}) .
627 # RelyxTable::write_attribute("bottomline", $row->{"bottom_line"}) .
628 # RelyxTable::write_attribute("endhead", $row->{"endhead"}) .
629 # RelyxTable::write_attribute("endfirsthead", $row->{"endfirsthead"}) .
630 # RelyxTable::write_attribute("endfoot", $row->{"endfoot"}) .
631 # RelyxTable::write_attribute("endlastfoot", $row->{"endlastfoot"}) .
632 # RelyxTable::write_attribute("newpage", $row->{"newpage"}) .
636 } # end package RelyxTable::Row
638 ################################################################################
641 package RelyxTable::Cell;
643 # multicolumn - 0 (regular cell), 1 (beg. of multicol), 2 (part of multicol)
644 # alignment - alignment of this cell
645 # top_line - does the cell have a line on the top?
646 # bottom_line - does the cell have a line on the bottom?
648 # rotate - rotate cell?
649 # line_breaks - cell has line breaks in it (???)
650 # special - does this multicol have a special description (@ commands?)
651 # pwidth - pwidth of this cell for a parbox command (for linebreaks)
654 # args 1 and 2 are the parent row and column of this cell
656 my ($parent_row, $parent_col) = (shift, shift);
658 $cell->{"multicolumn"} = "normal"; # by default, it isn't a multicol
659 $cell->{"alignment"} = "l"; # doesn't really matter: will be reset soon
660 $cell->{"top_line"} = 0;
661 $cell->{"bottom_line"} = 0;
662 $cell->{"has_cont_row"} = 0;
663 $cell->{"rotate"} = 0;
664 $cell->{"line_breaks"} = 0;
665 $cell->{"special"} = "";
666 $cell->{"pwidth"} = "";
668 # Have to bless $cell here, so that we can call methods on it
671 # The cell should inherit characteristics from its parent row & col
672 $cell->row_inherit($parent_row);
673 $cell->col_inherit($parent_col);
679 # Inherit fields from parent row
680 my ($cell, $row) = (shift, shift);
681 $cell->{"top_line"} = $row->{"top_line"};
682 $cell->{"bottom_line"} = $row->{"bottom_line"};
683 } # end sub row_inherit
686 # Inherit field(s) from parent column
687 my ($cell, $col) = (shift, shift);
688 $cell->{"alignment"} = $col->{"alignment"};
692 # print information for this cell
693 # Note that we need to put "" around pwidth and special for multicol5 format
696 my @arr = ($MulticolumnTypes{$cell->{"multicolumn"}},
697 $TableAlignments{$cell->{"alignment"}},
699 $cell->{"bottom_line"},
700 $cell->{"has_cont_row"},
702 $cell->{"line_breaks"},
703 '"' . $cell->{"special"} . '"',
704 '"' . $cell->{"pwidth"} . '"',
706 $to_print .= join(" ",@arr);
712 # print out header information for this column
716 $to_print = "<cell" .
717 # RelyxTable::write_attribute("topline", $row->{"top_line"}) .
718 # RelyxTable::write_attribute("bottomline", $row->{"bottom_line"}) .
719 # RelyxTable::write_attribute("endhead", $row->{"endhead"}) .
720 # RelyxTable::write_attribute("endfirsthead", $row->{"endfirsthead"}) .
721 # RelyxTable::write_attribute("endfoot", $row->{"endfoot"}) .
722 # RelyxTable::write_attribute("endlastfoot", $row->{"endlastfoot"}) .
723 # RelyxTable::write_attribute("newpage", $row->{"newpage"}) .
730 } # end package RelyxTable::Cell
732 1; # return "true" to calling routine