3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Jean-Marc Lasgouttes
10 * Full author contact details are available in file CREDITS.
21 #include "support/lassert.h"
22 #include "support/convert.h"
23 #include "support/lstrings.h"
34 // filled in preamble.cpp
35 map<char, int> special_columns;
42 ColInfo() : align('n'), valign('n'), rightlines(0), leftlines(0) {}
45 /// vertical alignment
49 /// special column alignment
51 /// number of lines on the right
53 /// number of lines on the left
58 /// row type for longtables
65 /// part of head on first page
69 /// part of foot on last page
76 RowInfo() : topline(false), bottomline(false), type(LT_NORMAL),
77 caption(false), newpage(false) {}
78 /// Does this row have any special setting?
81 return topline || bottomline || !top_space.empty() ||
82 !bottom_space.empty() || !interline_space.empty() ||
83 type != LT_NORMAL || caption || newpage;
85 /// horizontal line above
87 /// horizontal line below
89 /// Extra space between the top line and this row
91 /// Extra space between this row and the bottom line
93 /// Extra space between the bottom line and the next top line
94 string interline_space;
95 /// These are for longtabulars only
96 /// row type (head, foot, firsthead etc.)
100 /// row for a newpage
105 /// the numeric values are part of the file format!
109 /// A multicolumn cell. The number of columns is <tt>1 + number
110 /// of CELL_PART_OF_MULTICOLUMN cells</tt> that follow directly
111 CELL_BEGIN_OF_MULTICOLUMN = 1,
112 /// This is a dummy cell (part of a multicolumn cell)
113 CELL_PART_OF_MULTICOLUMN = 2
119 CellInfo() : multi(CELL_NORMAL), align('n'), valign('n'),
120 leftlines(0), rightlines(0), topline(false),
121 bottomline(false), rotate(0) {}
128 /// vertical cell alignment
130 /// number of lines on the left
132 /// number of lines on the right
134 /// do we have a line above?
136 /// do we have a line below?
138 /// how is the cell rotated?
140 /// width for multicolumn cells
142 /// special formatting for multicolumn cells
150 ltType() : topDL(false), bottomDL(false), empty(false) {}
151 // we have this header type (is set in the getLT... functions)
153 // double borders on top
155 // double borders on bottom
157 // used for FirstHeader & LastFooter and if this is true
158 // all the rows marked as FirstHeader or LastFooter are
159 // ignored in the output and it is set to be empty!
164 /// translate a horizontal alignment (as stored in ColInfo and CellInfo) to LyX
165 inline char const * verbose_align(char c)
180 /// translate a vertical alignment (as stored in ColInfo and CellInfo) to LyX
181 inline char const * verbose_valign(char c)
183 // The default value for no special alignment is "top".
196 // stripped down from tabluar.C. We use it currently only for bools and
198 string const write_attribute(string const & name, bool const & b)
200 // we write only true attribute values so we remove a bit of the
201 // file format bloat for tabulars.
202 return b ? ' ' + name + "=\"true\"" : string();
206 string const write_attribute(string const & name, string const & s)
208 return s.empty() ? string() : ' ' + name + "=\"" + s + '"';
212 string const write_attribute(string const & name, int const & i)
214 // we write only true attribute values so we remove a bit of the
215 // file format bloat for tabulars.
216 return i ? write_attribute(name, convert<string>(i)) : string();
220 /*! rather brutish way to code table structure in a string:
225 \multicolumn{2}{c}{4} & 5 //
231 gets "translated" to:
234 HLINE 1 TAB 2 TAB 3 HLINE HLINE LINE
235 \hline HLINE \multicolumn{2}{c}{4} TAB 5 HLINE HLINE LINE
236 HLINE 6 TAB 7 HLINE HLINE LINE
237 HLINE 8 HLINE \endhead HLINE LINE
241 char const TAB = '\001';
242 char const LINE = '\002';
243 char const HLINE = '\004';
247 * Move the information in leftlines, rightlines, align and valign to the
248 * special field. This is necessary if the special field is not empty,
249 * because LyX ignores leftlines > 1, rightlines > 1, align and valign in
252 void ci2special(ColInfo & ci)
254 if (ci.width.empty() && ci.align == 'n')
255 // The alignment setting is already in special, since
256 // handle_colalign() never stores ci with these settings
257 // and ensures that leftlines == 0 and rightlines == 0 in
261 if (!ci.width.empty()) {
264 ci.special += ">{\\raggedright}";
267 ci.special += ">{\\raggedleft}";
270 ci.special += ">{\\centering}";
273 if (ci.valign == 'n')
276 ci.special += ci.valign;
277 ci.special += '{' + ci.width + '}';
280 ci.special += ci.align;
282 // LyX can only have one left and one right line.
283 for (int i = 1; i < ci.leftlines; ++i)
284 ci.special.insert(0, "|");
285 for (int i = 1; i < ci.rightlines; ++i)
287 ci.leftlines = min(ci.leftlines, 1);
288 ci.rightlines = min(ci.rightlines, 1);
295 * Handle column specifications for tabulars and multicolumns.
296 * The next token of the parser \p p must be an opening brace, and we read
297 * everything until the matching closing brace.
298 * The resulting column specifications are filled into \p colinfo. This is
299 * in an intermediate form. fix_colalign() makes it suitable for LyX output.
301 void handle_colalign(Parser & p, vector<ColInfo> & colinfo,
302 ColInfo const & start)
304 if (p.get_token().cat() != catBegin)
305 cerr << "Wrong syntax for table column alignment.\n"
306 "Expected '{', got '" << p.curr_token().asInput()
309 ColInfo next = start;
310 for (Token t = p.get_token(); p.good() && t.cat() != catEnd;
313 cerr << "t: " << t << " c: '" << t.character() << "'\n";
316 // We cannot handle comments here
317 if (t.cat() == catComment) {
318 if (t.cs().empty()) {
322 cerr << "Ignoring comment: " << t.asInput();
326 switch (t.character()) {
330 // new column, horizontal aligned
331 next.align = t.character();
332 if (!next.special.empty())
334 colinfo.push_back(next);
340 // new column, vertical aligned box
341 next.valign = t.character();
342 next.width = p.verbatim_item();
343 if (!next.special.empty())
345 colinfo.push_back(next);
350 if (colinfo.empty()) {
351 if (next.special.empty())
355 } else if (colinfo.back().special.empty())
356 ++colinfo.back().rightlines;
357 else if (next.special.empty())
360 colinfo.back().special += '|';
363 // text before the next column
364 string const s = trimSpaceAndEol(p.verbatim_item());
365 if (next.special.empty() &&
367 // Maybe this can be converted to a
368 // horizontal alignment setting for
369 // fixed width columns
370 if (s == "\\raggedleft")
372 else if (s == "\\raggedright")
374 else if (s == "\\centering")
377 next.special = ">{" + s + '}';
379 next.special += ">{" + s + '}';
383 // text after the last column
384 string const s = trimSpaceAndEol(p.verbatim_item());
386 // This is not possible in LaTeX.
387 cerr << "Ignoring separator '<{"
388 << s << "}'." << endl;
390 ColInfo & ci = colinfo.back();
392 ci.special += "<{" + s + '}';
397 // *{n}{arg} means 'n' columns of type 'arg'
398 string const num = p.verbatim_item();
399 string const arg = p.verbatim_item();
400 size_t const n = convert<unsigned int>(num);
401 if (!arg.empty() && n > 0) {
403 for (size_t i = 0; i < n; ++i)
407 handle_colalign(p2, colinfo, next);
410 cerr << "Ignoring column specification"
411 " '*{" << num << "}{"
412 << arg << "}'." << endl;
417 // text instead of the column spacing
419 // text in addition to the column spacing
420 next.special += t.character();
421 next.special += '{' + p.verbatim_item() + '}';
424 // try user defined column types
425 if (special_columns.find(t.character()) !=
426 special_columns.end()) {
428 next.special += t.character();
430 special_columns[t.character()];
431 for (int i = 0; i < nargs; ++i)
432 next.special += '{' +
435 colinfo.push_back(next);
438 cerr << "Ignoring column specification"
439 " '" << t << "'." << endl;
444 // Maybe we have some column separators that need to be added to the
447 if (!next.special.empty()) {
448 ColInfo & ci = colinfo.back();
450 ci.special += next.special;
451 next.special.erase();
457 * Move the left and right lines and alignment settings of the column \p ci
458 * to the special field if necessary.
460 void fix_colalign(ColInfo & ci)
462 if (ci.leftlines > 1 || ci.rightlines > 1)
468 * LyX can't handle more than one vertical line at the left or right side
470 * This function moves the left and right lines and alignment settings of all
471 * columns in \p colinfo to the special field if necessary.
473 void fix_colalign(vector<ColInfo> & colinfo)
475 // Try to move extra leftlines to the previous column.
476 // We do this only if both special fields are empty, otherwise we
477 // can't tell wether the result will be the same.
478 for (size_t col = 0; col < colinfo.size(); ++col) {
479 if (colinfo[col].leftlines > 1 &&
480 colinfo[col].special.empty() && col > 0 &&
481 colinfo[col - 1].rightlines == 0 &&
482 colinfo[col - 1].special.empty()) {
483 ++colinfo[col - 1].rightlines;
484 --colinfo[col].leftlines;
487 // Try to move extra rightlines to the next column
488 for (size_t col = 0; col < colinfo.size(); ++col) {
489 if (colinfo[col].rightlines > 1 &&
490 colinfo[col].special.empty() &&
491 col < colinfo.size() - 1 &&
492 colinfo[col + 1].leftlines == 0 &&
493 colinfo[col + 1].special.empty()) {
494 ++colinfo[col + 1].leftlines;
495 --colinfo[col].rightlines;
498 // Move the lines and alignment settings to the special field if
500 for (size_t col = 0; col < colinfo.size(); ++col)
501 fix_colalign(colinfo[col]);
506 * Parse hlines and similar stuff.
507 * \returns wether the token \p t was parsed
509 bool parse_hlines(Parser & p, Token const & t, string & hlines,
510 bool is_long_tabular)
512 LASSERT(t.cat() == catEscape, return false);
514 if (t.cs() == "hline" || t.cs() == "toprule" || t.cs() == "midrule" ||
515 t.cs() == "bottomrule")
516 hlines += '\\' + t.cs();
518 else if (t.cs() == "cline")
519 hlines += "\\cline{" + p.verbatim_item() + '}';
521 else if (t.cs() == "cmidrule") {
522 // We cannot handle the \cmidrule(l){3-4} form
525 bool const hasParentheses(p.getFullArg('(', ')').first);
529 hlines += "\\cmidrule{" + p.verbatim_item() + '}';
532 else if (t.cs() == "addlinespace") {
535 bool const hasArgument(p.getFullArg('{', '}').first);
538 hlines += "\\addlinespace{" + p.verbatim_item() + '}';
540 hlines += "\\addlinespace";
543 else if (is_long_tabular && t.cs() == "newpage")
544 hlines += "\\newpage";
553 /// Position in a row
555 /// At the very beginning, before the first token
557 /// After the first token and before any column token
559 /// After the first column token. Comments and whitespace are only
560 /// treated as tokens in this position
562 /// After the first non-column token at the end
568 * Parse table structure.
569 * We parse tables in a two-pass process: This function extracts the table
570 * structure (rows, columns, hlines etc.), but does not change the cell
571 * content. The cell content is parsed in a second step in handle_tabular().
573 void parse_table(Parser & p, ostream & os, bool is_long_tabular,
574 RowPosition & pos, unsigned flags)
576 // table structure commands such as \hline
579 // comments that occur at places where we can't handle them
583 Token const & t = p.get_token();
586 debugToken(cerr, t, flags);
589 // comments and whitespace in hlines
592 case IN_HLINES_START:
594 if (t.cat() == catComment) {
599 // We can't handle comments here,
600 // store them for later use
601 comments += t.asInput();
603 } else if (t.cat() == catSpace ||
604 t.cat() == catNewline) {
605 // whitespace is irrelevant here, we
606 // need to recognize hline stuff
615 // We need to handle structure stuff first in order to
616 // determine wether we need to output a HLINE separator
617 // before the row or not.
618 if (t.cat() == catEscape) {
619 if (parse_hlines(p, t, hlines, is_long_tabular)) {
622 pos = IN_HLINES_START;
627 case IN_HLINES_START:
634 else if (t.cs() == "tabularnewline" ||
638 cerr << "Warning: Converting TeX "
639 "'\\cr' to LaTeX '\\\\'."
641 // stuff before the line break
642 os << comments << HLINE << hlines << HLINE
644 //cerr << "hlines: " << hlines << endl;
651 else if (is_long_tabular &&
652 (t.cs() == "endhead" ||
653 t.cs() == "endfirsthead" ||
654 t.cs() == "endfoot" ||
655 t.cs() == "endlastfoot")) {
656 hlines += t.asInput();
660 // these commands are implicit line
662 os << comments << HLINE << hlines
669 pos = IN_HLINES_START;
671 case IN_HLINES_START:
678 // We need a HLINE separator if we either have no hline
679 // stuff at all and are just starting a row or if we just
680 // got the first non-hline token.
683 // no hline tokens exist, first token at row start
684 case IN_HLINES_START:
685 // hline tokens exist, first non-hline token at row
687 os << hlines << HLINE << comments;
693 // Oops, there is still cell content or unsupported
694 // booktabs commands after hline stuff. The latter are
695 // moved to the cell, and the first does not work in
696 // LaTeX, so we ignore the hlines.
699 if (support::contains(hlines, "\\hline") ||
700 support::contains(hlines, "\\cline") ||
701 support::contains(hlines, "\\newpage"))
702 cerr << "Ignoring '" << hlines
703 << "' in a cell" << endl;
713 // If we come here we have normal cell content
717 if (t.cat() == catMath) {
718 // we are inside some text mode thingy, so opening new math is allowed
719 Token const & n = p.get_token();
720 if (n.cat() == catMath) {
721 // TeX's $$...$$ syntax for displayed math
723 // This does only work because parse_math outputs TeX
724 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
726 p.get_token(); // skip the second '$' token
728 // simple $...$ stuff
731 // This does only work because parse_math outputs TeX
732 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
737 else if (t.cat() == catSpace
738 || t.cat() == catNewline
739 || t.cat() == catLetter
740 || t.cat() == catSuper
742 || t.cat() == catOther
743 || t.cat() == catActive
744 || t.cat() == catParameter)
747 else if (t.cat() == catBegin) {
749 parse_table(p, os, is_long_tabular, pos,
754 else if (t.cat() == catEnd) {
755 if (flags & FLAG_BRACE_LAST)
757 cerr << "unexpected '}'\n";
760 else if (t.cat() == catAlign) {
765 else if (t.cat() == catComment)
768 else if (t.cs() == "(") {
770 // This does only work because parse_math outputs TeX
771 parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
775 else if (t.cs() == "[") {
777 // This does only work because parse_math outputs TeX
778 parse_math(p, os, FLAG_EQUATION, MATH_MODE);
782 else if (t.cs() == "begin") {
783 string const name = p.getArg('{', '}');
784 active_environments.push_back(name);
785 os << "\\begin{" << name << '}';
786 // treat the nested environment as a block, don't
787 // parse &, \\ etc, because they don't belong to our
788 // table if they appear.
789 os << p.ertEnvironment(name);
790 os << "\\end{" << name << '}';
791 active_environments.pop_back();
794 else if (t.cs() == "end") {
795 if (flags & FLAG_END) {
796 // eat environment name
797 string const name = p.getArg('{', '}');
798 if (name != active_environment())
799 p.error("\\end{" + name + "} does not match \\begin{"
800 + active_environment() + "}");
803 p.error("found 'end' unexpectedly");
810 // We can have comments if the last line is incomplete
813 // We can have hline stuff if the last line is incomplete
814 if (!hlines.empty()) {
815 // this does not work in LaTeX, so we ignore it
816 cerr << "Ignoring '" << hlines << "' at end of tabular"
822 void handle_hline_above(RowInfo & ri, vector<CellInfo> & ci)
825 for (size_t col = 0; col < ci.size(); ++col)
826 ci[col].topline = true;
830 void handle_hline_below(RowInfo & ri, vector<CellInfo> & ci)
832 ri.bottomline = true;
833 for (size_t col = 0; col < ci.size(); ++col)
834 ci[col].bottomline = true;
838 } // anonymous namespace
841 void handle_tabular(Parser & p, ostream & os, string const & name,
842 string const & tabularwidth, Context & context)
844 bool const is_long_tabular(name == "longtable");
845 bool booktabs = false;
846 string tabularvalignment("middle");
847 string posopts = p.getOpt();
848 if (!posopts.empty()) {
849 // FIXME: Convert this to ERT
851 cerr << "horizontal longtable positioning '"
852 << posopts << "' ignored\n";
853 else if (posopts == "[t]")
854 tabularvalignment = "top";
855 else if (posopts == "[b]")
856 tabularvalignment = "bottom";
858 cerr << "vertical tabular positioning '"
859 << posopts << "' ignored\n";
862 vector<ColInfo> colinfo;
864 // handle column formatting
865 handle_colalign(p, colinfo, ColInfo());
866 fix_colalign(colinfo);
868 // first scan of cells
869 // use table mode to keep it minimal-invasive
870 // not exactly what's TeX doing...
871 vector<string> lines;
873 RowPosition rowpos = ROW_START;
874 parse_table(p, ss, is_long_tabular, rowpos, FLAG_END);
875 split(ss.str(), lines, LINE);
877 vector< vector<CellInfo> > cellinfo(lines.size());
878 vector<RowInfo> rowinfo(lines.size());
885 //cerr << "// split into rows\n";
886 for (size_t row = 0; row < rowinfo.size();) {
889 cellinfo[row].resize(colinfo.size());
890 bool deletelastrow = false;
893 vector<string> dummy;
894 //cerr << "\n########### LINE: " << lines[row] << "########\n";
895 split(lines[row], dummy, HLINE);
897 // handle horizontal line fragments
898 // we do only expect this for a last line without '\\'
899 if (dummy.size() != 3) {
900 if ((dummy.size() != 1 && dummy.size() != 2) ||
901 row != rowinfo.size() - 1)
902 cerr << "unexpected dummy size: " << dummy.size()
903 << " content: " << lines[row] << "\n";
906 lines[row] = dummy[1];
908 //cerr << "line: " << row << " above 0: " << dummy[0] << "\n";
909 //cerr << "line: " << row << " below 2: " << dummy[2] << "\n";
910 //cerr << "line: " << row << " cells 1: " << dummy[1] << "\n";
912 for (int i = 0; i <= 2; i += 2) {
913 //cerr << " reading from line string '" << dummy[i] << "'\n";
916 Token t = p1.get_token();
917 //cerr << "read token: " << t << "\n";
918 if (t.cs() == "hline" || t.cs() == "toprule" ||
919 t.cs() == "midrule" ||
920 t.cs() == "bottomrule") {
921 if (t.cs() != "hline")
924 if (rowinfo[row].topline) {
925 if (row > 0) // extra bottomline above
926 handle_hline_below(rowinfo[row - 1], cellinfo[row - 1]);
928 cerr << "dropping extra "
930 //cerr << "below row: " << row-1 << endl;
932 handle_hline_above(rowinfo[row], cellinfo[row]);
933 //cerr << "above row: " << row << endl;
936 //cerr << "below row: " << row << endl;
937 handle_hline_below(rowinfo[row], cellinfo[row]);
939 } else if (t.cs() == "cline" || t.cs() == "cmidrule") {
940 if (t.cs() == "cmidrule")
942 string arg = p1.verbatim_item();
943 //cerr << "read " << t.cs() << " arg: '" << arg << "'\n";
945 split(arg, cols, '-');
947 size_t from = convert<unsigned int>(cols[0]);
949 cerr << "Could not parse "
950 << t.cs() << " start column."
953 // 1 based index -> 0 based
955 if (from >= colinfo.size()) {
956 cerr << t.cs() << " starts at "
957 "non existing column "
958 << (from + 1) << endl;
959 from = colinfo.size() - 1;
961 size_t to = convert<unsigned int>(cols[1]);
963 cerr << "Could not parse "
964 << t.cs() << " end column."
967 // 1 based index -> 0 based
969 if (to >= colinfo.size()) {
970 cerr << t.cs() << " ends at "
971 "non existing column "
973 to = colinfo.size() - 1;
975 for (size_t col = from; col <= to; ++col) {
976 //cerr << "row: " << row << " col: " << col << " i: " << i << endl;
978 rowinfo[row].topline = true;
979 cellinfo[row][col].topline = true;
981 rowinfo[row].bottomline = true;
982 cellinfo[row][col].bottomline = true;
985 } else if (t.cs() == "addlinespace") {
987 string const opt = p.next_token().cat() == catBegin ?
988 p.verbatim_item() : string();
991 rowinfo[row].top_space = "default";
993 rowinfo[row].top_space = translate_len(opt);
994 } else if (rowinfo[row].bottomline) {
996 rowinfo[row].bottom_space = "default";
998 rowinfo[row].bottom_space = translate_len(opt);
1001 rowinfo[row].interline_space = "default";
1003 rowinfo[row].interline_space = translate_len(opt);
1005 } else if (t.cs() == "endhead") {
1007 endhead.empty = true;
1009 rowinfo[row].type = LT_HEAD;
1010 for (int r = row - 1; r >= 0; --r) {
1011 if (rowinfo[r].type != LT_NORMAL)
1013 rowinfo[r].type = LT_HEAD;
1014 endhead.empty = false;
1017 } else if (t.cs() == "endfirsthead") {
1019 endfirsthead.empty = true;
1021 rowinfo[row].type = LT_FIRSTHEAD;
1022 for (int r = row - 1; r >= 0; --r) {
1023 if (rowinfo[r].type != LT_NORMAL)
1025 rowinfo[r].type = LT_FIRSTHEAD;
1026 endfirsthead.empty = false;
1028 endfirsthead.set = true;
1029 } else if (t.cs() == "endfoot") {
1031 endfoot.empty = true;
1033 rowinfo[row].type = LT_FOOT;
1034 for (int r = row - 1; r >= 0; --r) {
1035 if (rowinfo[r].type != LT_NORMAL)
1037 rowinfo[r].type = LT_FOOT;
1038 endfoot.empty = false;
1041 } else if (t.cs() == "endlastfoot") {
1043 endlastfoot.empty = true;
1045 rowinfo[row].type = LT_LASTFOOT;
1046 for (int r = row - 1; r >= 0; --r) {
1047 if (rowinfo[r].type != LT_NORMAL)
1049 rowinfo[r].type = LT_LASTFOOT;
1050 endlastfoot.empty = false;
1052 endlastfoot.set = true;
1053 } else if (t.cs() == "newpage") {
1056 rowinfo[row - 1].newpage = true;
1058 // This does not work in LaTeX
1064 rowinfo[row].newpage = true;
1066 cerr << "unexpected line token: " << t << endl;
1071 // LyX ends headers and footers always with \tabularnewline.
1072 // This causes one additional row in the output.
1073 // If the last row of a header/footer is empty, we can work
1074 // around that by removing it.
1076 RowInfo test = rowinfo[row-1];
1077 test.type = LT_NORMAL;
1078 if (lines[row-1].empty() && !test.special()) {
1079 switch (rowinfo[row-1].type) {
1081 if (rowinfo[row].type != LT_FIRSTHEAD &&
1082 rowinfo[row-2].type == LT_FIRSTHEAD)
1083 deletelastrow = true;
1086 if (rowinfo[row].type != LT_HEAD &&
1087 rowinfo[row-2].type == LT_HEAD)
1088 deletelastrow = true;
1091 if (rowinfo[row].type != LT_FOOT &&
1092 rowinfo[row-2].type == LT_FOOT)
1093 deletelastrow = true;
1096 if (rowinfo[row].type != LT_LASTFOOT &&
1097 rowinfo[row-2].type == LT_LASTFOOT)
1098 deletelastrow = true;
1106 if (deletelastrow) {
1107 lines.erase(lines.begin() + (row - 1));
1108 rowinfo.erase(rowinfo.begin() + (row - 1));
1109 cellinfo.erase(cellinfo.begin() + (row - 1));
1114 vector<string> cells;
1115 split(lines[row], cells, TAB);
1116 for (size_t col = 0, cell = 0; cell < cells.size();
1118 //cerr << "cell content: '" << cells[cell] << "'\n";
1119 if (col >= colinfo.size()) {
1120 // This does not work in LaTeX
1121 cerr << "Ignoring extra cell '"
1122 << cells[cell] << "'." << endl;
1125 Parser p(cells[cell]);
1127 //cells[cell] << "'\n";
1128 if (p.next_token().cs() == "multicolumn") {
1131 size_t const ncells =
1132 convert<unsigned int>(p.verbatim_item());
1134 // special cell properties alignment
1136 handle_colalign(p, t, ColInfo());
1137 p.skip_spaces(true);
1138 ColInfo & ci = t.front();
1140 // The logic of LyX for multicolumn vertical
1141 // lines is too complicated to reproduce it
1142 // here (see LyXTabular::TeXCellPreamble()).
1143 // Therefore we simply put everything in the
1147 cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN;
1148 cellinfo[row][col].align = ci.align;
1149 cellinfo[row][col].special = ci.special;
1150 cellinfo[row][col].leftlines = ci.leftlines;
1151 cellinfo[row][col].rightlines = ci.rightlines;
1153 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
1154 if (!cellinfo[row][col].content.empty()) {
1155 // This may or may not work in LaTeX,
1156 // but it does not work in LyX.
1157 // FIXME: Handle it correctly!
1158 cerr << "Moving cell content '"
1160 << "' into a multicolumn cell. "
1161 "This will probably not work."
1164 cellinfo[row][col].content += os.str();
1166 // add dummy cells for multicol
1167 for (size_t i = 0; i < ncells - 1; ++i) {
1169 if (col >= colinfo.size()) {
1170 cerr << "The cell '"
1173 << convert<string>(ncells)
1174 << " columns while the table has only "
1175 << convert<string>(colinfo.size())
1177 << " Therefore the surplus columns will be ignored."
1181 cellinfo[row][col].multi = CELL_PART_OF_MULTICOLUMN;
1182 cellinfo[row][col].align = 'c';
1185 } else if (col == 0 && colinfo.size() > 1 &&
1187 p.next_token().cs() == "caption") {
1188 // longtable caption support in LyX is a hack:
1189 // Captions require a row of their own with
1190 // the caption flag set to true, having only
1191 // one multicolumn cell. The contents of that
1192 // cell must contain exactly one caption inset
1193 // and nothing else.
1194 // Fortunately, the caption flag is only needed
1195 // for tables with more than one column.
1196 rowinfo[row].caption = true;
1197 for (size_t c = 1; c < cells.size(); ++c) {
1198 if (!cells[c].empty()) {
1199 cerr << "Moving cell content '"
1201 << "' into the caption cell. "
1202 "This will probably not work."
1204 cells[0] += cells[c];
1208 cellinfo[row][col].align = colinfo[col].align;
1209 cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN;
1211 parse_text_in_inset(p, os, FLAG_CELL, false, context);
1212 cellinfo[row][col].content += os.str();
1213 // add dummy multicolumn cells
1214 for (size_t c = 1; c < colinfo.size(); ++c)
1215 cellinfo[row][c].multi = CELL_PART_OF_MULTICOLUMN;
1219 if (p.next_token().cs() == "begin") {
1222 string const env = p.getArg('{', '}');
1223 if (env == "sideways" || env == "turn") {
1224 string angle = "90";
1225 if (env == "turn") {
1227 angle = p.getArg('{', '}');
1229 active_environments.push_back(env);
1230 p.ertEnvironment(env);
1231 active_environments.pop_back();
1233 if (!p.good() && support::isStrInt(angle))
1234 rotate = convert<int>(angle);
1238 cellinfo[row][col].leftlines = colinfo[col].leftlines;
1239 cellinfo[row][col].rightlines = colinfo[col].rightlines;
1240 cellinfo[row][col].align = colinfo[col].align;
1243 cellinfo[row][col].rotate = rotate;
1245 active_environments.push_back(p.getArg('{', '}'));
1248 parse_text_in_inset(p, os, FLAG_END, false, context);
1249 active_environments.pop_back();
1250 preamble.registerAutomaticallyLoadedPackage("rotating");
1252 parse_text_in_inset(p, os, FLAG_CELL, false, context);
1254 cellinfo[row][col].content += os.str();
1258 //cerr << "// handle almost empty last row what we have\n";
1259 // handle almost empty last row
1260 if (row && lines[row].empty() && row + 1 == rowinfo.size()) {
1261 //cerr << "remove empty last line\n";
1262 if (rowinfo[row].topline)
1263 rowinfo[row - 1].bottomline = true;
1264 for (size_t col = 0; col < colinfo.size(); ++col)
1265 if (cellinfo[row][col].topline)
1266 cellinfo[row - 1][col].bottomline = true;
1273 // Now we have the table structure and content in rowinfo, colinfo
1275 // Unfortunately LyX has some limitations that we need to work around.
1277 // Convert cells with special content to multicolumn cells
1278 // (LyX ignores the special field for non-multicolumn cells).
1279 for (size_t row = 0; row < rowinfo.size(); ++row) {
1280 for (size_t col = 0; col < cellinfo[row].size(); ++col) {
1281 if (cellinfo[row][col].multi == CELL_NORMAL &&
1282 !cellinfo[row][col].special.empty())
1283 cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN;
1287 // Distribute lines from rows/columns to cells
1288 // The code was stolen from convert_tablines() in lyx2lyx/lyx_1_6.py.
1289 // Each standard cell inherits the settings of the corresponding
1290 // rowinfo/colinfo. This works because all cells with individual
1291 // settings were converted to multicolumn cells above.
1292 // Each multicolumn cell inherits the settings of the rowinfo/colinfo
1293 // corresponding to the first column of the multicolumn cell (default
1294 // of the multicol package). This works because the special field
1295 // overrides the line fields.
1296 for (size_t row = 0; row < rowinfo.size(); ++row) {
1297 for (size_t col = 0; col < cellinfo[row].size(); ++col) {
1298 if (cellinfo[row][col].multi == CELL_NORMAL) {
1299 cellinfo[row][col].topline = rowinfo[row].topline;
1300 cellinfo[row][col].bottomline = rowinfo[row].bottomline;
1301 cellinfo[row][col].leftlines = colinfo[col].leftlines;
1302 cellinfo[row][col].rightlines = colinfo[col].rightlines;
1303 } else if (cellinfo[row][col].multi == CELL_BEGIN_OF_MULTICOLUMN) {
1305 while (s < cellinfo[row].size() &&
1306 cellinfo[row][s].multi == CELL_PART_OF_MULTICOLUMN)
1308 if (s < cellinfo[row].size() &&
1309 cellinfo[row][s].multi != CELL_BEGIN_OF_MULTICOLUMN)
1310 cellinfo[row][col].rightlines = colinfo[col].rightlines;
1311 if (col > 0 && cellinfo[row][col-1].multi == CELL_NORMAL)
1312 cellinfo[row][col].leftlines = colinfo[col].leftlines;
1318 preamble.registerAutomaticallyLoadedPackage("booktabs");
1319 if (is_long_tabular)
1320 preamble.registerAutomaticallyLoadedPackage("longtable");
1322 //cerr << "// output what we have\n";
1323 // output what we have
1324 string const rotate = "0";
1325 os << "\n<lyxtabular version=\"3\" rows=\"" << rowinfo.size()
1326 << "\" columns=\"" << colinfo.size() << "\">\n";
1328 << write_attribute("rotate", rotate)
1329 << write_attribute("booktabs", booktabs)
1330 << write_attribute("islongtable", is_long_tabular);
1331 if (is_long_tabular) {
1332 os << write_attribute("firstHeadTopDL", endfirsthead.topDL)
1333 << write_attribute("firstHeadBottomDL", endfirsthead.bottomDL)
1334 << write_attribute("firstHeadEmpty", endfirsthead.empty)
1335 << write_attribute("headTopDL", endhead.topDL)
1336 << write_attribute("headBottomDL", endhead.bottomDL)
1337 << write_attribute("footTopDL", endfoot.topDL)
1338 << write_attribute("footBottomDL", endfoot.bottomDL)
1339 << write_attribute("lastFootTopDL", endlastfoot.topDL)
1340 << write_attribute("lastFootBottomDL", endlastfoot.bottomDL)
1341 << write_attribute("lastFootEmpty", endlastfoot.empty);
1343 os << write_attribute("tabularvalignment", tabularvalignment)
1344 << write_attribute("tabularwidth", tabularwidth);
1347 //cerr << "// after header\n";
1348 for (size_t col = 0; col < colinfo.size(); ++col) {
1349 os << "<column alignment=\""
1350 << verbose_align(colinfo[col].align) << "\""
1352 << verbose_valign(colinfo[col].valign) << "\""
1353 << write_attribute("width", translate_len(colinfo[col].width))
1354 << write_attribute("special", colinfo[col].special)
1357 //cerr << "// after cols\n";
1359 for (size_t row = 0; row < rowinfo.size(); ++row) {
1361 << write_attribute("topspace", rowinfo[row].top_space)
1362 << write_attribute("bottomspace", rowinfo[row].bottom_space)
1363 << write_attribute("interlinespace", rowinfo[row].interline_space)
1364 << write_attribute("endhead",
1365 rowinfo[row].type == LT_HEAD)
1366 << write_attribute("endfirsthead",
1367 rowinfo[row].type == LT_FIRSTHEAD)
1368 << write_attribute("endfoot",
1369 rowinfo[row].type == LT_FOOT)
1370 << write_attribute("endlastfoot",
1371 rowinfo[row].type == LT_LASTFOOT)
1372 << write_attribute("newpage", rowinfo[row].newpage)
1373 << write_attribute("caption", rowinfo[row].caption)
1375 for (size_t col = 0; col < colinfo.size(); ++col) {
1376 CellInfo const & cell = cellinfo[row][col];
1378 if (cell.multi != CELL_NORMAL)
1379 os << " multicolumn=\"" << cell.multi << "\"";
1380 os << " alignment=\"" << verbose_align(cell.align)
1382 << " valignment=\"" << verbose_valign(cell.valign)
1384 << write_attribute("topline", cell.topline)
1385 << write_attribute("bottomline", cell.bottomline)
1386 << write_attribute("leftline", cell.leftlines > 0)
1387 << write_attribute("rightline", cell.rightlines > 0)
1388 << write_attribute("rotate", cell.rotate);
1389 //cerr << "\nrow: " << row << " col: " << col;
1391 // cerr << " topline=\"true\"";
1392 //if (cell.bottomline)
1393 // cerr << " bottomline=\"true\"";
1394 os << " usebox=\"none\""
1395 << write_attribute("width", translate_len(cell.width));
1396 if (cell.multi != CELL_NORMAL)
1397 os << write_attribute("special", cell.special);
1399 << "\n\\begin_inset Text\n"
1401 << "\n\\end_inset\n"
1407 os << "</lyxtabular>\n";