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