X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ftex2lyx%2Ftable.cpp;h=3cce03df4308561040fb9a1b93018fa64fbfb2ff;hb=237193f8a888777192981136469a5d4febb8e9d8;hp=dddf71df6a7eeb0221b8e5b7c1b3fea1cd8808bb;hpb=c0b20c1c92da3832d3f941120100fd95fa3f7755;p=lyx.git diff --git a/src/tex2lyx/table.cpp b/src/tex2lyx/table.cpp index dddf71df6a..3cce03df43 100644 --- a/src/tex2lyx/table.cpp +++ b/src/tex2lyx/table.cpp @@ -16,10 +16,13 @@ #include "tex2lyx.h" +#include "Preamble.h" + #include "support/lassert.h" #include "support/convert.h" #include "support/lstrings.h" +#include #include #include #include @@ -29,9 +32,6 @@ using namespace std; namespace lyx { -// filled in preamble.cpp -map special_columns; - namespace { @@ -72,27 +72,43 @@ enum LTRowType class RowInfo { public: RowInfo() : topline(false), bottomline(false), type(LT_NORMAL), - newpage(false) {} + caption(false), newpage(false) {} + /// Does this row have any special setting? + bool special() const + { + return topline || bottomline || !top_space.empty() || + !bottom_space.empty() || !interline_space.empty() || + type != LT_NORMAL || caption || newpage; + } /// horizontal line above bool topline; /// horizontal line below bool bottomline; + /// Extra space between the top line and this row + string top_space; + /// Extra space between this row and the bottom line + string bottom_space; + /// Extra space between the bottom line and the next top line + string interline_space; /// These are for longtabulars only /// row type (head, foot, firsthead etc.) LTRowType type; + /// row for a caption + bool caption; /// row for a newpage bool newpage; }; +/// the numeric values are part of the file format! enum Multicolumn { /// A normal cell CELL_NORMAL = 0, /// A multicolumn cell. The number of columns is 1 + number /// of CELL_PART_OF_MULTICOLUMN cells that follow directly - CELL_BEGIN_OF_MULTICOLUMN, + CELL_BEGIN_OF_MULTICOLUMN = 1, /// This is a dummy cell (part of a multicolumn cell) - CELL_PART_OF_MULTICOLUMN + CELL_PART_OF_MULTICOLUMN = 2 }; @@ -100,7 +116,7 @@ class CellInfo { public: CellInfo() : multi(CELL_NORMAL), align('n'), valign('n'), leftlines(0), rightlines(0), topline(false), - bottomline(false), rotate(false) {} + bottomline(false), rotate(0) {} /// cell content string content; /// multicolumn flag @@ -117,8 +133,8 @@ public: bool topline; /// do we have a line below? bool bottomline; - /// is the cell rotated? - bool rotate; + /// how is the cell rotated? + int rotate; /// width for multicolumn cells string width; /// special formatting for multicolumn cells @@ -126,6 +142,23 @@ public: }; +class ltType { +public: + // constructor + ltType() : set(false), topDL(false), bottomDL(false), empty(false) {} + // we have this header type (is set in the getLT... functions) + bool set; + // double borders on top + bool topDL; + // double borders on bottom + bool bottomDL; + // used for FirstHeader & LastFooter and if this is true + // all the rows marked as FirstHeader or LastFooter are + // ignored in the output and it is set to be empty! + bool empty; +}; + + /// translate a horizontal alignment (as stored in ColInfo and CellInfo) to LyX inline char const * verbose_align(char c) { @@ -174,6 +207,14 @@ string const write_attribute(string const & name, string const & s) } +string const write_attribute(string const & name, int const & i) +{ + // we write only true attribute values so we remove a bit of the + // file format bloat for tabulars. + return i ? write_attribute(name, convert(i)) : string(); +} + + /*! rather brutish way to code table structure in a string: \verbatim @@ -318,7 +359,7 @@ void handle_colalign(Parser & p, vector & colinfo, break; case '>': { // text before the next column - string const s = trim(p.verbatim_item()); + string const s = trimSpaceAndEol(p.verbatim_item()); if (next.special.empty() && next.align == 'n') { // Maybe this can be converted to a @@ -338,7 +379,7 @@ void handle_colalign(Parser & p, vector & colinfo, } case '<': { // text after the last column - string const s = trim(p.verbatim_item()); + string const s = trimSpaceAndEol(p.verbatim_item()); if (colinfo.empty()) // This is not possible in LaTeX. cerr << "Ignoring separator '<{" @@ -377,25 +418,22 @@ void handle_colalign(Parser & p, vector & colinfo, next.special += t.character(); next.special += '{' + p.verbatim_item() + '}'; break; - default: + default: { // try user defined column types - if (special_columns.find(t.character()) != - special_columns.end()) { - ci2special(next); - next.special += t.character(); - int const nargs = - special_columns[t.character()]; - for (int i = 0; i < nargs; ++i) - next.special += '{' + - p.verbatim_item() + - '}'; - colinfo.push_back(next); - next = ColInfo(); - } else - cerr << "Ignoring column specification" - " '" << t << "'." << endl; + // unknown column types (nargs == -1) are + // assumed to consume no arguments + ci2special(next); + next.special += t.character(); + int const nargs = + preamble.getSpecialTableColumnArguments(t.character()); + for (int i = 0; i < nargs; ++i) + next.special += '{' + + p.verbatim_item() + '}'; + colinfo.push_back(next); + next = ColInfo(); break; } + } } // Maybe we have some column separators that need to be added to the @@ -468,12 +506,35 @@ bool parse_hlines(Parser & p, Token const & t, string & hlines, { LASSERT(t.cat() == catEscape, return false); - if (t.cs() == "hline") - hlines += "\\hline"; + if (t.cs() == "hline" || t.cs() == "toprule" || t.cs() == "midrule" || + t.cs() == "bottomrule") + hlines += '\\' + t.cs(); else if (t.cs() == "cline") hlines += "\\cline{" + p.verbatim_item() + '}'; + else if (t.cs() == "cmidrule") { + // We cannot handle the \cmidrule(l){3-4} form + p.pushPosition(); + p.skip_spaces(true); + bool const hasParentheses(p.getFullArg('(', ')').first); + p.popPosition(); + if (hasParentheses) + return false; + hlines += "\\cmidrule{" + p.verbatim_item() + '}'; + } + + else if (t.cs() == "addlinespace") { + p.pushPosition(); + p.skip_spaces(true); + bool const hasArgument(p.getFullArg('{', '}').first); + p.popPosition(); + if (hasArgument) + hlines += "\\addlinespace{" + p.verbatim_item() + '}'; + else + hlines += "\\addlinespace"; + } + else if (is_long_tabular && t.cs() == "newpage") hlines += "\\newpage"; @@ -517,7 +578,7 @@ void parse_table(Parser & p, ostream & os, bool is_long_tabular, Token const & t = p.get_token(); #ifdef FILEDEBUG - cerr << "t: " << t << " flags: " << flags << "\n"; + debugToken(cerr, t, flags); #endif // comments and whitespace in hlines @@ -607,7 +668,6 @@ void parse_table(Parser & p, ostream & os, bool is_long_tabular, } continue; } - } // We need a HLINE separator if we either have no hline @@ -625,14 +685,20 @@ void parse_table(Parser & p, ostream & os, bool is_long_tabular, pos = IN_COLUMNS; break; case IN_HLINES_END: - // Oops, there is still cell content after hline - // stuff. This does not work in LaTeX, so we ignore - // the hlines. - cerr << "Ignoring '" << hlines << "' in a cell" - << endl; + // Oops, there is still cell content or unsupported + // booktabs commands after hline stuff. The latter are + // moved to the cell, and the first does not work in + // LaTeX, so we ignore the hlines. os << comments; - hlines.erase(); comments.erase(); + if (support::contains(hlines, "\\hline") || + support::contains(hlines, "\\cline") || + support::contains(hlines, "\\newpage")) + cerr << "Ignoring '" << hlines + << "' in a cell" << endl; + else + os << hlines; + hlines.erase(); pos = IN_COLUMNS; break; case IN_COLUMNS: @@ -663,13 +729,13 @@ void parse_table(Parser & p, ostream & os, bool is_long_tabular, } } - else if (t.cat() == catSpace + else if (t.cat() == catSpace || t.cat() == catNewline - || t.cat() == catLetter - || t.cat() == catSuper - || t.cat() == catSub - || t.cat() == catOther - || t.cat() == catActive + || t.cat() == catLetter + || t.cat() == catSuper + || t.cat() == catSub + || t.cat() == catOther + || t.cat() == catActive || t.cat() == catParameter) os << t.cs(); @@ -715,7 +781,7 @@ void parse_table(Parser & p, ostream & os, bool is_long_tabular, // treat the nested environment as a block, don't // parse &, \\ etc, because they don't belong to our // table if they appear. - os << p.verbatimEnvironment(name); + os << p.ertEnvironment(name); os << "\\end{" << name << '}'; active_environments.pop_back(); } @@ -767,17 +833,25 @@ void handle_hline_below(RowInfo & ri, vector & ci) } // anonymous namespace -void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, - Context & context) +void handle_tabular(Parser & p, ostream & os, string const & name, + string const & tabularwidth, Context & context) { + bool const is_long_tabular(name == "longtable"); + bool booktabs = false; + string tabularvalignment("middle"); string posopts = p.getOpt(); if (!posopts.empty()) { // FIXME: Convert this to ERT if (is_long_tabular) - cerr << "horizontal longtable"; + cerr << "horizontal longtable positioning '" + << posopts << "' ignored\n"; + else if (posopts == "[t]") + tabularvalignment = "top"; + else if (posopts == "[b]") + tabularvalignment = "bottom"; else - cerr << "vertical tabular"; - cerr << " positioning '" << posopts << "' ignored\n"; + cerr << "vertical tabular positioning '" + << posopts << "' ignored\n"; } vector colinfo; @@ -797,13 +871,18 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, vector< vector > cellinfo(lines.size()); vector rowinfo(lines.size()); + ltType endfirsthead; + ltType endhead; + ltType endfoot; + ltType endlastfoot; // split into rows //cerr << "// split into rows\n"; - for (size_t row = 0; row < rowinfo.size(); ++row) { + for (size_t row = 0; row < rowinfo.size();) { // init row cellinfo[row].resize(colinfo.size()); + bool deletelastrow = false; // split row vector dummy; @@ -831,13 +910,18 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, while (p1.good()) { Token t = p1.get_token(); //cerr << "read token: " << t << "\n"; - if (t.cs() == "hline") { + if (t.cs() == "hline" || t.cs() == "toprule" || + t.cs() == "midrule" || + t.cs() == "bottomrule") { + if (t.cs() != "hline") + booktabs = true; if (i == 0) { if (rowinfo[row].topline) { if (row > 0) // extra bottomline above handle_hline_below(rowinfo[row - 1], cellinfo[row - 1]); else - cerr << "dropping extra hline\n"; + cerr << "dropping extra " + << t.cs() << '\n'; //cerr << "below row: " << row-1 << endl; } else { handle_hline_above(rowinfo[row], cellinfo[row]); @@ -847,37 +931,39 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, //cerr << "below row: " << row << endl; handle_hline_below(rowinfo[row], cellinfo[row]); } - } else if (t.cs() == "cline") { + } else if (t.cs() == "cline" || t.cs() == "cmidrule") { + if (t.cs() == "cmidrule") + booktabs = true; string arg = p1.verbatim_item(); - //cerr << "read cline arg: '" << arg << "'\n"; - vector t; - split(arg, t, '-'); - t.resize(2); - size_t from = convert(t[0]); + //cerr << "read " << t.cs() << " arg: '" << arg << "'\n"; + vector cols; + split(arg, cols, '-'); + cols.resize(2); + size_t from = convert(cols[0]); if (from == 0) cerr << "Could not parse " - "cline start column." + << t.cs() << " start column." << endl; else // 1 based index -> 0 based --from; if (from >= colinfo.size()) { - cerr << "cline starts at non " - "existing column " + cerr << t.cs() << " starts at " + "non existing column " << (from + 1) << endl; from = colinfo.size() - 1; } - size_t to = convert(t[1]); + size_t to = convert(cols[1]); if (to == 0) cerr << "Could not parse " - "cline end column." + << t.cs() << " end column." << endl; else // 1 based index -> 0 based --to; if (to >= colinfo.size()) { - cerr << "cline ends at non " - "existing column " + cerr << t.cs() << " ends at " + "non existing column " << (to + 1) << endl; to = colinfo.size() - 1; } @@ -891,38 +977,74 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, cellinfo[row][col].bottomline = true; } } + } else if (t.cs() == "addlinespace") { + booktabs = true; + string const opt = p.next_token().cat() == catBegin ? + p.verbatim_item() : string(); + if (i == 0) { + if (opt.empty()) + rowinfo[row].top_space = "default"; + else + rowinfo[row].top_space = translate_len(opt); + } else if (rowinfo[row].bottomline) { + if (opt.empty()) + rowinfo[row].bottom_space = "default"; + else + rowinfo[row].bottom_space = translate_len(opt); + } else { + if (opt.empty()) + rowinfo[row].interline_space = "default"; + else + rowinfo[row].interline_space = translate_len(opt); + } } else if (t.cs() == "endhead") { - if (i > 0) + if (i == 0) + endhead.empty = true; + else rowinfo[row].type = LT_HEAD; for (int r = row - 1; r >= 0; --r) { if (rowinfo[r].type != LT_NORMAL) break; rowinfo[r].type = LT_HEAD; + endhead.empty = false; } + endhead.set = true; } else if (t.cs() == "endfirsthead") { - if (i > 0) + if (i == 0) + endfirsthead.empty = true; + else rowinfo[row].type = LT_FIRSTHEAD; for (int r = row - 1; r >= 0; --r) { if (rowinfo[r].type != LT_NORMAL) break; rowinfo[r].type = LT_FIRSTHEAD; + endfirsthead.empty = false; } + endfirsthead.set = true; } else if (t.cs() == "endfoot") { - if (i > 0) + if (i == 0) + endfoot.empty = true; + else rowinfo[row].type = LT_FOOT; for (int r = row - 1; r >= 0; --r) { if (rowinfo[r].type != LT_NORMAL) break; rowinfo[r].type = LT_FOOT; + endfoot.empty = false; } + endfoot.set = true; } else if (t.cs() == "endlastfoot") { - if (i > 0) + if (i == 0) + endlastfoot.empty = true; + else rowinfo[row].type = LT_LASTFOOT; for (int r = row - 1; r >= 0; --r) { if (rowinfo[r].type != LT_NORMAL) break; rowinfo[r].type = LT_LASTFOOT; + endlastfoot.empty = false; } + endlastfoot.set = true; } else if (t.cs() == "newpage") { if (i == 0) { if (row > 0) @@ -941,6 +1063,48 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, } } + // LyX ends headers and footers always with \tabularnewline. + // This causes one additional row in the output. + // If the last row of a header/footer is empty, we can work + // around that by removing it. + if (row > 1) { + RowInfo test = rowinfo[row-1]; + test.type = LT_NORMAL; + if (lines[row-1].empty() && !test.special()) { + switch (rowinfo[row-1].type) { + case LT_FIRSTHEAD: + if (rowinfo[row].type != LT_FIRSTHEAD && + rowinfo[row-2].type == LT_FIRSTHEAD) + deletelastrow = true; + break; + case LT_HEAD: + if (rowinfo[row].type != LT_HEAD && + rowinfo[row-2].type == LT_HEAD) + deletelastrow = true; + break; + case LT_FOOT: + if (rowinfo[row].type != LT_FOOT && + rowinfo[row-2].type == LT_FOOT) + deletelastrow = true; + break; + case LT_LASTFOOT: + if (rowinfo[row].type != LT_LASTFOOT && + rowinfo[row-2].type == LT_LASTFOOT) + deletelastrow = true; + break; + case LT_NORMAL: + break; + } + } + } + + if (deletelastrow) { + lines.erase(lines.begin() + (row - 1)); + rowinfo.erase(rowinfo.begin() + (row - 1)); + cellinfo.erase(cellinfo.begin() + (row - 1)); + continue; + } + // split into cells vector cells; split(lines[row], cells, TAB); @@ -965,6 +1129,7 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, // special cell properties alignment vector t; handle_colalign(p, t, ColInfo()); + p.skip_spaces(true); ColInfo & ci = t.front(); // The logic of LyX for multicolumn vertical @@ -994,18 +1159,93 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, cellinfo[row][col].content += os.str(); // add dummy cells for multicol - for (size_t i = 0; i < ncells - 1 && col < colinfo.size(); ++i) { + for (size_t i = 0; i < ncells - 1; ++i) { ++col; + if (col >= colinfo.size()) { + cerr << "The cell '" + << cells[cell] + << "' specifies " + << convert(ncells) + << " columns while the table has only " + << convert(colinfo.size()) + << " columns!" + << " Therefore the surplus columns will be ignored." + << endl; + break; + } cellinfo[row][col].multi = CELL_PART_OF_MULTICOLUMN; cellinfo[row][col].align = 'c'; } + } else if (col == 0 && colinfo.size() > 1 && + is_long_tabular && + p.next_token().cs() == "caption") { + // longtable caption support in LyX is a hack: + // Captions require a row of their own with + // the caption flag set to true, having only + // one multicolumn cell. The contents of that + // cell must contain exactly one caption inset + // and nothing else. + // Fortunately, the caption flag is only needed + // for tables with more than one column. + rowinfo[row].caption = true; + for (size_t c = 1; c < cells.size(); ++c) { + if (!cells[c].empty()) { + cerr << "Moving cell content '" + << cells[c] + << "' into the caption cell. " + "This will probably not work." + << endl; + cells[0] += cells[c]; + } + } + cells.resize(1); + cellinfo[row][col].align = colinfo[col].align; + cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN; + ostringstream os; + parse_text_in_inset(p, os, FLAG_CELL, false, context); + cellinfo[row][col].content += os.str(); + // add dummy multicolumn cells + for (size_t c = 1; c < colinfo.size(); ++c) + cellinfo[row][c].multi = CELL_PART_OF_MULTICOLUMN; } else { + bool turn = false; + int rotate = 0; + if (p.next_token().cs() == "begin") { + p.pushPosition(); + p.get_token(); + string const env = p.getArg('{', '}'); + if (env == "sideways" || env == "turn") { + string angle = "90"; + if (env == "turn") { + turn = true; + angle = p.getArg('{', '}'); + } + active_environments.push_back(env); + p.ertEnvironment(env); + active_environments.pop_back(); + p.skip_spaces(); + if (!p.good() && support::isStrInt(angle)) + rotate = convert(angle); + } + p.popPosition(); + } cellinfo[row][col].leftlines = colinfo[col].leftlines; cellinfo[row][col].rightlines = colinfo[col].rightlines; cellinfo[row][col].align = colinfo[col].align; ostringstream os; - parse_text_in_inset(p, os, FLAG_CELL, false, context); + if (rotate != 0) { + cellinfo[row][col].rotate = rotate; + p.get_token(); + active_environments.push_back(p.getArg('{', '}')); + if (turn) + p.getArg('{', '}'); + parse_text_in_inset(p, os, FLAG_END, false, context); + active_environments.pop_back(); + preamble.registerAutomaticallyLoadedPackage("rotating"); + } else { + parse_text_in_inset(p, os, FLAG_CELL, false, context); + } cellinfo[row][col].content += os.str(); } } @@ -1021,6 +1261,8 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, cellinfo[row - 1][col].bottomline = true; rowinfo.pop_back(); } + + ++row; } // Now we have the table structure and content in rowinfo, colinfo @@ -1037,14 +1279,65 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, } } + // Distribute lines from rows/columns to cells + // The code was stolen from convert_tablines() in lyx2lyx/lyx_1_6.py. + // Each standard cell inherits the settings of the corresponding + // rowinfo/colinfo. This works because all cells with individual + // settings were converted to multicolumn cells above. + // Each multicolumn cell inherits the settings of the rowinfo/colinfo + // corresponding to the first column of the multicolumn cell (default + // of the multicol package). This works because the special field + // overrides the line fields. + for (size_t row = 0; row < rowinfo.size(); ++row) { + for (size_t col = 0; col < cellinfo[row].size(); ++col) { + if (cellinfo[row][col].multi == CELL_NORMAL) { + cellinfo[row][col].topline = rowinfo[row].topline; + cellinfo[row][col].bottomline = rowinfo[row].bottomline; + cellinfo[row][col].leftlines = colinfo[col].leftlines; + cellinfo[row][col].rightlines = colinfo[col].rightlines; + } else if (cellinfo[row][col].multi == CELL_BEGIN_OF_MULTICOLUMN) { + size_t s = col + 1; + while (s < cellinfo[row].size() && + cellinfo[row][s].multi == CELL_PART_OF_MULTICOLUMN) + s++; + if (s < cellinfo[row].size() && + cellinfo[row][s].multi != CELL_BEGIN_OF_MULTICOLUMN) + cellinfo[row][col].rightlines = colinfo[col].rightlines; + if (col > 0 && cellinfo[row][col-1].multi == CELL_NORMAL) + cellinfo[row][col].leftlines = colinfo[col].leftlines; + } + } + } + + if (booktabs) + preamble.registerAutomaticallyLoadedPackage("booktabs"); + if (is_long_tabular) + preamble.registerAutomaticallyLoadedPackage("longtable"); + //cerr << "// output what we have\n"; // output what we have + string const rotate = "0"; os << "\n\n"; os << "\n"; + << write_attribute("rotate", rotate) + << write_attribute("booktabs", booktabs) + << write_attribute("islongtable", is_long_tabular); + if (is_long_tabular) { + os << write_attribute("firstHeadTopDL", endfirsthead.topDL) + << write_attribute("firstHeadBottomDL", endfirsthead.bottomDL) + << write_attribute("firstHeadEmpty", endfirsthead.empty) + << write_attribute("headTopDL", endhead.topDL) + << write_attribute("headBottomDL", endhead.bottomDL) + << write_attribute("footTopDL", endfoot.topDL) + << write_attribute("footBottomDL", endfoot.bottomDL) + << write_attribute("lastFootTopDL", endlastfoot.topDL) + << write_attribute("lastFootBottomDL", endlastfoot.bottomDL) + << write_attribute("lastFootEmpty", endlastfoot.empty); + } else + os << write_attribute("tabularvalignment", tabularvalignment) + << write_attribute("tabularwidth", tabularwidth); + os << ">\n"; //cerr << "// after header\n"; for (size_t col = 0; col < colinfo.size(); ++col) { @@ -1052,8 +1345,6 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, << verbose_align(colinfo[col].align) << "\"" << " valignment=\"" << verbose_valign(colinfo[col].valign) << "\"" - << write_attribute("leftline", colinfo[col].leftlines > 0) - << write_attribute("rightline", colinfo[col].rightlines > 0) << write_attribute("width", translate_len(colinfo[col].width)) << write_attribute("special", colinfo[col].special) << ">\n"; @@ -1062,8 +1353,9 @@ void handle_tabular(Parser & p, ostream & os, bool is_long_tabular, for (size_t row = 0; row < rowinfo.size(); ++row) { os << "\n"; for (size_t col = 0; col < colinfo.size(); ++col) { CellInfo const & cell = cellinfo[row][col];