Here comes the long promised patch that fixes tabular column parsing in tex2lyx. You can try it with the attached example.
With this patch, tex2lyx is able to parse all standard LaTeX column specifications (including those of array.sty). Vertical lines in multicolumn cells should also be fixed. I had to put all multicolumn specifications in the special field, because the logic in LyXTabular::TeXCellPreamble() is too complicated to reproduce it in tex2lyx, and the original attempt with last_rightline did not work in all cases. In general I made the data structures a bit more general than LyX allows it. This made it possible to separate 1) the parsing step and 2) the adjustment to LyX limitations. We will be able to simplify (or even remove) the second step when the tabular code in LyX gets the long needed renovation. OK to apply? My further plans are: - improve row handling (hlines etc) in a similar way - complete the longtable conversion - fix nested tables crash After these changes tex2lyx should work for tables as well as for normal text. Georg
diff -p -r -U 3 -X excl.tmp lyx-1.4-clean/src/tex2lyx/ChangeLog lyx-1.4-cvs/src/tex2lyx/ChangeLog --- lyx-1.4-clean/src/tex2lyx/ChangeLog 2005-02-26 15:52:21.000000000 +0100 +++ lyx-1.4-cvs/src/tex2lyx/ChangeLog 2005-03-06 11:49:28.000000000 +0100 @@ -1,3 +1,18 @@ +2005-03-06 Georg Baum <[EMAIL PROTECTED]> + + * table.C (verbose_valign): new + * table.C (string2int): remove, use convert<int>() instead since the + default value is always 0 and this is also the default of + convert<int>(). + * table.C (ci2special): new + * table.C (handle_colalign): handle b{}, p{} and m{} correctly + * table.C (handle_colalign): fix >{\raggedleft} and >{\raggedright} + (they were swapped) + * table.C (handle_colalign): handle >{\centering} + * table.C (handle_colalign): handle <{}, *{}, @{} and !{} + * table.C (fix_colalign): new + * table.C (handle_tabular): fix multicolumns with vertical lines + 2005-02-25 Angus Leeming <[EMAIL PROTECTED]> * context.h: declare as "class Font" rather than "struct Font". diff -p -r -U 3 -X excl.tmp lyx-1.4-clean/src/tex2lyx/table.C lyx-1.4-cvs/src/tex2lyx/table.C --- lyx-1.4-clean/src/tex2lyx/table.C 2005-02-26 15:52:21.000000000 +0100 +++ lyx-1.4-cvs/src/tex2lyx/table.C 2005-03-06 12:16:46.000000000 +0100 @@ -16,6 +16,9 @@ #include "tex2lyx.h" +#include "support/convert.h" +#include "support/lstrings.h" + #include <cctype> #include <fstream> #include <iostream> @@ -40,17 +43,19 @@ namespace { class ColInfo { public: - ColInfo() : align('c'), rightline(false), leftline(false) {} + ColInfo() : align('n'), valign('n'), rightlines(0), leftlines(0) {} /// column alignment char align; + /// vertical alignment + char valign; /// column width string width; /// special column alignment string special; - /// how many lines on the right? - int rightline; - /// a line on the left? - bool leftline; + /// number of lines on the right + int rightlines; + /// number of lines on the left + int leftlines; }; @@ -86,24 +91,44 @@ public: }; +enum Multicolumn { + /// A normal cell + CELL_NORMAL = 0, + /// A multicolumn cell. The number of columns is <tt>1 + number + /// of CELL_PART_OF_MULTICOLUMN cells</tt> that follow directly + CELL_BEGIN_OF_MULTICOLUMN, + /// This is a dummy cell (part of a multicolumn cell) + CELL_PART_OF_MULTICOLUMN +}; + + class CellInfo { public: - CellInfo() : multi(0), align('n'), leftline(false), rightline(false), - topline(false), bottomline(false) {} + CellInfo() : multi(CELL_NORMAL), align('n'), valign('n'), + leftlines(0), rightlines(0), topline(false), + bottomline(false), rotate(false) {} /// cell content string content; /// multicolumn flag - int multi; + Multicolumn multi; /// cell alignment char align; - /// do we have a line on the left? - bool leftline; - /// do we have a line on the right? - bool rightline; + /// vertical cell alignment + char valign; + /// number of lines on the left + int leftlines; + /// number of lines on the right + int rightlines; /// do we have a line above? bool topline; /// do we have a line below? bool bottomline; + /// is the cell rotated? + bool rotate; + /// width for multicolumn cells + string width; + /// special formatting for multicolumn cells + string special; }; @@ -114,6 +139,14 @@ inline char const * verbose_align(char c } +/// translate a vertical alignment (as stored in ColInfo and CellInfo) to LyX +inline char const * verbose_valign(char c) +{ + // The default value for no special alignment is "top". + return c == 'p' ? "top" : c == 'm' ? "middle" : c == 'b' ? "bottom" : "top"; +} + + // stripped down from tabluar.C. We use it currently only for bools and // strings string const write_attribute(string const & name, bool const & b) @@ -130,44 +163,93 @@ string const write_attribute(string cons } -int string2int(string const & s, int deflt = 0) -{ - istringstream is(s); - int i = deflt; - is >> i; - return i; -} - - -/* rather brutish way to code table structure in a string: +/*! rather brutish way to code table structure in a string: +\verbatim \begin{tabular}{ccc} 1 & 2 & 3\\ \hline \multicolumn{2}{c}{4} & 5 // 6 & 7 \\ + 8 \endhead \end{tabular} +\endverbatim gets "translated" to: - HLINE 1 TAB 2 TAB 3 HLINE HLINE LINE - \hline HLINE \multicolumn{2}{c}{4} TAB 5 HLINE HLINE LINE - HLINE 6 TAB 7 HLINE HLINE LINE -*/ +\verbatim + HLINE 1 TAB 2 TAB 3 HLINE HLINE LINE + \hline HLINE \multicolumn{2}{c}{4} TAB 5 HLINE HLINE LINE + HLINE 6 TAB 7 HLINE HLINE LINE + HLINE 8 HLINE \endhead HLINE LINE +\endverbatim + */ char const TAB = '\001'; char const LINE = '\002'; char const HLINE = '\004'; -/// handle column specifications for tabulars and multicolumns -void handle_colalign(Parser & p, vector<ColInfo> & colinfo) +/*! + * Move the information in leftlines, rightlines, align and valign to the + * special field. This is necessary if the special field is not empty, + * because LyX ignores leftlines, rightlines, align and valign in this case. + */ +void ci2special(ColInfo & ci) { - if (p.get_token().cat() != catBegin) - cerr << "wrong syntax for table column alignment. '{' expected\n"; + if (ci.width.empty() && ci.align == 'n') + // The alignment setting is already in special, since + // handle_colalign() never stores ci with these settings + // and ensures that leftlines == 0 and rightlines == 0 in + // this case. + return; + + if (!ci.width.empty()) { + switch (ci.align) { + case 'l': + ci.special += ">{\\raggedright}"; + case 'r': + ci.special += ">{\\raggedleft}"; + case 'c': + ci.special += ">{\\centering}"; + } + if (ci.valign == 'n') + ci.special += 'p'; + else + ci.special += ci.valign; + ci.special += '{' + ci.width + '}'; + ci.width.erase(); + } else + ci.special += ci.align; + + for (int i = 0; i < ci.leftlines; ++i) + ci.special.insert(0, "|"); + for (int i = 0; i < ci.rightlines; ++i) + ci.special += '|'; + ci.leftlines = 0; + ci.rightlines = 0; + ci.align = 'n'; + ci.valign = 'n'; +} + - char nextalign = 'b'; - bool leftline = false; - for (Token t=p.get_token(); p.good() && t.cat() != catEnd; t = p.get_token()){ +/*! + * Handle column specifications for tabulars and multicolumns. + * The next token of the parser \p p must be an opening brace, and we read + * everything until the matching closing brace. + * The resulting column specifications are filled into \p colinfo. This is + * in an intermediate form. fix_colalign() makes it suitable for LyX output. + */ +void handle_colalign(Parser & p, vector<ColInfo> & colinfo, + ColInfo const & start) +{ + if (p.get_token().cat() != catBegin) + cerr << "Wrong syntax for table column alignment.\n" + "Expected '{', got '" << p.curr_token().asInput() + << "'.\n"; + + ColInfo next = start; + for (Token t = p.get_token(); p.good() && t.cat() != catEnd; + t = p.get_token()) { #ifdef FILEDEBUG cerr << "t: " << t << " c: '" << t.character() << "'\n"; #endif @@ -185,57 +267,179 @@ void handle_colalign(Parser & p, vector< switch (t.character()) { case 'c': case 'l': - case 'r': { - ColInfo ci; - ci.align = t.character(); - if (colinfo.size() && colinfo.back().rightline > 1) { - ci.leftline = true; - --colinfo.back().rightline; - } - colinfo.push_back(ci); + case 'r': + // new column, horizontal aligned + next.align = t.character(); + if (!next.special.empty()) + ci2special(next); + colinfo.push_back(next); + next = ColInfo(); break; - } case 'p': - colinfo.push_back(ColInfo()); - colinfo.back().align = nextalign; - colinfo.back().width = p.verbatim_item(); - nextalign = 'b'; + case 'b': + case 'm': + // new column, vertical aligned box + next.valign = t.character(); + next.width = p.verbatim_item(); + if (!next.special.empty()) + ci2special(next); + colinfo.push_back(next); + next = ColInfo(); break; case '|': - if (colinfo.empty()) - leftline = true; + // vertical rule + if (colinfo.empty()) { + if (next.special.empty()) + ++next.leftlines; + else + next.special += '|'; + } else if (colinfo.back().special.empty()) + ++colinfo.back().rightlines; + else if (next.special.empty()) + ++next.leftlines; else - ++colinfo.back().rightline; + colinfo.back().special += '|'; break; case '>': { - string s = p.verbatim_item(); - if (s == "\\raggedleft ") - nextalign = 'l'; - else if (s == "\\raggedright ") - nextalign = 'r'; - else - cerr << "unknown '>' column '" << s << "'\n"; + // text before the next column + string const s = trim(p.verbatim_item()); + if (next.special.empty() && + next.align == 'n') { + // Maybe this can be converted to a + // horizontal alignment setting for + // fixed width columns + if (s == "\\raggedleft") + next.align = 'r'; + else if (s == "\\raggedright") + next.align = 'l'; + else if (s == "\\centering") + next.align = 'c'; + else + next.special = ">{" + s + '}'; + } else + next.special += ">{" + s + '}'; break; } - default: - if (special_columns.find(t.character()) != special_columns.end()) { - ColInfo ci; - ci.align = 'c'; - ci.special += t.character(); - int const nargs = special_columns[t.character()]; - for (int i = 0; i < nargs; ++i) - ci.special += "{" + p.verbatim_item() + "}"; - //cerr << "handling special column '" << t << "' " << nargs - // << " '" << ci.special << "'\n"; - colinfo.push_back(ci); + case '<': { + // text after the last column + string const s = trim(p.verbatim_item()); + if (colinfo.empty()) + // This is not possible in LaTeX. + cerr << "Ignoring separator '<{" + << s << "}'." << endl; + else { + ColInfo & ci = colinfo.back(); + ci2special(ci); + ci.special += "<{" + s + '}'; + } + break; + } + case '*': { + // *{n}{arg} means 'n' columns of type 'arg' + string const num = p.verbatim_item(); + string const arg = p.verbatim_item(); + size_t const n = convert<unsigned int>(num); + if (!arg.empty() && n > 0) { + string s("{"); + for (size_t i = 0; i < n; ++i) + s += arg; + s += '}'; + Parser p2(s); + handle_colalign(p2, colinfo, next); + next = ColInfo(); } else { - cerr << "ignoring special separator '" << t << "'\n"; + cerr << "Ignoring column specification" + " '*{" << num << "}{" + << arg << "}'." << endl; } break; } + case '@': + // text instead of the column spacing + case '!': + // text in addition to the column spacing + next.special += t.character(); + next.special += '{' + p.verbatim_item() + '}'; + break; + 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; + break; + } + } + + // Maybe we have some column separators that need to be added to the + // last column? + ci2special(next); + if (!next.special.empty()) { + ColInfo & ci = colinfo.back(); + ci2special(ci); + ci.special += next.special; + next.special.erase(); + } +} + + +/*! + * Move the left and right lines and alignment settings of the column \p ci + * to the special field if necessary. + */ +void fix_colalign(ColInfo & ci) +{ + if (ci.leftlines > 1 || ci.rightlines > 1) + ci2special(ci); +} + + +/*! + * LyX can't handle more than one vertical line at the left or right side + * of a column. + * This function moves the left and right lines and alignment settings of all + * columns in \p colinfo to the special field if necessary. + */ +void fix_colalign(vector<ColInfo> & colinfo) +{ + // Try to move extra leftlines to the previous column. + // We do this only if both special fields are empty, otherwise we + // can't tell wether the result will be the same. + for (size_t col = 0; col < colinfo.size(); ++col) { + if (colinfo[col].leftlines > 1 && + colinfo[col].special.empty() && col > 0 && + colinfo[col - 1].rightlines == 0 && + colinfo[col - 1].special.empty()) { + ++colinfo[col - 1].rightlines; + --colinfo[col].leftlines; + } } - if (colinfo.size() && leftline) - colinfo[0].leftline = true; + // Try to move extra rightlines to the next column + for (size_t col = 0; col < colinfo.size(); ++col) { + if (colinfo[col].rightlines > 1 && + colinfo[col].special.empty() && + col < colinfo.size() - 1 && + colinfo[col + 1].leftlines == 0 && + colinfo[col + 1].special.empty()) { + ++colinfo[col + 1].leftlines; + --colinfo[col].rightlines; + } + } + // Move the lines and alignment settings to the special field if + // necessary + for (size_t col = 0; col < colinfo.size(); ++col) + fix_colalign(colinfo[col]); } @@ -551,6 +755,7 @@ void handle_tabular(Parser & p, ostream { string posopts = p.getOpt(); if (!posopts.empty()) { + // FIXME: Convert this to ERT if (is_long_tabular) cerr << "horizontal longtable"; else @@ -558,10 +763,11 @@ void handle_tabular(Parser & p, ostream cerr << " positioning '" << posopts << "' ignored\n"; } - vector<ColInfo> colinfo; + vector<ColInfo> colinfo; // handle column formatting - handle_colalign(p, colinfo); + handle_colalign(p, colinfo, ColInfo()); + fix_colalign(colinfo); // first scan of cells // use table mode to keep it minimal-invasive @@ -630,14 +836,28 @@ void handle_tabular(Parser & p, ostream vector<string> t; split(arg, t, '-'); t.resize(2); - size_t from = string2int(t[0]) - 1; + size_t from = convert<unsigned int>(t[0]); + if (from == 0) + cerr << "Could not parse " + "cline start column." + << endl; + else + // 1 based index -> 0 based + --from; if (from >= colinfo.size()) { cerr << "cline starts at non " "existing column " << (from + 1) << endl; from = colinfo.size() - 1; } - size_t to = string2int(t[1]) - 1; + size_t to = convert<unsigned int>(t[1]); + if (to == 0) + cerr << "Could not parse " + "cline end column." + << endl; + else + // 1 based index -> 0 based + --to; if (to >= colinfo.size()) { cerr << "cline ends at non " "existing column " @@ -691,6 +911,7 @@ void handle_tabular(Parser & p, ostream if (row > 0) rowinfo[row - 1].newpage = true; else + // This does not work in LaTeX cerr << "Ignoring " "'\\newpage' " "before rows." @@ -706,63 +927,69 @@ void handle_tabular(Parser & p, ostream // split into cells vector<string> cells; split(lines[row], cells, TAB); - // Has the last multicolumn cell a rightline? - bool last_rightline = false; - for (size_t col = 0, cell = 0; - cell < cells.size() && col < colinfo.size(); ++col, ++cell) { + for (size_t col = 0, cell = 0; cell < cells.size(); + ++col, ++cell) { //cerr << "cell content: '" << cells[cell] << "'\n"; + if (col >= colinfo.size()) { + // This does not work in LaTeX + cerr << "Ignoring extra cell '" + << cells[cell] << "'." << endl; + continue; + } Parser p(cells[cell]); p.skip_spaces(); //cells[cell] << "'\n"; if (p.next_token().cs() == "multicolumn") { // how many cells? p.get_token(); - size_t const ncells = string2int(p.verbatim_item()); + size_t const ncells = + convert<unsigned int>(p.verbatim_item()); // special cell properties alignment vector<ColInfo> t; - handle_colalign(p, t); - cellinfo[row][col].multi = 1; - cellinfo[row][col].align = t.front().align; + handle_colalign(p, t, ColInfo()); + ColInfo & ci = t.front(); + + // The logic of LyX for multicolumn vertical + // lines is too complicated to reproduce it + // here (see LyXTabular::TeXCellPreamble()). + // Therefore we simply put everything in the + // special field. + ci2special(ci); + + cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN; + cellinfo[row][col].align = ci.align; + cellinfo[row][col].special = ci.special; + cellinfo[row][col].leftlines = ci.leftlines; + cellinfo[row][col].rightlines = ci.rightlines; ostringstream os; parse_text_in_inset(p, os, FLAG_ITEM, false, context); - cellinfo[row][col].content = os.str(); - - // multicolumn cells are tricky: This - // \multicolumn{2}{|c|}{col1-2}& - // \multicolumn{2}{|c|}{col3-4} "\\" - // gives | col1-2 | col3-4 | and not - // | col1-2 || col3-4 | - // So: - if (last_rightline && t.front().leftline) { - t.front().leftline = false; + if (!cellinfo[row][col].content.empty()) { + // This may or may not work in LaTeX, + // but it does not work in LyX. + // FIXME: Handle it correctly! + cerr << "Moving cell content '" + << cells[cell] + << "' into a multicolumn cell. " + "This will probably not work." + << endl; } - last_rightline = t.front().rightline; - - // multicolumn lines override normal cell lines - cellinfo[row][col].leftline = t.front().leftline; - cellinfo[row][col].rightline = t.front().rightline; + cellinfo[row][col].content += os.str(); // add dummy cells for multicol for (size_t i = 0; i < ncells - 1 && col < colinfo.size(); ++i) { ++col; - cellinfo[row][col].multi = 2; + cellinfo[row][col].multi = CELL_PART_OF_MULTICOLUMN; cellinfo[row][col].align = 'c'; } - // more than one line on the right? - if (t.front().rightline > 1) - cellinfo[row][col + 1].leftline = true; - } else { - // FLAG_END is a hack, we need to read all of it - cellinfo[row][col].leftline = colinfo[col].leftline; - cellinfo[row][col].rightline = colinfo[col].rightline; - cellinfo[row][col].align = colinfo[col].align; + 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); - cellinfo[row][col].content = os.str(); - last_rightline = false; + cellinfo[row][col].content += os.str(); } } @@ -777,7 +1004,20 @@ void handle_tabular(Parser & p, ostream cellinfo[row - 1][col].bottomline = true; rowinfo.pop_back(); } + } + // Now we have the table structure and content in rowinfo, colinfo + // and cellinfo. + // Unfortunately LyX has some limitations that we need to work around. + + // Convert cells with special content to multicolumn cells + // (LyX ignores the special field for non-multicolumn cells). + 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].special.empty()) + cellinfo[row][col].multi = CELL_BEGIN_OF_MULTICOLUMN; + } } //cerr << "// output what we have\n"; @@ -785,6 +1025,7 @@ void handle_tabular(Parser & p, ostream os << "\n<lyxtabular version=\"3\" rows=\"" << rowinfo.size() << "\" columns=\"" << colinfo.size() << "\">\n"; os << "<features" + << write_attribute("rotate", false) << write_attribute("islongtable", is_long_tabular) << ">\n"; @@ -792,9 +1033,10 @@ void handle_tabular(Parser & p, ostream for (size_t col = 0; col < colinfo.size(); ++col) { os << "<column alignment=\"" << verbose_align(colinfo[col].align) << "\"" - << " valignment=\"top\"" - << write_attribute("leftline", colinfo[col].leftline) - << write_attribute("rightline", colinfo[col].rightline) + << " valignment=\"" + << verbose_valign(colinfo[col].valign) << "\"" + << write_attribute("leftline", colinfo[col].leftlines > 0) + << write_attribute("rightline", colinfo[col].rightlines > 0) << write_attribute("width", colinfo[col].width) << write_attribute("special", colinfo[col].special) << ">\n"; @@ -818,22 +1060,27 @@ void handle_tabular(Parser & p, ostream for (size_t col = 0; col < colinfo.size(); ++col) { CellInfo const & cell = cellinfo[row][col]; os << "<cell"; - if (cell.multi) + if (cell.multi != CELL_NORMAL) os << " multicolumn=\"" << cell.multi << "\""; os << " alignment=\"" << verbose_align(cell.align) << "\"" - << " valignment=\"top\"" + << " valignment=\"" << verbose_valign(cell.valign) + << "\"" << write_attribute("topline", cell.topline) << write_attribute("bottomline", cell.bottomline) - << write_attribute("leftline", cell.leftline) - << write_attribute("rightline", cell.rightline); + << write_attribute("leftline", cell.leftlines > 0) + << write_attribute("rightline", cell.rightlines > 0) + << write_attribute("rotate", cell.rotate); //cerr << "\nrow: " << row << " col: " << col; //if (cell.topline) // cerr << " topline=\"true\""; //if (cell.bottomline) // cerr << " bottomline=\"true\""; os << " usebox=\"none\"" - << ">" + << write_attribute("width", cell.width); + if (cell.multi != CELL_NORMAL) + os << write_attribute("special", cell.special); + os << ">" << "\n\\begin_inset Text\n" << cell.content << "\n\\end_inset\n"
\documentclass{article} \usepackage[T1]{fontenc} \usepackage[latin1]{inputenc} \usepackage{array} \begin{document} A table with different columns: \begin{tabular}{lcrp{1cm}b{1cm}m{1cm}*{3}{|c|}} 1& 2& 3& 4& 5& 6& 7& 8& 9\\ 1& 2& 3& 4& 5& 6& 7& 8& 9 \end{tabular} [EMAIL PROTECTED]>{.}c<{.}r!{,}p{1cm}b{1cm}m{1cm}*{3}{|c|}} 1& 2& 3& 4& 5& 6& 7& 8& 9\\ 1& 2& 3& 4& 5& 6& 7& 8& 9 \end{tabular} \begin{tabular}{>{\raggedright}p{1cm}>{\raggedleft}p{1cm}>{\centering}p{1cm}llllll} 1& 2& 3& 4& 5& 6& 7& 8& 9\\ 1& 2& 3& 4& 5& 6& 7& 8& 9 \end{tabular} Separators with 'l' columns: [EMAIL PROTECTED] 1& 2& 3& 4& 5& 6& 7& 8& 9\\ 1& 2& 3& 4& 5& 6& 7& 8& 9 \end{tabular} A table without the last tabularnewline \begin{tabular}{l|c|r} left& center& right\\ left& center& right \end{tabular} A table with the last tabularnewline \begin{tabular}{l|c|r} left& center& right\\ left& center& right\\ \end{tabular} A table with a hline and the last tabularnewline \begin{tabular}{l|c|r} left& center& right\\ left& center& right\\ \hline \end{tabular} A table with multicolumns \begin{tabular}{ccc} col1& col2& col3\tabularnewline \multicolumn{2}{|c|}{left rule multicol1-2 right rule}& col3\tabularnewline \multicolumn{2}{|c|}{left rule multicol1-2 right rule}& \multicolumn{1}{|c|}{left rule multicol3 right rule}\tabularnewline \multicolumn{2}{|c|}{left rule multicol1-2 right rule}& \multicolumn{1}{c}{multicol3}\tabularnewline \multicolumn{2}{c}{multicol1-2}& \multicolumn{1}{|c|}{left rule multicol3 right rule}\tabularnewline \multicolumn{2}{||c||}{2 left rules multicol1-2 2 right rules}& \multicolumn{1}{|c|}{left rule multicol3 right rule}\tabularnewline \multicolumn{2}{|||c|||}{3 left rules multicol1-2 3 right rules}& \multicolumn{1}{|c|}{left rule multicol3 right rule}\tabularnewline \multicolumn{2}{||c||}{2 left rules multicol1-2 2 right rules}& \multicolumn{1}{c}{multicol3}\tabularnewline \multicolumn{2}{|||c|||}{3 left rules multicol1-2 3 right rules}& \multicolumn{1}{c}{multicol3}\tabularnewline \multicolumn{2}{|c|}{left rule multicol1-2 right rule}& \multicolumn{1}{||c||}{2 left rules multicol3 2 right rules}\tabularnewline \multicolumn{2}{||c||}{2 left rules multicol1-2 2 right rules}& \multicolumn{1}{|||c|||}{3 left rules multicol3 3 right rules}\tabularnewline \end{tabular} A table with vrules: \begin{tabular}{|c|c|c|} col1& col2& col3 \tabularnewline \multicolumn{2}{|c|}{left rule multicol1-2 right rule}& col3\tabularnewline \multicolumn{2}{|c|}{left rule multicol1-2 right rule}& \multicolumn{1}{|c|}{left rule multicol3 right rule}\tabularnewline \multicolumn{2}{|c|}{left rule multicol1-2 right rule}& \multicolumn{1}{c}{multicol3}\tabularnewline \multicolumn{2}{c}{multicol1-2}& \multicolumn{1}{|c|}{left rule multicol3 right rule}\tabularnewline \multicolumn{2}{||c||}{2 left rules multicol1-2 2 right rules}& \multicolumn{1}{|c|}{left rule multicol3 right rule}\tabularnewline \multicolumn{2}{|||c|||}{3 left rules multicol1-2 3 right rules}& \multicolumn{1}{|c|}{left rule multicol3 right rule}\tabularnewline \multicolumn{2}{||c||}{2 left rules multicol1-2 2 right rules}& \multicolumn{1}{c}{multicol3}\tabularnewline \multicolumn{2}{|||c|||}{3 left rules multicol1-2 3 right rules}& \multicolumn{1}{c}{multicol3}\tabularnewline \multicolumn{2}{|c|}{left rule multicol1-2 right rule}& \multicolumn{1}{||c||}{2 left rules multicol3 2 right rules}\tabularnewline \multicolumn{2}{||c||}{2 left rules multicol1-2 2 right rules}& \multicolumn{1}{|||c|||}{3 left rules multicol3 3 right rules}\tabularnewline \end{tabular} \end{document}