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