]> git.lyx.org Git - features.git/blob - src/tex2lyx/table.cpp
tex2lyx: support decimal alignment in tables
[features.git] / src / tex2lyx / table.cpp
1 /**
2  * \file table.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  * \author Jean-Marc Lasgouttes
8  * \author Georg Baum
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 // {[(
14
15 #include <config.h>
16
17 #include "tex2lyx.h"
18
19 #include "Context.h"
20 #include "Preamble.h"
21
22 #include "support/lassert.h"
23 #include "support/convert.h"
24 #include "support/lstrings.h"
25
26 #include <algorithm>
27 #include <iostream>
28 #include <sstream>
29 #include <vector>
30 #include <map>
31
32 using namespace std;
33
34 namespace lyx {
35
36
37 namespace {
38
39 class ColInfo {
40 public:
41         ColInfo() : align('n'), valign('n'), rightlines(0), leftlines(0),
42                 varwidth(false), decimal_point('\0') {}
43         /// column alignment
44         char align;
45         /// vertical alignment
46         char valign;
47         /// column width
48         string width;
49         /// special column alignment
50         string special;
51         /// number of lines on the right
52         int rightlines;
53         /// number of lines on the left
54         int leftlines;
55         /// varwidth column
56         bool varwidth;
57         /// decimal separator
58         char decimal_point;
59 };
60
61
62 /// row type for longtables
63 enum LTRowType
64 {
65         /// normal row
66         LT_NORMAL,
67         /// part of head
68         LT_HEAD,
69         /// part of head on first page
70         LT_FIRSTHEAD,
71         /// part of foot
72         LT_FOOT,
73         /// part of foot on last page
74         LT_LASTFOOT
75 };
76
77
78 class RowInfo {
79 public:
80         RowInfo() : topline(false), bottomline(false), type(LT_NORMAL),
81                     caption(false), newpage(false) {}
82         /// Does this row have any special setting?
83         bool special() const
84         {
85                 return topline || bottomline || !top_space.empty() ||
86                         !bottom_space.empty() || !interline_space.empty() ||
87                         type != LT_NORMAL || caption || newpage;
88         }
89         /// horizontal line above
90         bool topline;
91         /// horizontal line below
92         bool bottomline;
93         /// Extra space between the top line and this row
94         string top_space;
95         /// Extra space between this row and the bottom line
96         string bottom_space;
97         /// Extra space between the bottom line and the next top line
98         string interline_space;
99         /// These are for longtabulars only
100         /// row type (head, foot, firsthead etc.)
101         LTRowType type;
102         /// row for a caption
103         bool caption;
104         /// row for a newpage
105         bool newpage;
106 };
107
108
109 /// the numeric values are part of the file format!
110 enum Multi {
111         /// A normal cell
112         CELL_NORMAL = 0,
113         /// A multicolumn cell. The number of columns is <tt>1 + number
114         /// of CELL_PART_OF_MULTICOLUMN cells</tt> that follow directly
115         CELL_BEGIN_OF_MULTICOLUMN,
116         /// This is a dummy cell (part of a multicolumn cell)
117         CELL_PART_OF_MULTICOLUMN,
118         ///
119         CELL_BEGIN_OF_MULTIROW,
120         ///
121         CELL_PART_OF_MULTIROW
122 };
123
124
125 class CellInfo {
126 public:
127         CellInfo() : multi(CELL_NORMAL), align('n'), valign('n'),
128                      leftlines(0), rightlines(0), topline(false),
129                      bottomline(false), rotate(0), mrxnum(0) {}
130         /// cell content
131         string content;
132         /// multicolumn flag
133         Multi multi;
134         /// cell alignment
135         char align;
136         /// vertical cell alignment
137         char valign;
138         /// number of lines on the left
139         int leftlines;
140         /// number of lines on the right
141         int rightlines;
142         /// do we have a line above?
143         bool topline;
144         /// do we have a line below?
145         bool bottomline;
146         /// how is the cell rotated?
147         int rotate;
148         /// width for multicolumn cells
149         string width;
150         /// special formatting for multicolumn cells
151         string special;
152         /// multirow offset
153         string mroffset;
154         /// number of further multirows
155         int mrxnum;
156 };
157
158
159 class ltType {
160 public:
161         // constructor
162         ltType() : set(false), topDL(false), bottomDL(false), empty(false) {}
163         // we have this header type (is set in the getLT... functions)
164         bool set;
165         // double borders on top
166         bool topDL;
167         // double borders on bottom
168         bool bottomDL;
169         // used for FirstHeader & LastFooter and if this is true
170         // all the rows marked as FirstHeader or LastFooter are
171         // ignored in the output and it is set to be empty!
172         bool empty;
173 };
174
175
176 /// translate a horizontal alignment (as stored in ColInfo and CellInfo) to LyX
177 inline char const * verbose_align(char c)
178 {
179         switch (c) {
180         case 'c':
181                 return "center";
182         case 'r':
183                 return "right";
184         case 'l':
185                 return "left";
186         case 'd':
187                 return "decimal";
188         default:
189                 return "none";
190         }
191 }
192
193
194 /// translate a vertical alignment (as stored in ColInfo and CellInfo) to LyX
195 inline char const * verbose_valign(char c)
196 {
197         // The default value for no special alignment is "top".
198         switch (c) {
199         case 'm':
200                 return "middle";
201         case 'b':
202                 return "bottom";
203         case 'p':
204         default:
205                 return "top";
206         }
207 }
208
209
210 // stripped down from tabluar.C. We use it currently only for bools and
211 // strings
212 string const write_attribute(string const & name, bool const & b)
213 {
214         // we write only true attribute values so we remove a bit of the
215         // file format bloat for tabulars.
216         return b ? ' ' + name + "=\"true\"" : string();
217 }
218
219
220 string const write_attribute(string const & name, string const & s)
221 {
222         return s.empty() ? string() : ' ' + name + "=\"" + s + '"';
223 }
224
225
226 string const write_attribute(string const & name, int const & i)
227 {
228         // we write only true attribute values so we remove a bit of the
229         // file format bloat for tabulars.
230         return i ? write_attribute(name, convert<string>(i)) : string();
231 }
232
233
234 /*! rather brutish way to code table structure in a string:
235
236 \verbatim
237   \begin{tabular}{ccc}
238     1 & 2 & 3\\ \hline
239     \multicolumn{2}{c}{4} & 5 //
240     6 & 7 \\
241     8 \endhead
242   \end{tabular}
243 \endverbatim
244
245  gets "translated" to:
246
247 \verbatim
248          HLINE 1                     TAB 2 TAB 3 HLINE          HLINE LINE
249   \hline HLINE \multicolumn{2}{c}{4} TAB 5       HLINE          HLINE LINE
250          HLINE 6                     TAB 7       HLINE          HLINE LINE
251          HLINE 8                                 HLINE \endhead HLINE LINE
252 \endverbatim
253  */
254
255 char const TAB   = '\001';
256 char const LINE  = '\002';
257 char const HLINE = '\004';
258
259
260 /*!
261  * Move the information in leftlines, rightlines, align and valign to the
262  * special field. This is necessary if the special field is not empty,
263  * because LyX ignores leftlines > 1, rightlines > 1, align and valign in
264  * this case.
265  */
266 void ci2special(ColInfo & ci)
267 {
268         if (ci.width.empty() && ci.align == 'n')
269                 // The alignment setting is already in special, since
270                 // handle_colalign() never stores ci with these settings
271                 // and ensures that leftlines == 0 and rightlines == 0 in
272                 // this case.
273                 return;
274
275         if (ci.decimal_point != '\0') {
276                 // we only support decimal point natively
277                 // with 'l' alignment in or 'n' alignment
278                 // with width in second row
279                 if (ci.align != 'l' && ci.align != 'n') {
280                         ci.decimal_point = '\0';
281                         return;
282                 } else
283                         ci.special.clear();
284         }
285
286         if (!ci.width.empty()) {
287                 string arraybackslash;
288                 if (ci.varwidth)
289                         arraybackslash = "\\arraybackslash";
290                 switch (ci.align) {
291                 case 'l':
292                         ci.special += ">{\\raggedright" + arraybackslash + "}";
293                         break;
294                 case 'r':
295                         ci.special += ">{\\raggedleft" + arraybackslash + "}";
296                         break;
297                 case 'c':
298                         ci.special += ">{\\centering" + arraybackslash + "}";
299                         break;
300                 }
301                 if (ci.varwidth)
302                         ci.special += 'X';
303                 else if (ci.valign == 'n')
304                         ci.special += 'p';
305                 else
306                         ci.special += ci.valign;
307                 ci.special += '{' + ci.width + '}';
308                 ci.width.erase();
309         } else
310                 ci.special += ci.align;
311
312         // LyX can only have one left and one right line.
313         for (int i = 1; i < ci.leftlines; ++i)
314                 ci.special.insert(0, "|");
315         for (int i = 1; i < ci.rightlines; ++i)
316                 ci.special += '|';
317         ci.leftlines = min(ci.leftlines, 1);
318         ci.rightlines = min(ci.rightlines, 1);
319         ci.align = 'n';
320         ci.valign = 'n';
321 }
322
323
324 /*!
325  * Handle column specifications for tabulars and multicolumns.
326  * The next token of the parser \p p must be an opening brace, and we read
327  * everything until the matching closing brace.
328  * The resulting column specifications are filled into \p colinfo. This is
329  * in an intermediate form. fix_colalign() makes it suitable for LyX output.
330  */
331 void handle_colalign(Parser & p, vector<ColInfo> & colinfo,
332                      ColInfo const & start)
333 {
334         if (p.get_token().cat() != catBegin)
335                 cerr << "Wrong syntax for table column alignment.\n"
336                         "Expected '{', got '" << p.curr_token().asInput()
337                      << "'.\n";
338
339         ColInfo next = start;
340         for (Token t = p.get_token(); p.good() && t.cat() != catEnd;
341              t = p.get_token()) {
342 #ifdef FILEDEBUG
343                 cerr << "t: " << t << "  c: '" << t.character() << "'\n";
344 #endif
345
346                 // We cannot handle comments here
347                 if (t.cat() == catComment) {
348                         if (t.cs().empty()) {
349                                 // "%\n" combination
350                                 p.skip_spaces();
351                         } else
352                                 cerr << "Ignoring comment: " << t.asInput();
353                         continue;
354                 }
355
356                 switch (t.character()) {
357                         case ' ':
358                                 // whitespace, ignore.
359                                 break;
360                         case 'c':
361                         case 'l':
362                         case 'r':
363                                 // new column, horizontal aligned
364                                 next.align = t.character();
365                                 if (!next.special.empty()) {
366                                         ci2special(next);
367                                         // handle decimal separator
368                                         if (next.decimal_point != '\0') {
369                                                 if (!colinfo.empty() && colinfo.back().align == 'r') {
370                                                         colinfo.back().align = 'd';
371                                                         colinfo.back().decimal_point = next.decimal_point;
372                                                 } else
373                                                         next.decimal_point = '\0';
374                                         }
375                                 }
376                                 colinfo.push_back(next);
377                                 next = ColInfo();
378                                 break;
379                         case 'X':
380                                 // varwidth column
381                                 next.varwidth = true;
382                                 if (!next.special.empty())
383                                         ci2special(next);
384                                 colinfo.push_back(next);
385                                 next = ColInfo();
386                                 break;
387                         case 'p':
388                         case 'b':
389                         case 'm':
390                                 // new column, vertical aligned box
391                                 next.valign = t.character();
392                                 next.width = p.verbatim_item();
393                                 if (!next.special.empty()) {
394                                         ci2special(next);
395                                         // handle decimal separator
396                                         if (next.decimal_point != '\0') {
397                                                 if (!colinfo.empty() && colinfo.back().align == 'r') {
398                                                         colinfo.back().align = 'd';
399                                                         colinfo.back().decimal_point = next.decimal_point;
400                                                 } else
401                                                         next.decimal_point = '\0';
402                                         }
403                                 }
404                                 colinfo.push_back(next);
405                                 next = ColInfo();
406                                 break;
407                         case '|':
408                                 // vertical rule
409                                 if (colinfo.empty()) {
410                                         if (next.special.empty())
411                                                 ++next.leftlines;
412                                         else
413                                                 next.special += '|';
414                                 } else if (colinfo.back().special.empty())
415                                         ++colinfo.back().rightlines;
416                                 else if (next.special.empty())
417                                         ++next.leftlines;
418                                 else
419                                         colinfo.back().special += '|';
420                                 break;
421                         case '>': {
422                                 // text before the next column
423                                 string const s = trimSpaceAndEol(p.verbatim_item());
424                                 if (next.special.empty() &&
425                                     next.align == 'n') {
426                                         // Maybe this can be converted to a
427                                         // horizontal alignment setting for
428                                         // fixed width columns
429                                         if (s == "\\raggedleft" || s == "\\raggedleft\\arraybackslash")
430                                                 next.align = 'r';
431                                         else if (s == "\\raggedright" || s == "\\raggedright\\arraybackslash")
432                                                 next.align = 'l';
433                                         else if (s == "\\centering" || s == "\\centering\\arraybackslash")
434                                                 next.align = 'c';
435                                         else
436                                                 next.special = ">{" + s + '}';
437                                 } else
438                                         next.special += ">{" + s + '}';
439                                 break;
440                         }
441                         case '<': {
442                                 // text after the last column
443                                 string const s = trimSpaceAndEol(p.verbatim_item());
444                                 if (colinfo.empty())
445                                         // This is not possible in LaTeX.
446                                         cerr << "Ignoring separator '<{"
447                                              << s << "}'." << endl;
448                                 else {
449                                         ColInfo & ci = colinfo.back();
450                                         ci2special(ci);
451                                         ci.special += "<{" + s + '}';
452                                 }
453                                 break;
454                         }
455                         case '*': {
456                                 if (p.next_token().character() != '{')
457                                         continue;
458                                 // *{n}{arg} means 'n' columns of type 'arg'
459                                 string const num = p.verbatim_item();
460                                 string const arg = p.verbatim_item();
461                                 size_t const n = convert<unsigned int>(num);
462                                 if (!arg.empty() && n > 0) {
463                                         string s("{");
464                                         for (size_t i = 0; i < n; ++i)
465                                                 s += arg;
466                                         s += '}';
467                                         Parser p2(s);
468                                         handle_colalign(p2, colinfo, next);
469                                         next = ColInfo();
470                                 } else {
471                                         cerr << "Ignoring column specification"
472                                                 " '*{" << num << "}{"
473                                              << arg << "}'." << endl;
474                                 }
475                                 break;
476                         }
477                         case '@':
478                                 // text instead of the column spacing
479                         case '!': {
480                                         // text in addition to the column spacing
481                                         string const arg =  p.verbatim_item();
482                                         next.special += t.character();
483                                         next.special += '{' + arg + '}';
484                                         string const sarg = arg.size() > 2 ? arg.substr(0, arg.size() - 1) : string();
485                                         if (t.character() == '@' && sarg == "\\extracolsep{0pt}")
486                                                 next.decimal_point = arg.back();
487                                         break;
488                         }
489                         default: {
490                                 // try user defined column types
491                                 // unknown column types (nargs == -1) are
492                                 // assumed to consume no arguments
493                                 ci2special(next);
494                                 next.special += t.character();
495                                 int const nargs =
496                                         preamble.getSpecialTableColumnArguments(t.character());
497                                 for (int i = 0; i < nargs; ++i)
498                                         next.special += '{' +
499                                                 p.verbatim_item() + '}';
500                                 colinfo.push_back(next);
501                                 next = ColInfo();
502                                 break;
503                         }
504                         }
505         }
506
507         // Maybe we have some column separators that need to be added to the
508         // last column?
509         ci2special(next);
510         if (!next.special.empty()) {
511                 ColInfo & ci = colinfo.back();
512                 ci2special(ci);
513                 ci.special += next.special;
514                 next.special.erase();
515         }
516 }
517
518
519 /*!
520  * Move the left and right lines and alignment settings of the column \p ci
521  * to the special field if necessary.
522  */
523 void fix_colalign(ColInfo & ci)
524 {
525         if (ci.leftlines > 1 || ci.rightlines > 1)
526                 ci2special(ci);
527 }
528
529
530 /*!
531  * LyX can't handle more than one vertical line at the left or right side
532  * of a column.
533  * This function moves the left and right lines and alignment settings of all
534  * columns in \p colinfo to the special field if necessary.
535  */
536 void fix_colalign(vector<ColInfo> & colinfo)
537 {
538         // Try to move extra leftlines to the previous column.
539         // We do this only if both special fields are empty, otherwise we
540         // can't tell wether the result will be the same.
541         for (size_t col = 0; col < colinfo.size(); ++col) {
542                 if (colinfo[col].leftlines > 1 &&
543                     colinfo[col].special.empty() && col > 0 &&
544                     colinfo[col - 1].rightlines == 0 &&
545                     colinfo[col - 1].special.empty()) {
546                         ++colinfo[col - 1].rightlines;
547                         --colinfo[col].leftlines;
548                 }
549         }
550         // Try to move extra rightlines to the next column
551         for (size_t col = 0; col < colinfo.size(); ++col) {
552                 if (colinfo[col].rightlines > 1 &&
553                     colinfo[col].special.empty() &&
554                     col < colinfo.size() - 1 &&
555                     colinfo[col + 1].leftlines == 0 &&
556                     colinfo[col + 1].special.empty()) {
557                         ++colinfo[col + 1].leftlines;
558                         --colinfo[col].rightlines;
559                 }
560         }
561         // Move the lines and alignment settings to the special field if
562         // necessary
563         for (size_t col = 0; col < colinfo.size(); ++col)
564                 fix_colalign(colinfo[col]);
565 }
566
567
568 /*!
569  * Parse hlines and similar stuff.
570  * \returns wether the token \p t was parsed
571  */
572 bool parse_hlines(Parser & p, Token const & t, string & hlines,
573                   bool is_long_tabular)
574 {
575         LASSERT(t.cat() == catEscape, return false);
576
577         if (t.cs() == "hline" || t.cs() == "toprule" || t.cs() == "midrule" ||
578             t.cs() == "bottomrule")
579                 hlines += '\\' + t.cs();
580
581         else if (t.cs() == "cline")
582                 hlines += "\\cline{" + p.verbatim_item() + '}';
583
584         else if (t.cs() == "cmidrule") {
585                 // We cannot handle the \cmidrule(l){3-4} form
586                 p.pushPosition();
587                 p.skip_spaces(true);
588                 bool const hasParentheses(p.getFullArg('(', ')').first);
589                 p.popPosition();
590                 if (hasParentheses)
591                         return false;
592                 hlines += "\\cmidrule{" + p.verbatim_item() + '}';
593         }
594
595         else if (t.cs() == "addlinespace") {
596                 p.pushPosition();
597                 p.skip_spaces(true);
598                 bool const hasArgument(p.getFullArg('{', '}').first);
599                 p.popPosition();
600                 if (hasArgument)
601                         hlines += "\\addlinespace{" + p.verbatim_item() + '}';
602                 else
603                         hlines += "\\addlinespace";
604         }
605
606         else if (is_long_tabular && t.cs() == "newpage")
607                 hlines += "\\newpage";
608
609         else
610                 return false;
611
612         return true;
613 }
614
615
616 /// Position in a row
617 enum RowPosition {
618         /// At the very beginning, before the first token
619         ROW_START,
620         /// After the first token and before any column token
621         IN_HLINES_START,
622         /// After the first column token. Comments and whitespace are only
623         /// treated as tokens in this position
624         IN_COLUMNS,
625         /// After the first non-column token at the end
626         IN_HLINES_END
627 };
628
629
630 /*!
631  * Parse table structure.
632  * We parse tables in a two-pass process: This function extracts the table
633  * structure (rows, columns, hlines etc.), but does not change the cell
634  * content. The cell content is parsed in a second step in handle_tabular().
635  */
636 void parse_table(Parser & p, ostream & os, bool is_long_tabular,
637                  RowPosition & pos, unsigned flags)
638 {
639         // table structure commands such as \hline
640         string hlines;
641
642         // comments that occur at places where we can't handle them
643         string comments;
644
645         while (p.good()) {
646                 Token const & t = p.get_token();
647
648 #ifdef FILEDEBUG
649                 debugToken(cerr, t, flags);
650 #endif
651
652                 // comments and whitespace in hlines
653                 switch (pos) {
654                 case ROW_START:
655                 case IN_HLINES_START:
656                 case IN_HLINES_END:
657                         if (t.cat() == catComment) {
658                                 if (t.cs().empty())
659                                         // line continuation
660                                         p.skip_spaces();
661                                 else
662                                         // We can't handle comments here,
663                                         // store them for later use
664                                         comments += t.asInput();
665                                 continue;
666                         } else if (t.cat() == catSpace ||
667                                    t.cat() == catNewline) {
668                                 // whitespace is irrelevant here, we
669                                 // need to recognize hline stuff
670                                 p.skip_spaces();
671                                 continue;
672                         }
673                         break;
674                 case IN_COLUMNS:
675                         break;
676                 }
677
678                 // We need to handle structure stuff first in order to
679                 // determine wether we need to output a HLINE separator
680                 // before the row or not.
681                 if (t.cat() == catEscape) {
682                         if (parse_hlines(p, t, hlines, is_long_tabular)) {
683                                 switch (pos) {
684                                 case ROW_START:
685                                         pos = IN_HLINES_START;
686                                         break;
687                                 case IN_COLUMNS:
688                                         pos = IN_HLINES_END;
689                                         break;
690                                 case IN_HLINES_START:
691                                 case IN_HLINES_END:
692                                         break;
693                                 }
694                                 continue;
695                         }
696
697                         else if (t.cs() == "tabularnewline" ||
698                                  t.cs() == "\\" ||
699                                  t.cs() == "cr") {
700                                 if (t.cs() == "cr")
701                                         cerr << "Warning: Converting TeX "
702                                                 "'\\cr' to LaTeX '\\\\'."
703                                              << endl;
704                                 // stuff before the line break
705                                 os << comments << HLINE << hlines << HLINE
706                                    << LINE;
707                                 //cerr << "hlines: " << hlines << endl;
708                                 hlines.erase();
709                                 comments.erase();
710                                 pos = ROW_START;
711                                 continue;
712                         }
713
714                         else if (is_long_tabular &&
715                                  (t.cs() == "endhead" ||
716                                   t.cs() == "endfirsthead" ||
717                                   t.cs() == "endfoot" ||
718                                   t.cs() == "endlastfoot")) {
719                                 hlines += t.asInput();
720                                 switch (pos) {
721                                 case IN_COLUMNS:
722                                 case IN_HLINES_END:
723                                         // these commands are implicit line
724                                         // breaks
725                                         os << comments << HLINE << hlines
726                                            << HLINE << LINE;
727                                         hlines.erase();
728                                         comments.erase();
729                                         pos = ROW_START;
730                                         break;
731                                 case ROW_START:
732                                         pos = IN_HLINES_START;
733                                         break;
734                                 case IN_HLINES_START:
735                                         break;
736                                 }
737                                 continue;
738                         }
739                 }
740
741                 // We need a HLINE separator if we either have no hline
742                 // stuff at all and are just starting a row or if we just
743                 // got the first non-hline token.
744                 switch (pos) {
745                 case ROW_START:
746                         // no hline tokens exist, first token at row start
747                 case IN_HLINES_START:
748                         // hline tokens exist, first non-hline token at row
749                         // start
750                         os << hlines << HLINE << comments;
751                         hlines.erase();
752                         comments.erase();
753                         pos = IN_COLUMNS;
754                         break;
755                 case IN_HLINES_END:
756                         // Oops, there is still cell content or unsupported
757                         // booktabs commands after hline stuff. The latter are
758                         // moved to the cell, and the first does not work in
759                         // LaTeX, so we ignore the hlines.
760                         os << comments;
761                         comments.erase();
762                         if (support::contains(hlines, "\\hline") ||
763                             support::contains(hlines, "\\cline") ||
764                             support::contains(hlines, "\\newpage"))
765                                 cerr << "Ignoring '" << hlines
766                                      << "' in a cell" << endl;
767                         else
768                                 os << hlines;
769                         hlines.erase();
770                         pos = IN_COLUMNS;
771                         break;
772                 case IN_COLUMNS:
773                         break;
774                 }
775
776                 // If we come here we have normal cell content
777                 //
778                 // cat codes
779                 //
780                 if (t.cat() == catMath) {
781                         // we are inside some text mode thingy, so opening new math is allowed
782                         Token const & n = p.get_token();
783                         if (n.cat() == catMath) {
784                                 // TeX's $$...$$ syntax for displayed math
785                                 os << "\\[";
786                                 // This does only work because parse_math outputs TeX
787                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
788                                 os << "\\]";
789                                 p.get_token(); // skip the second '$' token
790                         } else {
791                                 // simple $...$  stuff
792                                 p.putback();
793                                 os << '$';
794                                 // This does only work because parse_math outputs TeX
795                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
796                                 os << '$';
797                         }
798                 }
799
800                 else if (t.cat() == catSpace
801                          || t.cat() == catNewline
802                          || t.cat() == catLetter
803                          || t.cat() == catSuper
804                          || t.cat() == catSub
805                          || t.cat() == catOther
806                          || t.cat() == catActive
807                          || t.cat() == catParameter)
808                         os << t.cs();
809
810                 else if (t.cat() == catBegin) {
811                         os << '{';
812                         parse_table(p, os, is_long_tabular, pos,
813                                     FLAG_BRACE_LAST);
814                         os << '}';
815                 }
816
817                 else if (t.cat() == catEnd) {
818                         if (flags & FLAG_BRACE_LAST)
819                                 return;
820                         cerr << "unexpected '}'\n";
821                 }
822
823                 else if (t.cat() == catAlign) {
824                         os << TAB;
825                         p.skip_spaces();
826                 }
827
828                 else if (t.cat() == catComment)
829                         os << t.asInput();
830
831                 else if (t.cs() == "(") {
832                         os << "\\(";
833                         // This does only work because parse_math outputs TeX
834                         parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
835                         os << "\\)";
836                 }
837
838                 else if (t.cs() == "[") {
839                         os << "\\[";
840                         // This does only work because parse_math outputs TeX
841                         parse_math(p, os, FLAG_EQUATION, MATH_MODE);
842                         os << "\\]";
843                 }
844
845                 else if (t.cs() == "begin") {
846                         string const name = p.getArg('{', '}');
847                         active_environments.push_back(name);
848                         os << "\\begin{" << name << '}';
849                         // treat the nested environment as a block, don't
850                         // parse &, \\ etc, because they don't belong to our
851                         // table if they appear.
852                         os << p.ertEnvironment(name);
853                         os << "\\end{" << name << '}';
854                         active_environments.pop_back();
855                 }
856
857                 else if (t.cs() == "end") {
858                         if (flags & FLAG_END) {
859                                 // eat environment name
860                                 string const name = p.getArg('{', '}');
861                                 if (name != active_environment())
862                                         p.error("\\end{" + name + "} does not match \\begin{"
863                                                 + active_environment() + "}");
864                                 return;
865                         }
866                         p.error("found 'end' unexpectedly");
867                 }
868
869                 else
870                         os << t.asInput();
871         }
872
873         // We can have comments if the last line is incomplete
874         os << comments;
875
876         // We can have hline stuff if the last line is incomplete
877         if (!hlines.empty()) {
878                 // this does not work in LaTeX, so we ignore it
879                 cerr << "Ignoring '" << hlines << "' at end of tabular"
880                      << endl;
881         }
882 }
883
884
885 void handle_hline_above(RowInfo & ri, vector<CellInfo> & ci)
886 {
887         ri.topline = true;
888         for (size_t col = 0; col < ci.size(); ++col)
889                 ci[col].topline = true;
890 }
891
892
893 void handle_hline_below(RowInfo & ri, vector<CellInfo> & ci)
894 {
895         ri.bottomline = true;
896         for (size_t col = 0; col < ci.size(); ++col)
897                 ci[col].bottomline = true;
898 }
899
900
901 } // anonymous namespace
902
903
904 void handle_tabular(Parser & p, ostream & os, string const & name,
905                     string const & tabularwidth, string const & halign,
906                     Context & context)
907 {
908         bool const is_long_tabular(name == "longtable" || name == "xltabular");
909         bool booktabs = false;
910         string tabularvalignment("middle");
911         string posopts = p.getOpt();
912         if (!posopts.empty()) {
913                 if (posopts == "[t]")
914                         tabularvalignment = "top";
915                 else if (posopts == "[b]")
916                         tabularvalignment = "bottom";
917                 else
918                         cerr << "vertical tabular positioning '"
919                              << posopts << "' ignored\n";
920         }
921
922         vector<ColInfo> colinfo;
923
924         // handle column formatting
925         handle_colalign(p, colinfo, ColInfo());
926         fix_colalign(colinfo);
927
928         // first scan of cells
929         // use table mode to keep it minimal-invasive
930         // not exactly what's TeX doing...
931         vector<string> lines;
932         ostringstream ss;
933         RowPosition rowpos = ROW_START;
934         parse_table(p, ss, is_long_tabular, rowpos, FLAG_END);
935         split(ss.str(), lines, LINE);
936
937         vector< vector<CellInfo> > cellinfo(lines.size());
938         vector<RowInfo> rowinfo(lines.size());
939         ltType endfirsthead;
940         ltType endhead;
941         ltType endfoot;
942         ltType endlastfoot;
943
944         // split into rows
945         //cerr << "// split into rows\n";
946         for (size_t row = 0; row < rowinfo.size();) {
947
948                 // init row
949                 cellinfo[row].resize(colinfo.size());
950                 bool deletelastrow = false;
951
952                 // split row
953                 vector<string> dummy;
954                 //cerr << "\n########### LINE: " << lines[row] << "########\n";
955                 split(lines[row], dummy, HLINE);
956
957                 // handle horizontal line fragments
958                 // we do only expect this for a last line without '\\'
959                 if (dummy.size() != 3) {
960                         if ((dummy.size() != 1 && dummy.size() != 2) ||
961                             row != rowinfo.size() - 1)
962                                 cerr << "unexpected dummy size: " << dummy.size()
963                                         << " content: " << lines[row] << "\n";
964                         dummy.resize(3);
965                 }
966                 lines[row] = dummy[1];
967
968                 //cerr << "line: " << row << " above 0: " << dummy[0] << "\n";
969                 //cerr << "line: " << row << " below 2: " << dummy[2] <<  "\n";
970                 //cerr << "line: " << row << " cells 1: " << dummy[1] <<  "\n";
971
972                 for (int i = 0; i <= 2; i += 2) {
973                         //cerr << "   reading from line string '" << dummy[i] << "'\n";
974                         Parser p1(dummy[i]);
975                         while (p1.good()) {
976                                 Token t = p1.get_token();
977                                 //cerr << "read token: " << t << "\n";
978                                 if (t.cs() == "hline" || t.cs() == "toprule" ||
979                                     t.cs() == "midrule" ||
980                                     t.cs() == "bottomrule") {
981                                         if (t.cs() != "hline")
982                                                 booktabs = true;
983                                         if (i == 0) {
984                                                 if (rowinfo[row].topline) {
985                                                         if (row > 0) // extra bottomline above
986                                                                 handle_hline_below(rowinfo[row - 1], cellinfo[row - 1]);
987                                                         else
988                                                                 cerr << "dropping extra "
989                                                                      << t.cs() << '\n';
990                                                         //cerr << "below row: " << row-1 << endl;
991                                                 } else {
992                                                         handle_hline_above(rowinfo[row], cellinfo[row]);
993                                                         //cerr << "above row: " << row << endl;
994                                                 }
995                                         } else {
996                                                 //cerr << "below row: " << row << endl;
997                                                 handle_hline_below(rowinfo[row], cellinfo[row]);
998                                         }
999                                 } else if (t.cs() == "cline" || t.cs() == "cmidrule") {
1000                                         if (t.cs() == "cmidrule")
1001                                                 booktabs = true;
1002                                         string arg = p1.verbatim_item();
1003                                         //cerr << "read " << t.cs() << " arg: '" << arg << "'\n";
1004                                         vector<string> cols;
1005                                         split(arg, cols, '-');
1006                                         cols.resize(2);
1007                                         size_t from = convert<unsigned int>(cols[0]);
1008                                         if (from == 0)
1009                                                 cerr << "Could not parse "
1010                                                      << t.cs() << " start column."
1011                                                      << endl;
1012                                         else
1013                                                 // 1 based index -> 0 based
1014                                                 --from;
1015                                         if (from >= colinfo.size()) {
1016                                                 cerr << t.cs() << " starts at "
1017                                                         "non existing column "
1018                                                      << (from + 1) << endl;
1019                                                 from = colinfo.size() - 1;
1020                                         }
1021                                         size_t to = convert<unsigned int>(cols[1]);
1022                                         if (to == 0)
1023                                                 cerr << "Could not parse "
1024                                                      << t.cs() << " end column."
1025                                                      << endl;
1026                                         else
1027                                                 // 1 based index -> 0 based
1028                                                 --to;
1029                                         if (to >= colinfo.size()) {
1030                                                 cerr << t.cs() << " ends at "
1031                                                         "non existing column "
1032                                                      << (to + 1) << endl;
1033                                                 to = colinfo.size() - 1;
1034                                         }
1035                                         for (size_t col = from; col <= to; ++col) {
1036                                                 //cerr << "row: " << row << " col: " << col << " i: " << i << endl;
1037                                                 if (i == 0) {
1038                                                         rowinfo[row].topline = true;
1039                                                         cellinfo[row][col].topline = true;
1040                                                 } else {
1041                                                         rowinfo[row].bottomline = true;
1042                                                         cellinfo[row][col].bottomline = true;
1043                                                 }
1044                                         }
1045                                 } else if (t.cs() == "addlinespace") {
1046                                         booktabs = true;
1047                                         string const opt = p.next_token().cat() == catBegin ?
1048                                                         p.verbatim_item() : string();
1049                                         if (i == 0) {
1050                                                 if (opt.empty())
1051                                                         rowinfo[row].top_space = "default";
1052                                                 else
1053                                                         rowinfo[row].top_space = translate_len(opt);
1054                                         } else if (rowinfo[row].bottomline) {
1055                                                 if (opt.empty())
1056                                                         rowinfo[row].bottom_space = "default";
1057                                                 else
1058                                                         rowinfo[row].bottom_space = translate_len(opt);
1059                                         } else {
1060                                                 if (opt.empty())
1061                                                         rowinfo[row].interline_space = "default";
1062                                                 else
1063                                                         rowinfo[row].interline_space = translate_len(opt);
1064                                         }
1065                                 } else if (t.cs() == "endhead") {
1066                                         if (i == 0)
1067                                                 endhead.empty = true;
1068                                         else
1069                                                 rowinfo[row].type = LT_HEAD;
1070                                         for (int r = row - 1; r >= 0; --r) {
1071                                                 if (rowinfo[r].type != LT_NORMAL)
1072                                                         break;
1073                                                 rowinfo[r].type = LT_HEAD;
1074                                                 endhead.empty = false;
1075                                         }
1076                                         endhead.set = true;
1077                                 } else if (t.cs() == "endfirsthead") {
1078                                         if (i == 0)
1079                                                 endfirsthead.empty = true;
1080                                         else
1081                                                 rowinfo[row].type = LT_FIRSTHEAD;
1082                                         for (int r = row - 1; r >= 0; --r) {
1083                                                 if (rowinfo[r].type != LT_NORMAL)
1084                                                         break;
1085                                                 rowinfo[r].type = LT_FIRSTHEAD;
1086                                                 endfirsthead.empty = false;
1087                                         }
1088                                         endfirsthead.set = true;
1089                                 } else if (t.cs() == "endfoot") {
1090                                         if (i == 0)
1091                                                 endfoot.empty = true;
1092                                         else
1093                                                 rowinfo[row].type = LT_FOOT;
1094                                         for (int r = row - 1; r >= 0; --r) {
1095                                                 if (rowinfo[r].type != LT_NORMAL)
1096                                                         break;
1097                                                 rowinfo[r].type = LT_FOOT;
1098                                                 endfoot.empty = false;
1099                                         }
1100                                         endfoot.set = true;
1101                                 } else if (t.cs() == "endlastfoot") {
1102                                         if (i == 0)
1103                                                 endlastfoot.empty = true;
1104                                         else
1105                                                 rowinfo[row].type = LT_LASTFOOT;
1106                                         for (int r = row - 1; r >= 0; --r) {
1107                                                 if (rowinfo[r].type != LT_NORMAL)
1108                                                         break;
1109                                                 rowinfo[r].type = LT_LASTFOOT;
1110                                                 endlastfoot.empty = false;
1111                                         }
1112                                         endlastfoot.set = true;
1113                                 } else if (t.cs() == "newpage") {
1114                                         if (i == 0) {
1115                                                 if (row > 0)
1116                                                         rowinfo[row - 1].newpage = true;
1117                                                 else
1118                                                         // This does not work in LaTeX
1119                                                         cerr << "Ignoring "
1120                                                                 "'\\newpage' "
1121                                                                 "before rows."
1122                                                              << endl;
1123                                         } else
1124                                                 rowinfo[row].newpage = true;
1125                                 } else {
1126                                         cerr << "unexpected line token: " << t << endl;
1127                                 }
1128                         }
1129                 }
1130
1131                 // LyX ends headers and footers always with \tabularnewline.
1132                 // This causes one additional row in the output.
1133                 // If the last row of a header/footer is empty, we can work
1134                 // around that by removing it.
1135                 if (row > 1) {
1136                         RowInfo test = rowinfo[row-1];
1137                         test.type = LT_NORMAL;
1138                         if (lines[row-1].empty() && !test.special()) {
1139                                 switch (rowinfo[row-1].type) {
1140                                 case LT_FIRSTHEAD:
1141                                         if (rowinfo[row].type != LT_FIRSTHEAD &&
1142                                             rowinfo[row-2].type == LT_FIRSTHEAD)
1143                                                 deletelastrow = true;
1144                                         break;
1145                                 case LT_HEAD:
1146                                         if (rowinfo[row].type != LT_HEAD &&
1147                                             rowinfo[row-2].type == LT_HEAD)
1148                                                 deletelastrow = true;
1149                                         break;
1150                                 case LT_FOOT:
1151                                         if (rowinfo[row].type != LT_FOOT &&
1152                                             rowinfo[row-2].type == LT_FOOT)
1153                                                 deletelastrow = true;
1154                                         break;
1155                                 case LT_LASTFOOT:
1156                                         if (rowinfo[row].type != LT_LASTFOOT &&
1157                                             rowinfo[row-2].type == LT_LASTFOOT)
1158                                                 deletelastrow = true;
1159                                         break;
1160                                 case LT_NORMAL:
1161                                         break;
1162                                 }
1163                         }
1164                 }
1165
1166                 if (deletelastrow) {
1167                         lines.erase(lines.begin() + (row - 1));
1168                         rowinfo.erase(rowinfo.begin() + (row - 1));
1169                         cellinfo.erase(cellinfo.begin() + (row - 1));
1170                         continue;
1171                 }
1172
1173                 // split into cells
1174                 vector<string> cells;
1175                 split(lines[row], cells, TAB);
1176                 for (size_t col = 0, cell = 0; cell < cells.size();
1177                      ++col, ++cell) {
1178                         //cerr << "cell content: '" << cells[cell] << "'\n";
1179                         if (col >= colinfo.size()) {
1180                                 // This does not work in LaTeX
1181                                 cerr << "Ignoring extra cell '"
1182                                      << cells[cell] << "'." << endl;
1183                                 continue;
1184                         }
1185                         string cellcont = cells[cell];
1186                         // For decimal cells, ass the content of the second one to the first one
1187                         // of a pair.
1188                         if (colinfo[col].decimal_point != '\0' && colinfo[col].align == 'd' && cell < cells.size() - 1)
1189                                 cellcont += colinfo[col].decimal_point + cells[cell + 1];
1190                         Parser parse(cellcont);
1191                         parse.skip_spaces();
1192                         //cells[cell] << "'\n";
1193                         if (parse.next_token().cs() == "multirow") {
1194                                 // We do not support the vpos arg yet.
1195                                 if (parse.hasOpt()) {
1196                                         string const vpos = parse.getArg('[', ']');
1197                                         parse.skip_spaces(true);
1198                                         cerr << "Ignoring multirow's vpos arg '"
1199                                              << vpos << "'!" << endl;
1200                                 }
1201                                 // how many cells?
1202                                 parse.get_token();
1203                                 size_t const ncells =
1204                                         convert<unsigned int>(parse.verbatim_item());
1205                                 // We do not support the bigstrut arg yet.
1206                                 if (parse.hasOpt()) {
1207                                         string const bs = parse.getArg('[', ']');
1208                                         parse.skip_spaces(true);
1209                                         cerr << "Ignoring multirow's bigstrut arg '"
1210                                              << bs << "'!" << endl;
1211                                 }
1212                                 // the width argument
1213                                 string const width = parse.getArg('{', '}');
1214                                 // the vmove arg
1215                                 string vmove;
1216                                 if (parse.hasOpt()) {
1217                                         vmove = parse.getArg('[', ']');
1218                                         parse.skip_spaces(true);
1219                                 }
1220
1221                                 if (width != "*")
1222                                         colinfo[col].width = width;
1223                                 if (!vmove.empty())
1224                                         cellinfo[row][col].mroffset = vmove;
1225                                 cellinfo[row][col].multi = CELL_BEGIN_OF_MULTIROW;
1226                                 cellinfo[row][col].leftlines  = colinfo[col].leftlines;
1227                                 cellinfo[row][col].rightlines = colinfo[col].rightlines;
1228                                 cellinfo[row][col].mrxnum = ncells - 1;
1229
1230                                 ostringstream os2;
1231                                 parse_text_in_inset(parse, os2, FLAG_ITEM, false, context);
1232                                 if (!cellinfo[row][col].content.empty()) {
1233                                         // This may or may not work in LaTeX,
1234                                         // but it does not work in LyX.
1235                                         // FIXME: Handle it correctly!
1236                                         cerr << "Moving cell content '"
1237                                              << cells[cell]
1238                                              << "' into a multirow cell. "
1239                                                 "This will probably not work."
1240                                              << endl;
1241                                 }
1242                                 cellinfo[row][col].content += os2.str();
1243                         } else if (parse.next_token().cs() == "multicolumn") {
1244                                 // how many cells?
1245                                 parse.get_token();
1246                                 size_t const ncells =
1247                                         convert<unsigned int>(parse.verbatim_item());
1248
1249                                 // special cell properties alignment
1250                                 vector<ColInfo> t;
1251                                 handle_colalign(parse, t, ColInfo());
1252                                 parse.skip_spaces(true);
1253                                 ColInfo & ci = t.front();
1254
1255                                 // The logic of LyX for multicolumn vertical
1256                                 // lines is too complicated to reproduce it
1257                                 // here (see LyXTabular::TeXCellPreamble()).
1258                                 // Therefore we simply put everything in the
1259                                 // special field.
1260                                 ci2special(ci);
1261
1262                                 cellinfo[row][col].multi      = CELL_BEGIN_OF_MULTICOLUMN;
1263                                 cellinfo[row][col].align      = ci.align;
1264                                 cellinfo[row][col].special    = ci.special;
1265                                 cellinfo[row][col].leftlines  = ci.leftlines;
1266                                 cellinfo[row][col].rightlines = ci.rightlines;
1267                                 ostringstream os2;
1268                                 parse_text_in_inset(parse, os2, FLAG_ITEM, false, context);
1269                                 if (!cellinfo[row][col].content.empty()) {
1270                                         // This may or may not work in LaTeX,
1271                                         // but it does not work in LyX.
1272                                         // FIXME: Handle it correctly!
1273                                         cerr << "Moving cell content '"
1274                                              << cells[cell]
1275                                              << "' into a multicolumn cell. "
1276                                                 "This will probably not work."
1277                                              << endl;
1278                                 }
1279                                 cellinfo[row][col].content += os2.str();
1280
1281                                 // add dummy cells for multicol
1282                                 for (size_t i = 0; i < ncells - 1; ++i) {
1283                                         ++col;
1284                                         if (col >= colinfo.size()) {
1285                                                 cerr << "The cell '"
1286                                                         << cells[cell]
1287                                                         << "' specifies "
1288                                                         << convert<string>(ncells)
1289                                                         << " columns while the table has only "
1290                                                         << convert<string>(colinfo.size())
1291                                                         << " columns!"
1292                                                         << " Therefore the surplus columns will be ignored."
1293                                                         << endl;
1294                                                 break;
1295                                         }
1296                                         cellinfo[row][col].multi = CELL_PART_OF_MULTICOLUMN;
1297                                         cellinfo[row][col].align = 'c';
1298                                 }
1299
1300                         } else if (col == 0 && colinfo.size() > 1 &&
1301                                    is_long_tabular &&
1302                                    parse.next_token().cs() == "caption") {
1303                                 // longtable caption support in LyX is a hack:
1304                                 // Captions require a row of their own with
1305                                 // the caption flag set to true, having only
1306                                 // one multicolumn cell. The contents of that
1307                                 // cell must contain exactly one caption inset
1308                                 // and nothing else.
1309                                 // Fortunately, the caption flag is only needed
1310                                 // for tables with more than one column.
1311                                 rowinfo[row].caption = true;
1312                                 for (size_t c = 1; c < cells.size(); ++c) {
1313                                         if (!cells[c].empty()) {
1314                                                 cerr << "Moving cell content '"
1315                                                      << cells[c]
1316                                                      << "' into the caption cell. "
1317                                                         "This will probably not work."
1318                                                      << endl;
1319                                                 cells[0] += cells[c];
1320                                         }
1321                                 }
1322                                 cells.resize(1);
1323                                 cellinfo[row][col].align      = colinfo[col].align;
1324                                 cellinfo[row][col].multi      = CELL_BEGIN_OF_MULTICOLUMN;
1325                                 ostringstream os2;
1326                                 parse_text_in_inset(parse, os2, FLAG_CELL, false, context);
1327                                 cellinfo[row][col].content += os2.str();
1328                                 // add dummy multicolumn cells
1329                                 for (size_t c = 1; c < colinfo.size(); ++c)
1330                                         cellinfo[row][c].multi = CELL_PART_OF_MULTICOLUMN;
1331                         } else {
1332                                 bool turn = false;
1333                                 int rotate = 0;
1334                                 if (parse.next_token().cs() == "begin") {
1335                                         parse.pushPosition();
1336                                         parse.get_token();
1337                                         string const env = parse.getArg('{', '}');
1338                                         if (env == "sideways" || env == "turn") {
1339                                                 string angle = "90";
1340                                                 if (env == "turn") {
1341                                                         turn = true;
1342                                                         angle = parse.getArg('{', '}');
1343                                                 }
1344                                                 active_environments.push_back(env);
1345                                                 parse.ertEnvironment(env);
1346                                                 active_environments.pop_back();
1347                                                 parse.skip_spaces();
1348                                                 if (!parse.good() && support::isStrInt(angle))
1349                                                         rotate = convert<int>(angle);
1350                                         }
1351                                         parse.popPosition();
1352                                 }
1353                                 cellinfo[row][col].leftlines  = colinfo[col].leftlines;
1354                                 cellinfo[row][col].rightlines = colinfo[col].rightlines;
1355                                 cellinfo[row][col].align      = colinfo[col].align;
1356                                 ostringstream os2;
1357                                 if (rotate != 0) {
1358                                         cellinfo[row][col].rotate = rotate;
1359                                         parse.get_token();
1360                                         active_environments.push_back(parse.getArg('{', '}'));
1361                                         if (turn)
1362                                                 parse.getArg('{', '}');
1363                                         parse_text_in_inset(parse, os2, FLAG_END, false, context);
1364                                         active_environments.pop_back();
1365                                         preamble.registerAutomaticallyLoadedPackage("rotating");
1366                                 } else {
1367                                         parse_text_in_inset(parse, os2, FLAG_CELL, false, context);
1368                                 }
1369                                 cellinfo[row][col].content += os2.str();
1370                         }
1371                 }
1372
1373                 //cerr << "//  handle almost empty last row what we have\n";
1374                 // handle almost empty last row
1375                 if (row && lines[row].empty() && row + 1 == rowinfo.size()) {
1376                         //cerr << "remove empty last line\n";
1377                         if (rowinfo[row].topline)
1378                                 rowinfo[row - 1].bottomline = true;
1379                         for (size_t col = 0; col < colinfo.size(); ++col)
1380                                 if (cellinfo[row][col].topline)
1381                                         cellinfo[row - 1][col].bottomline = true;
1382                         rowinfo.pop_back();
1383                 }
1384
1385                 ++row;
1386         }
1387
1388         // Now we have the table structure and content in rowinfo, colinfo
1389         // and cellinfo.
1390         // Unfortunately LyX has some limitations that we need to work around.
1391
1392         // Some post work
1393         for (size_t row = 0; row < rowinfo.size(); ++row) {
1394                 for (size_t col = 0; col < cellinfo[row].size(); ++col) {
1395                         // Convert cells with special content to multicolumn cells
1396                         // (LyX ignores the special field for non-multicolumn cells).
1397                         if (cellinfo[row][col].multi == CELL_NORMAL &&
1398                             !cellinfo[row][col].special.empty())
1399                                 cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN;
1400                         // Add multirow dummy cells
1401                         if (row > 1 && (cellinfo[row - 1][col].multi == CELL_PART_OF_MULTIROW
1402                                         || cellinfo[row - 1][col].multi == CELL_BEGIN_OF_MULTIROW)
1403                                     && cellinfo[row - 1][col].mrxnum > 0) {
1404                                 // add dummy cells for multirow
1405                                 cellinfo[row][col].multi = CELL_PART_OF_MULTIROW;
1406                                 cellinfo[row][col].align = 'c';
1407                                 cellinfo[row][col].mrxnum = cellinfo[row - 1][col].mrxnum - 1;
1408                         }
1409                 }
1410         }
1411
1412         // Distribute lines from rows/columns to cells
1413         // The code was stolen from convert_tablines() in lyx2lyx/lyx_1_6.py.
1414         // Each standard cell inherits the settings of the corresponding
1415         // rowinfo/colinfo. This works because all cells with individual
1416         // settings were converted to multicolumn cells above.
1417         // Each multicolumn cell inherits the settings of the rowinfo/colinfo
1418         // corresponding to the first column of the multicolumn cell (default
1419         // of the multicol package). This works because the special field
1420         // overrides the line fields.
1421         for (size_t row = 0; row < rowinfo.size(); ++row) {
1422                 for (size_t col = 0; col < cellinfo[row].size(); ++col) {
1423                         if (cellinfo[row][col].multi == CELL_NORMAL) {
1424                                 cellinfo[row][col].topline = rowinfo[row].topline;
1425                                 cellinfo[row][col].bottomline = rowinfo[row].bottomline;
1426                                 cellinfo[row][col].leftlines = colinfo[col].leftlines;
1427                                 cellinfo[row][col].rightlines = colinfo[col].rightlines;
1428                         } else if (cellinfo[row][col].multi == CELL_BEGIN_OF_MULTICOLUMN) {
1429                                 size_t s = col + 1;
1430                                 while (s < cellinfo[row].size() &&
1431                                        cellinfo[row][s].multi == CELL_PART_OF_MULTICOLUMN)
1432                                         s++;
1433                                 if (s < cellinfo[row].size() &&
1434                                     cellinfo[row][s].multi != CELL_BEGIN_OF_MULTICOLUMN)
1435                                         cellinfo[row][col].rightlines = colinfo[col].rightlines;
1436                                 if (col > 0 && cellinfo[row][col-1].multi == CELL_NORMAL)
1437                                         cellinfo[row][col].leftlines = colinfo[col].leftlines;
1438                         } else if (cellinfo[row][col].multi == CELL_BEGIN_OF_MULTIROW) {
1439                                 size_t s = row + 1;
1440                                 while (s < rowinfo.size() &&
1441                                        cellinfo[s][col].multi == CELL_PART_OF_MULTIROW)
1442                                         s++;
1443                                 if (s < cellinfo[row].size() &&
1444                                     cellinfo[s][col].multi != CELL_BEGIN_OF_MULTIROW)
1445                                         cellinfo[row][col].bottomline = rowinfo[row].bottomline;
1446                                 if (row > 0 && cellinfo[row - 1][col].multi == CELL_NORMAL)
1447                                         cellinfo[row][col].topline = rowinfo[row].topline;
1448                         }
1449                 }
1450         }
1451
1452         if (booktabs)
1453                 preamble.registerAutomaticallyLoadedPackage("booktabs");
1454         if (name == "longtable")
1455                 preamble.registerAutomaticallyLoadedPackage("longtable");
1456         else if (name == "xltabular")
1457                 preamble.registerAutomaticallyLoadedPackage("xltabular");
1458         else if (name == "tabularx")
1459                 preamble.registerAutomaticallyLoadedPackage("tabularx");
1460
1461         //cerr << "// output what we have\n";
1462         // output what we have
1463         size_type cols = colinfo.size();
1464         for (size_t col = 0; col < colinfo.size(); ++col) {
1465                 if (colinfo[col].decimal_point != '\0' && colinfo[col].align != 'd')
1466                         --cols;
1467         }
1468         os << "\n<lyxtabular version=\"3\" rows=\"" << rowinfo.size()
1469            << "\" columns=\"" << cols << "\">\n";
1470         os << "<features"
1471            << write_attribute("rotate", context.tablerotation)
1472            << write_attribute("booktabs", booktabs)
1473            << write_attribute("islongtable", is_long_tabular);
1474         if (is_long_tabular) {
1475                 os << write_attribute("firstHeadTopDL", endfirsthead.topDL)
1476                    << write_attribute("firstHeadBottomDL", endfirsthead.bottomDL)
1477                    << write_attribute("firstHeadEmpty", endfirsthead.empty)
1478                    << write_attribute("headTopDL", endhead.topDL)
1479                    << write_attribute("headBottomDL", endhead.bottomDL)
1480                    << write_attribute("footTopDL", endfoot.topDL)
1481                    << write_attribute("footBottomDL", endfoot.bottomDL)
1482                    << write_attribute("lastFootTopDL", endlastfoot.topDL)
1483                    << write_attribute("lastFootBottomDL", endlastfoot.bottomDL)
1484                    << write_attribute("lastFootEmpty", endlastfoot.empty);
1485                 if (!halign.empty())
1486                         os << write_attribute("longtabularalignment", halign);
1487         } else
1488                 os << write_attribute("tabularvalignment", tabularvalignment);
1489                    
1490         os << write_attribute("tabularwidth", tabularwidth) << ">\n";
1491
1492         //cerr << "// after header\n";
1493         for (size_t col = 0; col < colinfo.size(); ++col) {
1494                 if (colinfo[col].decimal_point != '\0' && colinfo[col].align != 'd')
1495                         continue;
1496                 os << "<column alignment=\""
1497                            << verbose_align(colinfo[col].align) << "\"";
1498                 if (colinfo[col].decimal_point != '\0')
1499                         os << " decimal_point=\"" << colinfo[col].decimal_point << "\"";
1500                 os << " valignment=\""
1501                    << verbose_valign(colinfo[col].valign) << "\""
1502                    << write_attribute("width", translate_len(colinfo[col].width))
1503                    << write_attribute("special", colinfo[col].special)
1504                    << write_attribute("varwidth", colinfo[col].varwidth)
1505                    << ">\n";
1506         }
1507         //cerr << "// after cols\n";
1508
1509         for (size_t row = 0; row < rowinfo.size(); ++row) {
1510                 os << "<row"
1511                    << write_attribute("topspace", rowinfo[row].top_space)
1512                    << write_attribute("bottomspace", rowinfo[row].bottom_space)
1513                    << write_attribute("interlinespace", rowinfo[row].interline_space)
1514                    << write_attribute("endhead",
1515                                       rowinfo[row].type == LT_HEAD)
1516                    << write_attribute("endfirsthead",
1517                                       rowinfo[row].type == LT_FIRSTHEAD)
1518                    << write_attribute("endfoot",
1519                                       rowinfo[row].type == LT_FOOT)
1520                    << write_attribute("endlastfoot",
1521                                       rowinfo[row].type == LT_LASTFOOT)
1522                    << write_attribute("newpage", rowinfo[row].newpage)
1523                    << write_attribute("caption", rowinfo[row].caption)
1524                    << ">\n";
1525                 for (size_t col = 0; col < colinfo.size(); ++col) {
1526                         CellInfo const & cell = cellinfo[row][col];
1527                         if (colinfo[col].decimal_point != '\0' && colinfo[col].align != 'd')
1528                                 // These are the second columns in a salign pair. Skip.
1529                                 continue;
1530                         os << "<cell";
1531                         if ((cell.multi == CELL_BEGIN_OF_MULTICOLUMN
1532                                     || cell.multi == CELL_PART_OF_MULTICOLUMN)
1533                                    && colinfo[col].align != 'd')
1534                                 os << " multicolumn=\"" << cell.multi << "\"";
1535                         if (cell.multi == CELL_BEGIN_OF_MULTIROW
1536                             || cell.multi == CELL_PART_OF_MULTIROW)
1537                                 os << " multirow=\"" << cell.multi << "\"";
1538                         os << " alignment=\"" << verbose_align(cell.align)
1539                            << "\""
1540                            << " valignment=\"" << verbose_valign(cell.valign)
1541                            << "\""
1542                            << write_attribute("topline", cell.topline)
1543                            << write_attribute("bottomline", cell.bottomline)
1544                            << write_attribute("leftline", cell.leftlines > 0)
1545                            << write_attribute("rightline", cell.rightlines > 0)
1546                            << write_attribute("rotate", cell.rotate)
1547                            << write_attribute("mroffset", cell.mroffset);
1548                         //cerr << "\nrow: " << row << " col: " << col;
1549                         //if (cell.topline)
1550                         //      cerr << " topline=\"true\"";
1551                         //if (cell.bottomline)
1552                         //      cerr << " bottomline=\"true\"";
1553                         os << " usebox=\"none\""
1554                            << write_attribute("width", translate_len(cell.width));
1555                         if (cell.multi != CELL_NORMAL)
1556                                 os << write_attribute("special", cell.special);
1557                         os << ">"
1558                            << "\n\\begin_inset Text\n"
1559                            << cell.content
1560                            << "\n\\end_inset\n"
1561                            << "</cell>\n";
1562                 }
1563                 os << "</row>\n";
1564         }
1565
1566         os << "</lyxtabular>\n";
1567 }
1568
1569
1570
1571
1572 // }])
1573
1574
1575 } // namespace lyx