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