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.
22 #include "support/lassert.h"
23 #include "support/convert.h"
24 #include "support/lstrings.h"
41 ColInfo() : align('n'), valign('n'), rightlines(0), leftlines(0),
42 varwidth(false), decimal_point('\0'), vcolumn(false) {}
45 /// vertical alignment
49 /// special column alignment
51 /// number of lines on the right
53 /// number of lines on the left
64 /// row type for longtables
71 /// part of head on first page
75 /// part of foot on last page
82 RowInfo() : topline(false), bottomline(false), type(LT_NORMAL),
83 caption(false), newpage(false) {}
84 /// Does this row have any special setting?
87 return topline || bottomline || !top_space.empty() ||
88 !bottom_space.empty() || !interline_space.empty() ||
89 type != LT_NORMAL || caption || newpage;
91 /// horizontal line above
93 /// horizontal line below
95 /// Extra space between the top line and this row
97 /// Extra space between this row and the bottom line
99 /// Extra space between the bottom line and the next top line
100 string interline_space;
101 /// These are for longtabulars only
102 /// row type (head, foot, firsthead etc.)
104 /// row for a caption
106 /// row for a newpage
111 /// the numeric values are part of the file format!
115 /// A multicolumn cell. The number of columns is <tt>1 + number
116 /// of CELL_PART_OF_MULTICOLUMN cells</tt> that follow directly
117 CELL_BEGIN_OF_MULTICOLUMN,
118 /// This is a dummy cell (part of a multicolumn cell)
119 CELL_PART_OF_MULTICOLUMN,
121 CELL_BEGIN_OF_MULTIROW,
123 CELL_PART_OF_MULTIROW
129 CellInfo() : multi(CELL_NORMAL), align('n'), valign('n'),
130 leftlines(0), rightlines(0), topline(false),
131 bottomline(false), topline_ltrim(false),
132 topline_rtrim(false), bottomline_ltrim(false),
133 bottomline_rtrim(false), rotate(0), mrxnum(0) {}
140 /// vertical cell alignment
142 /// number of lines on the left
144 /// number of lines on the right
146 /// do we have a line above?
148 /// do we have a line below?
150 /// Left trimming of top line
152 /// Right trimming of top line
154 /// Left trimming of bottom line
155 bool bottomline_ltrim;
156 /// Right trimming of top line
157 bool bottomline_rtrim;
158 /// how is the cell rotated?
160 /// width for multicolumn cells
162 /// special formatting for multicolumn cells
166 /// number of further multirows
174 ltType() : set(false), topDL(false), bottomDL(false), empty(false) {}
175 // we have this header type (is set in the getLT... functions)
177 // double borders on top
179 // double borders on bottom
181 // used for FirstHeader & LastFooter and if this is true
182 // all the rows marked as FirstHeader or LastFooter are
183 // ignored in the output and it is set to be empty!
188 /// translate a horizontal alignment (as stored in ColInfo and CellInfo) to LyX
189 inline char const * verbose_align(char c)
206 /// translate a vertical alignment (as stored in ColInfo and CellInfo) to LyX
207 inline char const * verbose_valign(char c)
209 // The default value for no special alignment is "top".
222 // stripped down from tabluar.C. We use it currently only for bools and
224 string const write_attribute(string const & name, bool const & b)
226 // we write only true attribute values so we remove a bit of the
227 // file format bloat for tabulars.
228 return b ? ' ' + name + "=\"true\"" : string();
232 string const write_attribute(string const & name, string const & s)
234 return s.empty() ? string() : ' ' + name + "=\"" + s + '"';
238 string const write_attribute(string const & name, int const & i)
240 // we write only true attribute values so we remove a bit of the
241 // file format bloat for tabulars.
242 return i ? write_attribute(name, convert<string>(i)) : string();
246 /*! rather brutish way to code table structure in a string:
251 \multicolumn{2}{c}{4} & 5 //
257 gets "translated" to:
260 HLINE 1 TAB 2 TAB 3 HLINE HLINE LINE
261 \hline HLINE \multicolumn{2}{c}{4} TAB 5 HLINE HLINE LINE
262 HLINE 6 TAB 7 HLINE HLINE LINE
263 HLINE 8 HLINE \endhead HLINE LINE
267 char const TAB = '\001';
268 char const LINE = '\002';
269 char const HLINE = '\004';
273 * Move the information in leftlines, rightlines, align and valign to the
274 * special field. This is necessary if the special field is not empty,
275 * because LyX ignores leftlines > 1, rightlines > 1, align and valign in
278 void ci2special(ColInfo & ci)
280 if (ci.width.empty() && ci.align == 'n')
281 // The alignment setting is already in special, since
282 // handle_colalign() never stores ci with these settings
283 // and ensures that leftlines == 0 and rightlines == 0 in
287 if (ci.decimal_point != '\0') {
288 // we only support decimal point natively
289 // with 'l' alignment in or 'n' alignment
290 // with width in second row
291 if (ci.align != 'l' && ci.align != 'n') {
292 ci.decimal_point = '\0';
298 if (!ci.width.empty()) {
299 string arraybackslash;
301 arraybackslash = "\\arraybackslash";
304 ci.special += ">{\\raggedright" + arraybackslash + "}";
307 ci.special += ">{\\raggedleft" + arraybackslash + "}";
310 ci.special += ">{\\centering" + arraybackslash + "}";
315 else if (ci.varwidth)
317 else if (ci.valign == 'n')
320 ci.special += ci.valign;
321 ci.special += '{' + ci.width + '}';
324 ci.special += ci.align;
326 // LyX can only have one left and one right line.
327 for (int i = 1; i < ci.leftlines; ++i)
328 ci.special.insert(0, "|");
329 for (int i = 1; i < ci.rightlines; ++i)
331 ci.leftlines = min(ci.leftlines, 1);
332 ci.rightlines = min(ci.rightlines, 1);
339 * Handle column specifications for tabulars and multicolumns.
340 * The next token of the parser \p p must be an opening brace, and we read
341 * everything until the matching closing brace.
342 * The resulting column specifications are filled into \p colinfo. This is
343 * in an intermediate form. fix_colalign() makes it suitable for LyX output.
345 void handle_colalign(Parser & p, vector<ColInfo> & colinfo,
346 ColInfo const & start)
348 if (p.get_token().cat() != catBegin)
349 warning_message("Wrong syntax for table column alignment.\n"
350 "Expected '{', got '" + p.curr_token().asInput()+ "'");
352 ColInfo next = start;
353 for (Token t = p.get_token(); p.good() && t.cat() != catEnd;
356 debug_message("t: " + t.asInput() + " c: '" + t.character() + "'");
358 // We cannot handle comments here
359 if (t.cat() == catComment) {
360 if (t.cs().empty()) {
364 warning_message("Ignoring comment: " + t.asInput());
368 switch (t.character()) {
370 // whitespace, ignore.
375 // new column, horizontal aligned
376 next.align = t.character();
377 if (!next.special.empty()) {
379 // handle decimal separator
380 if (next.decimal_point != '\0') {
381 if (!colinfo.empty() && colinfo.back().align == 'r') {
382 colinfo.back().align = 'd';
383 colinfo.back().decimal_point = next.decimal_point;
385 next.decimal_point = '\0';
388 colinfo.push_back(next);
393 next.varwidth = true;
394 if (!next.special.empty())
396 colinfo.push_back(next);
400 // V column type (varwidth package)
401 string const s = trimSpaceAndEol(p.verbatim_item());
402 // V{\linewidth} is treated as a normal column
403 // (which allows for line breaks). The V type is
404 // automatically set by LyX in this case
405 if (s != "\\linewidth" || !next.special.empty()) {
410 colinfo.push_back(next);
417 // new column, vertical aligned box
418 next.valign = t.character();
419 next.width = p.verbatim_item();
420 if (!next.special.empty()) {
422 // handle decimal separator
423 if (next.decimal_point != '\0') {
424 if (!colinfo.empty() && colinfo.back().align == 'r') {
425 colinfo.back().align = 'd';
426 colinfo.back().decimal_point = next.decimal_point;
428 next.decimal_point = '\0';
431 colinfo.push_back(next);
436 if (colinfo.empty()) {
437 if (next.special.empty())
441 } else if (colinfo.back().special.empty())
442 ++colinfo.back().rightlines;
443 else if (next.special.empty() && p.next_token().cat() != catEnd)
446 colinfo.back().special += '|';
449 // text before the next column
450 string const s = trimSpaceAndEol(p.verbatim_item());
451 if (next.special.empty() &&
453 // Maybe this can be converted to a
454 // horizontal alignment setting for
455 // fixed width columns
456 if (s == "\\raggedleft" || s == "\\raggedleft\\arraybackslash")
458 else if (s == "\\raggedright" || s == "\\raggedright\\arraybackslash")
460 else if (s == "\\centering" || s == "\\centering\\arraybackslash")
463 next.special = ">{" + s + '}';
465 next.special += ">{" + s + '}';
469 // text after the last column
470 string const s = trimSpaceAndEol(p.verbatim_item());
472 // This is not possible in LaTeX.
473 warning_message("Ignoring separator '<{" + s + "}'.");
475 ColInfo & ci = colinfo.back();
477 ci.special += "<{" + s + '}';
482 if (p.next_token().character() != '{')
484 // *{n}{arg} means 'n' columns of type 'arg'
485 string const num = p.verbatim_item();
486 string const arg = p.verbatim_item();
487 size_t const n = convert<unsigned int>(num);
488 if (!arg.empty() && n > 0) {
490 for (size_t i = 0; i < n; ++i)
494 handle_colalign(p2, colinfo, next);
497 warning_message("Ignoring column specification"
498 " '*{" + num + "}{" + arg + "}'.");
503 // text instead of the column spacing
505 // text in addition to the column spacing
506 string const arg = p.verbatim_item();
507 next.special += t.character();
508 next.special += '{' + arg + '}';
509 string const sarg = arg.size() > 2 ? arg.substr(0, arg.size() - 1) : string();
510 if (t.character() == '@' && sarg == "\\extracolsep{0pt}")
511 next.decimal_point = arg.back();
515 // try user defined column types
516 // unknown column types (nargs == -1) are
517 // assumed to consume no arguments
519 next.special += t.character();
521 preamble.getSpecialTableColumnArguments(t.character());
522 for (int i = 0; i < nargs; ++i)
523 next.special += '{' +
524 p.verbatim_item() + '}';
525 colinfo.push_back(next);
532 // Maybe we have some column separators that need to be added to the
535 if (!next.special.empty()) {
536 ColInfo & ci = colinfo.back();
538 ci.special += next.special;
539 next.special.erase();
545 * Move the left and right lines and alignment settings of the column \p ci
546 * to the special field if necessary.
548 void fix_colalign(ColInfo & ci)
550 if (ci.leftlines > 1 || ci.rightlines > 1)
556 * LyX can't handle more than one vertical line at the left or right side
558 * This function moves the left and right lines and alignment settings of all
559 * columns in \p colinfo to the special field if necessary.
561 void fix_colalign(vector<ColInfo> & colinfo)
563 // Try to move extra leftlines to the previous column.
564 // We do this only if both special fields are empty, otherwise we
565 // can't tell whether the result will be the same.
566 for (size_t col = 0; col < colinfo.size(); ++col) {
567 if (colinfo[col].leftlines > 1 &&
568 colinfo[col].special.empty() && col > 0 &&
569 colinfo[col - 1].rightlines == 0 &&
570 colinfo[col - 1].special.empty()) {
571 ++colinfo[col - 1].rightlines;
572 --colinfo[col].leftlines;
575 // Try to move extra rightlines to the next column
576 for (size_t col = 0; col < colinfo.size(); ++col) {
577 if (colinfo[col].rightlines > 1 &&
578 colinfo[col].special.empty() &&
579 col < colinfo.size() - 1 &&
580 colinfo[col + 1].leftlines == 0 &&
581 colinfo[col + 1].special.empty()) {
582 ++colinfo[col + 1].leftlines;
583 --colinfo[col].rightlines;
586 // Move the lines and alignment settings to the special field if
588 for (auto & col : colinfo)
594 * Parse hlines and similar stuff.
595 * \returns whether the token \p t was parsed
597 bool parse_hlines(Parser & p, Token const & t, string & hlines,
598 bool is_long_tabular)
600 LASSERT(t.cat() == catEscape, return false);
602 if (t.cs() == "hline" || t.cs() == "toprule" || t.cs() == "midrule" ||
603 t.cs() == "bottomrule")
604 hlines += '\\' + t.cs();
606 else if (t.cs() == "cline")
607 hlines += "\\cline{" + p.verbatim_item() + '}';
609 else if (t.cs() == "cmidrule") {
612 // We do not support the optional height argument
615 // We support the \cmidrule(l){3-4} form but
616 // not the trim length parameters (l{<with>}r{<width>})
617 string const trim = p.getFullParentheseArg();
618 string const range = p.verbatim_item();
620 if (support::contains(trim, "{"))
622 hlines += "\\cmidrule" + trim + "{" + range + "}";
624 hlines += "\\cmidrule{" + range + '}';
627 else if (t.cs() == "addlinespace") {
630 bool const hasArgument(p.getFullArg('{', '}').first);
633 hlines += "\\addlinespace{" + p.verbatim_item() + '}';
635 hlines += "\\addlinespace";
638 else if (is_long_tabular && t.cs() == "newpage")
639 hlines += "\\newpage";
648 /// Position in a row
650 /// At the very beginning, before the first token
652 /// After the first token and before any column token
654 /// After the first column token. Comments and whitespace are only
655 /// treated as tokens in this position
657 /// After the first non-column token at the end
663 * Parse table structure.
664 * We parse tables in a two-pass process: This function extracts the table
665 * structure (rows, columns, hlines etc.), but does not change the cell
666 * content. The cell content is parsed in a second step in handle_tabular().
668 void parse_table(Parser & p, ostream & os, bool is_long_tabular,
669 RowPosition & pos, unsigned flags)
671 // table structure commands such as \hline
674 // comments that occur at places where we can't handle them
678 Token const & t = p.get_token();
681 debugToken(cerr, t, flags);
684 // comments and whitespace in hlines
687 case IN_HLINES_START:
689 if (t.cat() == catComment) {
694 // We can't handle comments here,
695 // store them for later use
696 comments += t.asInput();
698 } else if (t.cat() == catSpace ||
699 t.cat() == catNewline) {
700 // whitespace is irrelevant here, we
701 // need to recognize hline stuff
710 // We need to handle structure stuff first in order to
711 // determine whether we need to output a HLINE separator
712 // before the row or not.
713 if (t.cat() == catEscape) {
714 if (parse_hlines(p, t, hlines, is_long_tabular)) {
717 pos = IN_HLINES_START;
722 case IN_HLINES_START:
729 else if (t.cs() == "tabularnewline" ||
733 warning_message("Warning: Converting TeX "
734 "'\\cr' to LaTeX '\\\\'.");
735 // stuff before the line break
736 os << comments << HLINE << hlines << HLINE
738 //warning_message("hlines: " + hlines);
745 else if (is_long_tabular &&
746 (t.cs() == "endhead" ||
747 t.cs() == "endfirsthead" ||
748 t.cs() == "endfoot" ||
749 t.cs() == "endlastfoot")) {
750 hlines += t.asInput();
754 // these commands are implicit line
756 os << comments << HLINE << hlines
763 pos = IN_HLINES_START;
765 case IN_HLINES_START:
772 // We need a HLINE separator if we either have no hline
773 // stuff at all and are just starting a row or if we just
774 // got the first non-hline token.
777 // no hline tokens exist, first token at row start
778 case IN_HLINES_START:
779 // hline tokens exist, first non-hline token at row
781 os << hlines << HLINE << comments;
787 // Oops, there is still cell content or unsupported
788 // booktabs commands after hline stuff. The latter are
789 // moved to the cell, and the first does not work in
790 // LaTeX, so we ignore the hlines.
793 if (support::contains(hlines, "\\hline") ||
794 support::contains(hlines, "\\cline") ||
795 support::contains(hlines, "\\newpage"))
796 warning_message("Ignoring '" + hlines + "' in a cell");
806 // If we come here we have normal cell content
810 if (t.cat() == catMath) {
811 // we are inside some text mode thingy, so opening new math is allowed
812 Token const & n = p.get_token();
813 if (n.cat() == catMath) {
814 // TeX's $$...$$ syntax for displayed math
816 // This does only work because parse_math outputs TeX
817 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
819 p.get_token(); // skip the second '$' token
821 // simple $...$ stuff
824 // This does only work because parse_math outputs TeX
825 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
830 else if (t.cat() == catSpace
831 || t.cat() == catNewline
832 || t.cat() == catLetter
833 || t.cat() == catSuper
835 || t.cat() == catOther
836 || t.cat() == catActive
837 || t.cat() == catParameter)
840 else if (t.cat() == catBegin) {
842 parse_table(p, os, is_long_tabular, pos,
847 else if (t.cat() == catEnd) {
848 if (flags & FLAG_BRACE_LAST)
850 warning_message("unexpected '}'");
853 else if (t.cat() == catAlign) {
858 else if (t.cat() == catComment)
861 else if (t.cs() == "(") {
863 // This does only work because parse_math outputs TeX
864 parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
868 else if (t.cs() == "[") {
870 // This does only work because parse_math outputs TeX
871 parse_math(p, os, FLAG_EQUATION, MATH_MODE);
875 else if (t.cs() == "begin") {
876 string const name = p.getArg('{', '}');
877 active_environments.push_back(name);
878 os << "\\begin{" << name << '}';
879 // treat the nested environment as a block, don't
880 // parse &, \\ etc, because they don't belong to our
881 // table if they appear.
882 os << p.ertEnvironment(name);
883 os << "\\end{" << name << '}';
884 active_environments.pop_back();
887 else if (t.cs() == "end") {
888 if (flags & FLAG_END) {
889 // eat environment name
890 string const name = p.getArg('{', '}');
891 if (name != active_environment())
892 p.error("\\end{" + name + "} does not match \\begin{"
893 + active_environment() + "}");
896 p.error("found 'end' unexpectedly");
903 // We can have comments if the last line is incomplete
906 // We can have hline stuff if the last line is incomplete
907 if (!hlines.empty()) {
908 // this does not work in LaTeX, so we ignore it
909 warning_message("Ignoring '" + hlines + "' at end of tabular");
914 void handle_hline_above(RowInfo & ri, vector<CellInfo> & ci)
917 for (auto & col : ci)
922 void handle_hline_below(RowInfo & ri, vector<CellInfo> & ci)
924 ri.bottomline = true;
925 for (auto & col : ci)
926 col.bottomline = true;
930 void parse_cell_content(ostringstream & os2, Parser & parse, unsigned int flags, Context & newcontext,
931 vector< vector<CellInfo> > & cellinfo, vector<ColInfo> & colinfo,
932 size_t const row, size_t const col)
936 bool varwidth = false;
937 if (parse.next_token().cs() == "begin") {
938 parse.pushPosition();
940 string const env = parse.getArg('{', '}');
941 if (env == "sideways" || env == "turn") {
945 angle = parse.getArg('{', '}');
947 active_environments.push_back(env);
948 parse.ertEnvironment(env);
949 active_environments.pop_back();
951 if (!parse.good() && support::isStrInt(angle))
952 rotate = convert<int>(angle);
953 } else if (env == "cellvarwidth") {
954 active_environments.push_back(env);
955 parse.ertEnvironment(env);
956 active_environments.pop_back();
963 cellinfo[row][col].rotate = rotate;
965 active_environments.push_back(parse.getArg('{', '}'));
967 parse.getArg('{', '}');
968 parse_text_in_inset(parse, os2, FLAG_END, false, newcontext);
969 active_environments.pop_back();
970 preamble.registerAutomaticallyLoadedPackage("rotating");
971 } else if (varwidth) {
973 active_environments.push_back(parse.getArg('{', '}'));
976 cellinfo[row][col].valign = parse.getArg('[', ']')[1];
977 newcontext.in_table_cell = true;
978 parse_text_in_inset(parse, os2, FLAG_END, false, newcontext);
979 if (cellinfo[row][col].multi == CELL_NORMAL)
980 colinfo[col].align = newcontext.cell_align;
982 cellinfo[row][col].align = newcontext.cell_align;
983 active_environments.pop_back();
984 preamble.registerAutomaticallyLoadedPackage("varwidth");
986 parse_text_in_inset(parse, os2, flags, false, newcontext);
991 } // anonymous namespace
994 void handle_tabular(Parser & p, ostream & os, string const & name,
995 string const & tabularwidth, string const & halign,
996 Context const & context)
998 Context newcontext = context;
999 bool const is_long_tabular(name == "longtable" || name == "xltabular");
1000 bool booktabs = false;
1001 string tabularvalignment("middle");
1002 string posopts = p.getOpt();
1003 if (!posopts.empty()) {
1004 if (posopts == "[t]")
1005 tabularvalignment = "top";
1006 else if (posopts == "[b]")
1007 tabularvalignment = "bottom";
1009 warning_message("vertical tabular positioning '"
1010 + posopts + "' ignored");
1013 vector<ColInfo> colinfo;
1015 // handle column formatting
1016 handle_colalign(p, colinfo, ColInfo());
1017 fix_colalign(colinfo);
1019 // first scan of cells
1020 // use table mode to keep it minimal-invasive
1021 // not exactly what's TeX doing...
1022 vector<string> lines;
1024 RowPosition rowpos = ROW_START;
1025 parse_table(p, ss, is_long_tabular, rowpos, FLAG_END);
1026 split(ss.str(), lines, LINE);
1028 vector< vector<CellInfo> > cellinfo(lines.size());
1029 vector<RowInfo> rowinfo(lines.size());
1030 ltType endfirsthead;
1036 //warning_message("// split into rows");
1037 for (size_t row = 0; row < rowinfo.size();) {
1040 cellinfo[row].resize(colinfo.size());
1041 bool deletelastrow = false;
1044 vector<string> dummy;
1045 //warning_message("########### LINE: " << lines[row] << "########");
1046 split(lines[row], dummy, HLINE);
1048 // handle horizontal line fragments
1049 // we do only expect this for a last line without '\\'
1050 if (dummy.size() != 3) {
1051 if ((dummy.size() != 1 && dummy.size() != 2) ||
1052 row != rowinfo.size() - 1)
1053 warning_message("unexpected dummy size: " + convert<string>(dummy.size())
1054 + " content: " + lines[row]);
1057 lines[row] = dummy[1];
1059 //warning_message("line: " + row + " above 0: " + dummy[0]);
1060 //warning_message("line: " + row + " below 2: " + dummy[2]);
1061 //warning_message("line: " + row + " cells 1: " + dummy[1]);
1063 for (int i = 0; i <= 2; i += 2) {
1064 //warning_message(" reading from line string '" + dummy[i]);
1065 Parser p1(dummy[size_type(i)]);
1067 Token t = p1.get_token();
1068 //warning_message("read token: " + t);
1069 if (t.cs() == "hline" || t.cs() == "toprule" ||
1070 t.cs() == "midrule" ||
1071 t.cs() == "bottomrule") {
1072 if (t.cs() != "hline")
1075 if (rowinfo[row].topline) {
1076 if (row > 0) // extra bottomline above
1077 handle_hline_below(rowinfo[row - 1], cellinfo[row - 1]);
1079 warning_message("dropping extra " + t.cs());
1080 //warning_message("below row: " + row-1);
1082 handle_hline_above(rowinfo[row], cellinfo[row]);
1083 //warning_message("above row: " + row);
1086 //warning_message("below row: " + row);
1087 handle_hline_below(rowinfo[row], cellinfo[row]);
1089 } else if (t.cs() == "cline" || t.cs() == "cmidrule") {
1091 if (t.cs() == "cmidrule") {
1093 trim = p1.getFullParentheseArg();
1095 string arg = p1.verbatim_item();
1096 //warning_message("read " + t.cs() + " arg: '" + arg + "', trim: '" + trim);
1097 vector<string> cols;
1098 split(arg, cols, '-');
1100 size_t from = convert<unsigned int>(cols[0]);
1102 warning_message("Could not parse " + t.cs() + " start column.");
1104 // 1 based index -> 0 based
1106 if (from >= colinfo.size()) {
1107 warning_message(t.cs() + " starts at "
1108 "non existing column " + convert<string>(from + 1));
1109 from = colinfo.size() - 1;
1111 size_t to = convert<unsigned int>(cols[1]);
1113 warning_message("Could not parse " + t.cs() + " end column.");
1115 // 1 based index -> 0 based
1117 if (to >= colinfo.size()) {
1118 warning_message(t.cs() + " ends at non existing column "
1119 + convert<string>(to + 1));
1120 to = colinfo.size() - 1;
1122 for (size_t col = from; col <= to; ++col) {
1123 //warning_message("row: " + row + " col: " + col + " i: " + i);
1125 rowinfo[row].topline = true;
1126 cellinfo[row][col].topline = true;
1127 if (support::contains(trim, 'l') && col == from) {
1128 //rowinfo[row].topline_ltrim = true;
1129 cellinfo[row][col].topline_ltrim = true;
1131 else if (support::contains(trim, 'r') && col == to) {
1132 //rowinfo[row].topline_rtrim = true;
1133 cellinfo[row][col].topline_rtrim = true;
1136 rowinfo[row].bottomline = true;
1137 cellinfo[row][col].bottomline = true;
1138 if (support::contains(trim, 'l') && col == from) {
1139 //rowinfo[row].bottomline_ltrim = true;
1140 cellinfo[row][col].bottomline_ltrim = true;
1142 else if (support::contains(trim, 'r') && col == to) {
1143 //rowinfo[row].bottomline_rtrim = true;
1144 cellinfo[row][col].bottomline_rtrim = true;
1148 } else if (t.cs() == "addlinespace") {
1150 string const opt = p.next_token().cat() == catBegin ?
1151 p.verbatim_item() : string();
1154 rowinfo[row].top_space = "default";
1156 rowinfo[row].top_space = translate_len(opt);
1157 } else if (rowinfo[row].bottomline) {
1159 rowinfo[row].bottom_space = "default";
1161 rowinfo[row].bottom_space = translate_len(opt);
1164 rowinfo[row].interline_space = "default";
1166 rowinfo[row].interline_space = translate_len(opt);
1168 } else if (t.cs() == "endhead") {
1170 endhead.empty = true;
1172 rowinfo[row].type = LT_HEAD;
1173 for (int r = row - 1; r >= 0; --r) {
1174 if (rowinfo[r].type != LT_NORMAL)
1176 rowinfo[r].type = LT_HEAD;
1177 endhead.empty = false;
1180 } else if (t.cs() == "endfirsthead") {
1182 endfirsthead.empty = true;
1184 rowinfo[row].type = LT_FIRSTHEAD;
1185 for (int r = row - 1; r >= 0; --r) {
1186 if (rowinfo[r].type != LT_NORMAL)
1188 rowinfo[r].type = LT_FIRSTHEAD;
1189 endfirsthead.empty = false;
1191 endfirsthead.set = true;
1192 } else if (t.cs() == "endfoot") {
1194 endfoot.empty = true;
1196 rowinfo[row].type = LT_FOOT;
1197 for (int r = row - 1; r >= 0; --r) {
1198 if (rowinfo[r].type != LT_NORMAL)
1200 rowinfo[r].type = LT_FOOT;
1201 endfoot.empty = false;
1204 } else if (t.cs() == "endlastfoot") {
1206 endlastfoot.empty = true;
1208 rowinfo[row].type = LT_LASTFOOT;
1209 for (int r = row - 1; r >= 0; --r) {
1210 if (rowinfo[r].type != LT_NORMAL)
1212 rowinfo[r].type = LT_LASTFOOT;
1213 endlastfoot.empty = false;
1215 endlastfoot.set = true;
1216 } else if (t.cs() == "newpage") {
1219 rowinfo[row - 1].newpage = true;
1221 // This does not work in LaTeX
1222 warning_message("Ignoring "
1226 rowinfo[row].newpage = true;
1228 warning_message("unexpected line token: " + t.cs());
1233 // LyX ends headers and footers always with \tabularnewline.
1234 // This causes one additional row in the output.
1235 // If the last row of a header/footer is empty, we can work
1236 // around that by removing it.
1238 RowInfo test = rowinfo[row-1];
1239 test.type = LT_NORMAL;
1240 if (lines[row-1].empty() && !test.special()) {
1241 switch (rowinfo[row-1].type) {
1243 if (rowinfo[row].type != LT_FIRSTHEAD &&
1244 rowinfo[row-2].type == LT_FIRSTHEAD)
1245 deletelastrow = true;
1248 if (rowinfo[row].type != LT_HEAD &&
1249 rowinfo[row-2].type == LT_HEAD)
1250 deletelastrow = true;
1253 if (rowinfo[row].type != LT_FOOT &&
1254 rowinfo[row-2].type == LT_FOOT)
1255 deletelastrow = true;
1258 if (rowinfo[row].type != LT_LASTFOOT &&
1259 rowinfo[row-2].type == LT_LASTFOOT)
1260 deletelastrow = true;
1268 if (deletelastrow) {
1269 lines.erase(lines.begin() + (row - 1));
1270 rowinfo.erase(rowinfo.begin() + (row - 1));
1271 cellinfo.erase(cellinfo.begin() + (row - 1));
1276 vector<string> cells;
1277 split(lines[row], cells, TAB);
1278 for (size_t col = 0, cell = 0; cell < cells.size();
1280 //warning_message("cell content: '" + cells[cell]);
1281 if (col >= colinfo.size()) {
1282 // This does not work in LaTeX
1283 warning_message("Ignoring extra cell '" + cells[cell] + "'.");
1286 string cellcont = cells[cell];
1287 // For decimal cells, ass the content of the second one to the first one
1289 if (colinfo[col].decimal_point != '\0' && colinfo[col].align == 'd' && cell < cells.size() - 1)
1290 cellcont += colinfo[col].decimal_point + cells[cell + 1];
1291 Parser parse(cellcont);
1292 parse.skip_spaces();
1293 //cells[cell] << "'\n";
1294 if (parse.next_token().cs() == "multirow") {
1295 // We do not support the vpos arg yet.
1296 if (parse.hasOpt()) {
1297 string const vpos = parse.getArg('[', ']');
1298 parse.skip_spaces(true);
1299 warning_message("Ignoring multirow's vpos arg '"
1304 size_t const ncells =
1305 convert<unsigned int>(parse.verbatim_item());
1306 // We do not support the bigstrut arg yet.
1307 if (parse.hasOpt()) {
1308 string const bs = parse.getArg('[', ']');
1309 parse.skip_spaces(true);
1310 warning_message("Ignoring multirow's bigstrut arg '"
1313 // the width argument
1314 string const width = parse.getArg('{', '}');
1317 if (parse.hasOpt()) {
1318 vmove = parse.getArg('[', ']');
1319 parse.skip_spaces(true);
1322 if (width != "*" && width != "=")
1323 colinfo[col].width = width;
1325 cellinfo[row][col].mroffset = vmove;
1326 cellinfo[row][col].multi = CELL_BEGIN_OF_MULTIROW;
1327 cellinfo[row][col].leftlines = colinfo[col].leftlines;
1328 cellinfo[row][col].rightlines = colinfo[col].rightlines;
1329 cellinfo[row][col].mrxnum = ncells - 1;
1332 parse.get_token();// skip {
1333 parse_cell_content(os2, parse, FLAG_BRACE_LAST, newcontext,
1336 parse.get_token();// skip }
1337 if (!cellinfo[row][col].content.empty()) {
1338 // This may or may not work in LaTeX,
1339 // but it does not work in LyX.
1340 // FIXME: Handle it correctly!
1341 warning_message("Moving cell content '"
1343 + "' into a multirow cell. "
1344 "This will probably not work.");
1346 cellinfo[row][col].content += os2.str();
1347 } else if (parse.next_token().cs() == "multicolumn") {
1350 size_t const ncells =
1351 convert<unsigned int>(parse.verbatim_item());
1353 // special cell properties alignment
1355 handle_colalign(parse, t, ColInfo());
1356 parse.skip_spaces(true);
1357 ColInfo & ci = t.front();
1359 // The logic of LyX for multicolumn vertical
1360 // lines is too complicated to reproduce it
1361 // here (see LyXTabular::TeXCellPreamble()).
1362 // Therefore we simply put everything in the
1366 cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN;
1367 cellinfo[row][col].align = ci.align;
1368 cellinfo[row][col].special = ci.special;
1369 cellinfo[row][col].leftlines = ci.leftlines;
1370 cellinfo[row][col].rightlines = ci.rightlines;
1372 parse.get_token();// skip {
1373 parse_cell_content(os2, parse, FLAG_BRACE_LAST, newcontext,
1376 parse.get_token();// skip }
1377 if (!cellinfo[row][col].content.empty()) {
1378 // This may or may not work in LaTeX,
1379 // but it does not work in LyX.
1380 // FIXME: Handle it correctly!
1381 warning_message("Moving cell content '"
1383 + "' into a multicolumn cell. "
1384 "This will probably not work.");
1386 cellinfo[row][col].content += os2.str();
1388 // add dummy cells for multicol
1389 for (size_t i = 0; i < ncells - 1; ++i) {
1391 if (col >= colinfo.size()) {
1392 warning_message("The cell '"
1395 + convert<string>(ncells)
1396 + " columns while the table has only "
1397 + convert<string>(colinfo.size())
1399 + " Therefore the surplus columns will be ignored.");
1402 cellinfo[row][col].multi = CELL_PART_OF_MULTICOLUMN;
1403 cellinfo[row][col].align = 'c';
1406 } else if (col == 0 && colinfo.size() > 1 &&
1408 parse.next_token().cs() == "caption") {
1409 // longtable caption support in LyX is a hack:
1410 // Captions require a row of their own with
1411 // the caption flag set to true, having only
1412 // one multicolumn cell. The contents of that
1413 // cell must contain exactly one caption inset
1414 // and nothing else.
1415 // Fortunately, the caption flag is only needed
1416 // for tables with more than one column.
1417 rowinfo[row].caption = true;
1418 for (size_t c = 1; c < cells.size(); ++c) {
1419 if (!cells[c].empty()) {
1420 warning_message("Moving cell content '"
1422 + "' into the caption cell. "
1423 "This will probably not work.");
1424 cells[0] += cells[c];
1428 cellinfo[row][col].align = colinfo[col].align;
1429 cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN;
1431 parse_text_in_inset(parse, os2, FLAG_CELL, false, newcontext);
1432 cellinfo[row][col].content += os2.str();
1433 // add dummy multicolumn cells
1434 for (size_t c = 1; c < colinfo.size(); ++c)
1435 cellinfo[row][c].multi = CELL_PART_OF_MULTICOLUMN;
1436 } else if (col == 0 && colinfo.size() > 1 &&
1438 parse.next_token().cs() == "caption") {
1439 // longtable caption support in LyX is a hack:
1440 // Captions require a row of their own with
1441 // the caption flag set to true, having only
1442 // one multicolumn cell. The contents of that
1443 // cell must contain exactly one caption inset
1444 // and nothing else.
1445 // Fortunately, the caption flag is only needed
1446 // for tables with more than one column.
1447 rowinfo[row].caption = true;
1448 for (size_t c = 1; c < cells.size(); ++c) {
1449 if (!cells[c].empty()) {
1450 warning_message("Moving cell content '"
1452 + "' into the caption cell. "
1453 "This will probably not work.");
1454 cells[0] += cells[c];
1458 cellinfo[row][col].align = colinfo[col].align;
1459 cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN;
1461 parse_text_in_inset(parse, os2, FLAG_CELL, false, newcontext);
1462 cellinfo[row][col].content += os2.str();
1463 // add dummy multicolumn cells
1464 for (size_t c = 1; c < colinfo.size(); ++c)
1465 cellinfo[row][c].multi = CELL_PART_OF_MULTICOLUMN;
1468 parse_cell_content(os2, parse, FLAG_CELL, newcontext,
1471 cellinfo[row][col].leftlines = colinfo[col].leftlines;
1472 cellinfo[row][col].rightlines = colinfo[col].rightlines;
1473 cellinfo[row][col].align = colinfo[col].align;
1474 cellinfo[row][col].content += os2.str();
1478 //warning_message("// handle almost empty last row what we have");
1479 // handle almost empty last row
1480 if (row && lines[row].empty() && row + 1 == rowinfo.size()) {
1481 //warning_message("remove empty last line");
1482 if (rowinfo[row].topline)
1483 rowinfo[row - 1].bottomline = true;
1484 for (size_t col = 0; col < colinfo.size(); ++col)
1485 if (cellinfo[row][col].topline)
1486 cellinfo[row - 1][col].bottomline = true;
1493 // Now we have the table structure and content in rowinfo, colinfo
1495 // Unfortunately LyX has some limitations that we need to work around.
1498 for (size_t row = 0; row < rowinfo.size(); ++row) {
1499 for (size_t col = 0; col < cellinfo[row].size(); ++col) {
1500 // Convert cells with special content to multicolumn cells
1501 // (LyX ignores the special field for non-multicolumn cells).
1502 if (cellinfo[row][col].multi == CELL_NORMAL &&
1503 !cellinfo[row][col].special.empty())
1504 cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN;
1505 // Add multirow dummy cells
1506 if (row > 1 && (cellinfo[row - 1][col].multi == CELL_PART_OF_MULTIROW
1507 || cellinfo[row - 1][col].multi == CELL_BEGIN_OF_MULTIROW)
1508 && cellinfo[row - 1][col].mrxnum > 0) {
1509 // add dummy cells for multirow
1510 cellinfo[row][col].multi = CELL_PART_OF_MULTIROW;
1511 cellinfo[row][col].align = 'c';
1512 cellinfo[row][col].mrxnum = cellinfo[row - 1][col].mrxnum - 1;
1517 // Distribute lines from rows/columns to cells
1518 // The code was stolen from convert_tablines() in lyx2lyx/lyx_1_6.py.
1519 // Each standard cell inherits the settings of the corresponding
1520 // rowinfo/colinfo. This works because all cells with individual
1521 // settings were converted to multicolumn cells above.
1522 // Each multicolumn cell inherits the settings of the rowinfo/colinfo
1523 // corresponding to the first column of the multicolumn cell (default
1524 // of the multicol package). This works because the special field
1525 // overrides the line fields.
1526 for (size_t row = 0; row < rowinfo.size(); ++row) {
1527 for (size_t col = 0; col < cellinfo[row].size(); ++col) {
1528 if (cellinfo[row][col].multi == CELL_NORMAL) {
1529 cellinfo[row][col].topline = rowinfo[row].topline;
1530 cellinfo[row][col].bottomline = rowinfo[row].bottomline;
1531 cellinfo[row][col].leftlines = colinfo[col].leftlines;
1532 cellinfo[row][col].rightlines = colinfo[col].rightlines;
1533 } else if (cellinfo[row][col].multi == CELL_BEGIN_OF_MULTICOLUMN) {
1535 while (s < cellinfo[row].size() &&
1536 cellinfo[row][s].multi == CELL_PART_OF_MULTICOLUMN)
1538 if (s < cellinfo[row].size() &&
1539 cellinfo[row][s].multi != CELL_BEGIN_OF_MULTICOLUMN)
1540 cellinfo[row][col].rightlines = colinfo[col].rightlines;
1541 if (col > 0 && cellinfo[row][col-1].multi == CELL_NORMAL)
1542 cellinfo[row][col].leftlines = colinfo[col].leftlines;
1543 } else if (cellinfo[row][col].multi == CELL_BEGIN_OF_MULTIROW) {
1545 while (s < rowinfo.size() &&
1546 cellinfo[s][col].multi == CELL_PART_OF_MULTIROW)
1548 if (s < cellinfo[row].size() &&
1549 cellinfo[s][col].multi != CELL_BEGIN_OF_MULTIROW)
1550 cellinfo[row][col].bottomline = rowinfo[row].bottomline;
1551 if (row > 0 && cellinfo[row - 1][col].multi == CELL_NORMAL)
1552 cellinfo[row][col].topline = rowinfo[row].topline;
1558 preamble.registerAutomaticallyLoadedPackage("booktabs");
1559 if (name == "longtable")
1560 preamble.registerAutomaticallyLoadedPackage("longtable");
1561 else if (name == "xltabular")
1562 preamble.registerAutomaticallyLoadedPackage("xltabular");
1563 else if (name == "tabularx")
1564 preamble.registerAutomaticallyLoadedPackage("tabularx");
1566 //warning_message("output what we have");
1567 // output what we have
1568 size_type cols = colinfo.size();
1569 for (auto const & col : colinfo) {
1570 if (col.decimal_point != '\0' && col.align != 'd')
1573 os << "\n<lyxtabular version=\"3\" rows=\"" << rowinfo.size()
1574 << "\" columns=\"" << cols << "\">\n";
1576 << write_attribute("rotate", context.tablerotation)
1577 << write_attribute("booktabs", booktabs)
1578 << write_attribute("islongtable", is_long_tabular);
1579 if (is_long_tabular) {
1580 os << write_attribute("firstHeadTopDL", endfirsthead.topDL)
1581 << write_attribute("firstHeadBottomDL", endfirsthead.bottomDL)
1582 << write_attribute("firstHeadEmpty", endfirsthead.empty)
1583 << write_attribute("headTopDL", endhead.topDL)
1584 << write_attribute("headBottomDL", endhead.bottomDL)
1585 << write_attribute("footTopDL", endfoot.topDL)
1586 << write_attribute("footBottomDL", endfoot.bottomDL)
1587 << write_attribute("lastFootTopDL", endlastfoot.topDL)
1588 << write_attribute("lastFootBottomDL", endlastfoot.bottomDL)
1589 << write_attribute("lastFootEmpty", endlastfoot.empty);
1590 if (!halign.empty())
1591 os << write_attribute("longtabularalignment", halign);
1593 os << write_attribute("tabularvalignment", tabularvalignment);
1595 os << write_attribute("tabularwidth", tabularwidth) << ">\n";
1597 //warning_message("after header");
1598 for (auto const & col : colinfo) {
1599 if (col.decimal_point != '\0' && col.align != 'd')
1601 os << "<column alignment=\""
1602 << verbose_align(col.align) << "\"";
1603 if (col.decimal_point != '\0')
1604 os << " decimal_point=\"" << col.decimal_point << "\"";
1605 os << " valignment=\""
1606 << verbose_valign(col.valign) << "\""
1607 << write_attribute("width", translate_len(col.width))
1608 << write_attribute("special", col.special)
1609 << write_attribute("varwidth", col.varwidth)
1612 //warning_message("after cols");
1614 for (size_t row = 0; row < rowinfo.size(); ++row) {
1616 << write_attribute("topspace", rowinfo[row].top_space)
1617 << write_attribute("bottomspace", rowinfo[row].bottom_space)
1618 << write_attribute("interlinespace", rowinfo[row].interline_space)
1619 << write_attribute("endhead",
1620 rowinfo[row].type == LT_HEAD)
1621 << write_attribute("endfirsthead",
1622 rowinfo[row].type == LT_FIRSTHEAD)
1623 << write_attribute("endfoot",
1624 rowinfo[row].type == LT_FOOT)
1625 << write_attribute("endlastfoot",
1626 rowinfo[row].type == LT_LASTFOOT)
1627 << write_attribute("newpage", rowinfo[row].newpage)
1628 << write_attribute("caption", rowinfo[row].caption)
1630 for (size_t col = 0; col < colinfo.size(); ++col) {
1631 CellInfo const & cell = cellinfo[row][col];
1632 if (colinfo[col].decimal_point != '\0' && colinfo[col].align != 'd')
1633 // These are the second columns in a salign pair. Skip.
1636 if ((cell.multi == CELL_BEGIN_OF_MULTICOLUMN
1637 || cell.multi == CELL_PART_OF_MULTICOLUMN)
1638 && colinfo[col].align != 'd')
1639 os << " multicolumn=\"" << cell.multi << "\"";
1640 if (cell.multi == CELL_BEGIN_OF_MULTIROW
1641 || cell.multi == CELL_PART_OF_MULTIROW)
1642 os << " multirow=\"" << cell.multi << "\"";
1643 os << " alignment=\"" << verbose_align(cell.align)
1645 << " valignment=\"" << verbose_valign(cell.valign)
1647 << write_attribute("topline", cell.topline)
1648 << write_attribute("toplineltrim", cell.topline_ltrim)
1649 << write_attribute("toplinertrim", cell.topline_rtrim)
1650 << write_attribute("bottomline", cell.bottomline)
1651 << write_attribute("bottomlineltrim", cell.bottomline_ltrim)
1652 << write_attribute("bottomlinertrim", cell.bottomline_rtrim)
1653 << write_attribute("leftline", cell.leftlines > 0)
1654 << write_attribute("rightline", cell.rightlines > 0)
1655 << write_attribute("rotate", cell.rotate)
1656 << write_attribute("mroffset", cell.mroffset);
1657 //warning_message("\nrow: " + row + " col: " + col);
1659 // warning_message(" topline=\"true\"");
1660 //if (cell.bottomline)
1661 // warning_message(" bottomline=\"true\"");
1662 os << " usebox=\"none\""
1663 << write_attribute("width", translate_len(cell.width));
1664 if (cell.multi != CELL_NORMAL)
1665 os << write_attribute("special", cell.special);
1667 << "\n\\begin_inset Text\n"
1669 << "\n\\end_inset\n"
1675 os << "</lyxtabular>\n";