]> git.lyx.org Git - lyx.git/blobdiff - src/tex2lyx/table.C
Continue to improve GtkLengthEntry
[lyx.git] / src / tex2lyx / table.C
index c39e6584d93a630c15b696d222485e9e80de3b91..5cc0422d0341ca07b46e51cd0ca6d4191cabce74 100644 (file)
@@ -1,19 +1,30 @@
-/** The .tex to .lyx converter
-    \author André Pönitz (2003)
+/**
+ * \file table.C
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author André Pönitz
+ * \author Jean-Marc Lasgouttes
+ * \author Georg Baum
+ *
+ * Full author contact details are available in file CREDITS.
  */
 
 // {[(
 
 #include <config.h>
 
-#include "Lsstream.h"
 #include "tex2lyx.h"
 
+#include "support/convert.h"
+#include "support/lstrings.h"
+
 #include <cctype>
 #include <fstream>
 #include <iostream>
-#include <string>
+#include <sstream>
 #include <vector>
+#include <map>
 
 using std::cerr;
 using std::endl;
@@ -24,169 +35,488 @@ using std::string;
 using std::vector;
 
 
+// filled in preamble.C
+std::map<char, int> special_columns;
+
+
 namespace {
 
-struct ColInfo
-{
-       ColInfo() : rightline(0), leftline(false) {}
-       string align;      // column alignment
-       string width;      // column width
-       int    rightline;  // a line on the right?
-       bool   leftline;
+class ColInfo {
+public:
+       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;
+       /// number of lines on the right
+       int rightlines;
+       /// number of lines on the left
+       int leftlines;
 };
 
 
-struct RowInfo
+/// row type for longtables
+enum LTRowType
 {
-       RowInfo() : topline(false), bottomline(false) {} 
-       bool topline;     // horizontal line above
-       int  bottomline;  // horizontal line below
+       /// normal row
+       LT_NORMAL,
+       /// part of head
+       LT_HEAD,
+       /// part of head on first page
+       LT_FIRSTHEAD,
+       /// part of foot
+       LT_FOOT,
+       /// part of foot on last page
+       LT_LASTFOOT
 };
 
 
-struct CellInfo
-{
-       CellInfo()
-               : multi(0), leftline(false), rightline(false),
-          topline(false), bottomline(false)
-       {}
-
-       string content;    // cell content
-       int multi;         // multicolumn flag
-       string align;      // cell alignment
-       bool leftline;     // do we have a line on the left?
-       bool rightline;    // do we have a line on the right?
-       bool topline;        // do we have a line above?
-       bool bottomline;   // do we have a line below?
+class RowInfo {
+public:
+       RowInfo() : topline(false), bottomline(false), type(LT_NORMAL),
+                   newpage(false) {}
+       /// horizontal line above
+       bool topline;
+       /// horizontal line below
+       bool bottomline;
+       /// These are for longtabulars only
+       /// row type (head, foot, firsthead etc.)
+       LTRowType type;
+       /// row for a pagebreak
+       bool newpage;
 };
 
 
-int string2int(string const & s, int deflt = 0)
+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(CELL_NORMAL), align('n'), valign('n'),
+                    leftlines(0), rightlines(0), topline(false),
+                    bottomline(false), rotate(false) {}
+       /// cell content
+       string content;
+       /// multicolumn flag
+       Multicolumn multi;
+       /// cell alignment
+       char align;
+       /// 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;
+};
+
+
+/// translate a horizontal alignment (as stored in ColInfo and CellInfo) to LyX
+inline char const * verbose_align(char c)
 {
-       istringstream is(s);
-       int i = deflt;
-       is >> i;
-       return i;
+       switch (c) {
+       case 'c':
+               return "center";
+       case 'r':
+               return "right";
+       case 'l':
+               return "left";
+       default:
+               return "none";
+       }
 }
 
 
-string read_hlines(Parser & p)
+/// translate a vertical alignment (as stored in ColInfo and CellInfo) to LyX
+inline char const * verbose_valign(char c)
 {
-       ostringstream os;
-       p.skipSpaces();
-       while (p.good()) {
-               if (p.next_token().cs() == "hline") {
-                       p.get_token();
-                       os << "\\hline";
-               } else if (p.next_token().cs() == "cline") {
-                       p.get_token();
-                       os << "\\cline{" << p.verbatim_item() << "}";
-               } else
-                       break;
-               p.skipSpaces();
-       };
-       //cerr << "read_hlines(), read: '" << os.str() << "'\n";
-       //cerr << "read_hlines(), next token: " << p.next_token() << "\n";
-       return os.str();
+       // The default value for no special alignment is "top".
+       switch (c) {
+       case 'm':
+               return "middle";
+       case 'b':
+               return "bottom";
+       case 'p':
+       default:
+               return "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)
+{
+       // we write only true attribute values so we remove a bit of the
+       // file format bloat for tabulars.
+       return b ? ' ' + name + "=\"true\"" : string();
+}
+
 
-/* rather brutish way to code table structure in a string:
+string const write_attribute(string const & name, string const & s)
+{
+       return s.empty() ? string() : ' ' + name + "=\"" + s + '"';
+}
 
+
+/*! 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:
 
-  1 TAB 2 TAB 3 LINE
-  \hline HLINE  TAB 5 LINE
-  5 TAB 7 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';
 
-string get_align(char c)
+
+/*!
+ * 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)
 {
-       switch (c) {
-               case 'c': return "center";
-               case 'l': return "left";
-               case 'r': return "right";
-               case 'b': return "block";
-       }
-       return "center";
+       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}";
+                       break;
+               case 'r':
+                       ci.special += ">{\\raggedleft}";
+                       break;
+               case 'c':
+                       ci.special += ">{\\centering}";
+                       break;
+               }
+               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';
 }
 
 
-void handle_colalign(Parser & p, vector<ColInfo> & colinfo)
+/*!
+ * 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. '{' expected\n";
+               cerr << "Wrong syntax for table column alignment.\n"
+                       "Expected '{', got '" << p.curr_token().asInput()
+                    << "'.\n";
 
-       string nextalign = "block";
-       bool leftline = false;
-       for (Token t=p.get_token(); p.good() && t.cat() != catEnd; t = p.get_token()){
+       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
 
+               // We cannot handle comments here
+               if (t.cat() == catComment) {
+                       if (t.cs().empty()) {
+                               // "%\n" combination
+                               p.skip_spaces();
+                       } else
+                               cerr << "Ignoring comment: " << t.asInput();
+                       continue;
+               }
+
                switch (t.character()) {
                        case 'c':
                        case 'l':
-                       case 'r': {
-                               ColInfo ci;
-                               ci.align = get_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 = "block";
+                       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 = "left";
-                               else if (s == "\\raggedright ")
-                                       nextalign = "right";
-                               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;
                        }
+                       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 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:
-                               cerr << "ignoring special separator '" << t << "'\n";
+                               // 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;
                        }
        }
-       if (colinfo.size() && leftline)
-               colinfo[0].leftline = true;
+
+       // 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();
+       }
 }
 
 
-} // anonymous namespace
+/*!
+ * 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;
+               }
+       }
+       // 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]);
+}
+
+
+/*!
+ * Parse hlines and similar stuff.
+ * \returns wether the token \p t was parsed
+ */
+bool parse_hlines(Parser & p, Token const & t, string & hlines,
+                  bool is_long_tabular)
+{
+       BOOST_ASSERT(t.cat() == catEscape);
+
+       if (t.cs() == "hline")
+               hlines += "\\hline";
+
+       else if (t.cs() == "cline")
+               hlines += "\\cline{" + p.verbatim_item() + '}';
+
+       else if (is_long_tabular && t.cs() == "newpage")
+               hlines += "\\newpage";
 
+       else
+               return false;
 
-void parse_table(Parser & p, ostream & os, unsigned flags)
+       return true;
+}
+
+
+/// Position in a row
+enum RowPosition {
+       /// At the very beginning, before the first token
+       ROW_START,
+       /// After the first token and before any column token
+       IN_HLINES_START,
+       /// After the first column token. Comments and whitespace are only
+       /// treated as tokens in this position
+       IN_COLUMNS,
+       /// After the first non-column token at the end
+       IN_HLINES_END
+};
+
+
+/*!
+ * Parse table structure.
+ * We parse tables in a two-pass process: This function extracts the table
+ * structure (rows, columns, hlines etc.), but does not change the cell
+ * content. The cell content is parsed in a second step in handle_tabular().
+ */
+void parse_table(Parser & p, ostream & os, bool is_long_tabular,
+                 RowPosition & pos, unsigned flags)
 {
+       // table structure commands such as \hline
        string hlines;
 
+       // comments that occur at places where we can't handle them
+       string comments;
+
        while (p.good()) {
                Token const & t = p.get_token();
 
@@ -194,15 +524,136 @@ void parse_table(Parser & p, ostream & os, unsigned flags)
                cerr << "t: " << t << " flags: " << flags << "\n";
 #endif
 
+               // comments and whitespace in hlines
+               switch (pos) {
+               case ROW_START:
+               case IN_HLINES_START:
+               case IN_HLINES_END:
+                       if (t.cat() == catComment) {
+                               if (t.cs().empty())
+                                       // line continuation
+                                       p.skip_spaces();
+                               else
+                                       // We can't handle comments here,
+                                       // store them for later use
+                                       comments += t.asInput();
+                               continue;
+                       } else if (t.cat() == catSpace ||
+                                  t.cat() == catNewline) {
+                               // whitespace is irrelevant here, we
+                               // need to recognize hline stuff
+                               p.skip_spaces();
+                               continue;
+                       }
+                       break;
+               case IN_COLUMNS:
+                       break;
+               }
+
+               // We need to handle structure stuff first in order to
+               // determine wether we need to output a HLINE separator
+               // before the row or not.
+               if (t.cat() == catEscape) {
+                       if (parse_hlines(p, t, hlines, is_long_tabular)) {
+                               switch (pos) {
+                               case ROW_START:
+                                       pos = IN_HLINES_START;
+                                       break;
+                               case IN_COLUMNS:
+                                       pos = IN_HLINES_END;
+                                       break;
+                               case IN_HLINES_START:
+                               case IN_HLINES_END:
+                                       break;
+                               }
+                               continue;
+                       }
+
+                       else if (t.cs() == "tabularnewline" ||
+                                t.cs() == "\\" ||
+                                t.cs() == "cr") {
+                               if (t.cs() == "cr")
+                                       cerr << "Warning: Converting TeX "
+                                               "'\\cr' to LaTeX '\\\\'."
+                                            << endl;
+                               // stuff before the line break
+                               os << comments << HLINE << hlines << HLINE
+                                  << LINE;
+                               //cerr << "hlines: " << hlines << endl;
+                               hlines.erase();
+                               comments.erase();
+                               pos = ROW_START;
+                               continue;
+                       }
+
+                       else if (is_long_tabular &&
+                                (t.cs() == "endhead" ||
+                                 t.cs() == "endfirsthead" ||
+                                 t.cs() == "endfoot" ||
+                                 t.cs() == "endlastfoot")) {
+                               hlines += t.asInput();
+                               switch (pos) {
+                               case IN_COLUMNS:
+                               case IN_HLINES_END:
+                                       // these commands are implicit line
+                                       // breaks
+                                       os << comments << HLINE << hlines
+                                          << HLINE << LINE;
+                                       hlines.erase();
+                                       comments.erase();
+                                       pos = ROW_START;
+                                       break;
+                               case ROW_START:
+                                       pos = IN_HLINES_START;
+                                       break;
+                               case IN_HLINES_START:
+                                       break;
+                               }
+                               continue;
+                       }
+
+               }
+
+               // We need a HLINE separator if we either have no hline
+               // stuff at all and are just starting a row or if we just
+               // got the first non-hline token.
+               switch (pos) {
+               case ROW_START:
+                       // no hline tokens exist, first token at row start
+               case IN_HLINES_START:
+                       // hline tokens exist, first non-hline token at row
+                       // start
+                       os << hlines << HLINE << comments;
+                       hlines.erase();
+                       comments.erase();
+                       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;
+                       os << comments;
+                       hlines.erase();
+                       comments.erase();
+                       pos = IN_COLUMNS;
+                       break;
+               case IN_COLUMNS:
+                       break;
+               }
+
+               // If we come here we have normal cell content
                //
                // cat codes
                //
                if (t.cat() == catMath) {
-                               // we are inside some text mode thingy, so opening new math is allowed
+                       // we are inside some text mode thingy, so opening new math is allowed
                        Token const & n = p.get_token();
                        if (n.cat() == catMath) {
                                // TeX's $$...$$ syntax for displayed math
                                os << "\\[";
+                               // This does only work because parse_math outputs TeX
                                parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
                                os << "\\]";
                                p.get_token(); // skip the second '$' token
@@ -210,24 +661,27 @@ void parse_table(Parser & p, ostream & os, unsigned flags)
                                // simple $...$  stuff
                                p.putback();
                                os << '$';
+                               // This does only work because parse_math outputs TeX
                                parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
                                os << '$';
                        }
                }
 
+               else if (t.cat() == catSpace || t.cat() == catNewline)
+                               os << t.cs();
+
                else if (t.cat() == catLetter ||
-                              t.cat() == catSpace ||
                               t.cat() == catSuper ||
                               t.cat() == catSub ||
                               t.cat() == catOther ||
                               t.cat() == catActive ||
-                              t.cat() == catNewline ||
                               t.cat() == catParameter)
                        os << t.character();
 
                else if (t.cat() == catBegin) {
                        os << '{';
-                       parse_table(p, os, FLAG_BRACE_LAST);
+                       parse_table(p, os, is_long_tabular, pos,
+                                   FLAG_BRACE_LAST);
                        os << '}';
                }
 
@@ -239,33 +693,22 @@ void parse_table(Parser & p, ostream & os, unsigned flags)
 
                else if (t.cat() == catAlign) {
                        os << TAB;
+                       p.skip_spaces();
                }
 
-               //else if (t.cs() == "tabularnewline" || t.cs() == "\\") {
-               else if (t.cs() == "tabularnewline") {
-                       // stuff before the line break
-                       // and look ahead for stuff after the line break
-                       os << HLINE << hlines << HLINE << LINE << read_hlines(p) << HLINE;
-                       hlines.erase();
-               }
-
-               else if (t.cs() == "hline")
-                       hlines += "\\hline";
-
-               else if (t.cs() == "cline")
-                       hlines += "\\cline{" + p.verbatim_item() + '}';
-
                else if (t.cat() == catComment)
-                       handle_comment(p);
+                       os << t.asInput();
 
                else if (t.cs() == "(") {
                        os << "\\(";
+                       // This does only work because parse_math outputs TeX
                        parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
                        os << "\\)";
                }
 
                else if (t.cs() == "[") {
                        os << "\\[";
+                       // This does only work because parse_math outputs TeX
                        parse_math(p, os, FLAG_EQUATION, MATH_MODE);
                        os << "\\]";
                }
@@ -273,7 +716,13 @@ void parse_table(Parser & p, ostream & os, unsigned flags)
                else if (t.cs() == "begin") {
                        string const name = p.getArg('{', '}');
                        active_environments.push_back(name);
-                       parse_table(p, os, FLAG_END);
+                       os << "\\begin{" << name << '}';
+                       // 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 << "\\end{" << name << '}';
+                       active_environments.pop_back();
                }
 
                else if (t.cs() == "end") {
@@ -283,15 +732,24 @@ void parse_table(Parser & p, ostream & os, unsigned flags)
                                if (name != active_environment())
                                        p.error("\\end{" + name + "} does not match \\begin{"
                                                + active_environment() + "}");
-                               active_environments.pop_back();
                                return;
                        }
                        p.error("found 'end' unexpectedly");
                }
 
-               else 
+               else
                        os << t.asInput();
        }
+
+       // We can have comments if the last line is incomplete
+       os << comments;
+
+       // We can have hline stuff if the last line is incomplete
+       if (!hlines.empty()) {
+               // this does not work in LaTeX, so we ignore it
+               cerr << "Ignoring '" << hlines << "' at end of tabular"
+                    << endl;
+       }
 }
 
 
@@ -311,31 +769,40 @@ void handle_hline_below(RowInfo & ri, vector<CellInfo> & ci)
 }
 
 
-void handle_tabular(Parser & p, ostream & os)
+} // anonymous namespace
+
+
+void handle_tabular(Parser & p, ostream & os, bool is_long_tabular,
+                   Context & context)
 {
        string posopts = p.getOpt();
-       if (posopts.size())
-               cerr << "vertical tabular positioning '" << posopts << "' ignored\n";
+       if (!posopts.empty()) {
+               // FIXME: Convert this to ERT
+               if (is_long_tabular)
+                       cerr << "horizontal longtable";
+               else
+                       cerr << "vertical tabular";
+               cerr << " positioning '" << posopts << "' ignored\n";
+       }
 
-       vector<ColInfo>            colinfo;
+       vector<ColInfo> colinfo;
 
        // handle column formatting
-       handle_colalign(p, colinfo);
-
-       // handle initial hlines
+       handle_colalign(p, colinfo, ColInfo());
+       fix_colalign(colinfo);
 
        // first scan of cells
        // use table mode to keep it minimal-invasive
        // not exactly what's TeX doing...
        vector<string> lines;
        ostringstream ss;
-       ss << read_hlines(p) << HLINE; // handle initial hlines
-       parse_table(p, ss, FLAG_END);
+       RowPosition rowpos = ROW_START;
+       parse_table(p, ss, is_long_tabular, rowpos, FLAG_END);
        split(ss.str(), lines, LINE);
 
        vector< vector<CellInfo> > cellinfo(lines.size());
        vector<RowInfo> rowinfo(lines.size());
-       
+
        // split into rows
        //cerr << "// split into rows\n";
        for (size_t row = 0; row < rowinfo.size(); ++row) {
@@ -343,14 +810,16 @@ void handle_tabular(Parser & p, ostream & os)
                // init row
                cellinfo[row].resize(colinfo.size());
 
-               // split row    
+               // split row
                vector<string> dummy;
                //cerr << "\n########### LINE: " << lines[row] << "########\n";
                split(lines[row], dummy, HLINE);
 
                // handle horizontal line fragments
+               // we do only expect this for a last line without '\\'
                if (dummy.size() != 3) {
-                       if (dummy.size() != 1)
+                       if ((dummy.size() != 1 && dummy.size() != 2) ||
+                           row != rowinfo.size() - 1)
                                cerr << "unexpected dummy size: " << dummy.size()
                                        << " content: " << lines[row] << "\n";
                        dummy.resize(3);
@@ -361,7 +830,7 @@ void handle_tabular(Parser & p, ostream & os)
                //cerr << "line: " << row << " below 2: " << dummy[2] <<  "\n";
                //cerr << "line: " << row << " cells 1: " << dummy[1] <<  "\n";
 
-               for (int i = 0; i <= 2; i += 2) {       
+               for (int i = 0; i <= 2; i += 2) {
                        //cerr << "   reading from line string '" << dummy[i] << "'\n";
                        Parser p1(dummy[i]);
                        while (p1.good()) {
@@ -379,7 +848,7 @@ void handle_tabular(Parser & p, ostream & os)
                                                        handle_hline_above(rowinfo[row], cellinfo[row]);
                                                        //cerr << "above row: " << row << endl;
                                                }
-                                       } else {        
+                                       } else {
                                                //cerr << "below row: " << row << endl;
                                                handle_hline_below(rowinfo[row], cellinfo[row]);
                                        }
@@ -389,9 +858,35 @@ void handle_tabular(Parser & p, ostream & os)
                                        vector<string> t;
                                        split(arg, t, '-');
                                        t.resize(2);
-                                       size_t from = string2int(t[0]) - 1;
-                                       size_t to = string2int(t[1]);
-                                       for (size_t col = from; col < to; ++col) {
+                                       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 = 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 "
+                                                    << (to + 1) << endl;
+                                               to = colinfo.size() - 1;
+                                       }
+                                       for (size_t col = from; col <= to; ++col) {
                                                //cerr << "row: " << row << " col: " << col << " i: " << i << endl;
                                                if (i == 0) {
                                                        rowinfo[row].topline = true;
@@ -401,6 +896,50 @@ void handle_tabular(Parser & p, ostream & os)
                                                        cellinfo[row][col].bottomline = true;
                                                }
                                        }
+                               } else if (t.cs() == "endhead") {
+                                       if (i > 0)
+                                               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;
+                                       }
+                               } else if (t.cs() == "endfirsthead") {
+                                       if (i > 0)
+                                               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;
+                                       }
+                               } else if (t.cs() == "endfoot") {
+                                       if (i > 0)
+                                               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;
+                                       }
+                               } else if (t.cs() == "endlastfoot") {
+                                       if (i > 0)
+                                               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;
+                                       }
+                               } else if (t.cs() == "newpage") {
+                                       if (i == 0) {
+                                               if (row > 0)
+                                                       rowinfo[row - 1].newpage = true;
+                                               else
+                                                       // This does not work in LaTeX
+                                                       cerr << "Ignoring "
+                                                               "'\\newpage' "
+                                                               "before rows."
+                                                            << endl;
+                                       } else
+                                               rowinfo[row].newpage = true;
                                } else {
                                        cerr << "unexpected line token: " << t << endl;
                                }
@@ -410,40 +949,69 @@ void handle_tabular(Parser & p, ostream & os)
                // split into cells
                vector<string> cells;
                split(lines[row], cells, TAB);
-               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.skipSpaces(); 
+                       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    
+                               // special cell properties alignment
                                vector<ColInfo> t;
-                               handle_colalign(p, t);
-                               cellinfo[row][col].multi     = 1;
-                               cellinfo[row][col].align     = t.front().align;
-                               cellinfo[row][col].content   = parse_text(p, FLAG_ITEM, false);
-                               cellinfo[row][col].leftline  |= t.front().leftline;
-                               cellinfo[row][col].rightline |= t.front().rightline;
+                               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);
+                               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;
+                               }
+                               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].align = "center";
+                                       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].content = parse_text(p, FLAG_END, false);
+                       } else {
+                               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();
                        }
                }
 
@@ -458,66 +1026,91 @@ void handle_tabular(Parser & p, ostream & os)
                                        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";
        // output what we have
-       os << "<lyxtabular version=\"3\" rows=\"" << rowinfo.size()
-                << "\" columns=\"" << colinfo.size() << "\">\n"
-                << "<features>\n";
+       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";
 
        //cerr << "// after header\n";
        for (size_t col = 0; col < colinfo.size(); ++col) {
-               os << "<column alignment=\"" << colinfo[col].align << "\"";
-               if (colinfo[col].rightline)
-                       os << " rightline=\"true\"";
-               if (colinfo[col].leftline)
-                       os << " leftline=\"true\"";
-               os << " valignment=\"top\"";
-               os << " width=\"" << colinfo[col].width << "\"";
-               os << ">\n";
+               os << "<column alignment=\""
+                  << 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", colinfo[col].width)
+                  << write_attribute("special", colinfo[col].special)
+                  << ">\n";
        }
        //cerr << "// after cols\n";
 
        for (size_t row = 0; row < rowinfo.size(); ++row) {
-               os << "<row";
-               if (rowinfo[row].topline)
-                       os << " topline=\"true\"";
-               if (rowinfo[row].bottomline)
-                       os << " bottomline=\"true\"";
-               os << ">\n";
+               os << "<row"
+                  << write_attribute("topline", rowinfo[row].topline)
+                  << write_attribute("bottomline", rowinfo[row].bottomline)
+                  << write_attribute("endhead",
+                                     rowinfo[row].type == LT_HEAD)
+                  << write_attribute("endfirsthead",
+                                     rowinfo[row].type == LT_FIRSTHEAD)
+                  << write_attribute("endfoot",
+                                     rowinfo[row].type == LT_FOOT)
+                  << write_attribute("endlastfoot",
+                                     rowinfo[row].type == LT_LASTFOOT)
+                  << write_attribute("newpage", rowinfo[row].newpage)
+                  << ">\n";
                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 << "\"";
-                       if (cell.leftline)
-                               os << " leftline=\"true\"";
-                       if (cell.rightline)
-                               os << " rightline=\"true\"";
-                       if (cell.topline)
-                               os << " topline=\"true\"";
-                       if (cell.bottomline)
-                               os << " bottomline=\"true\"";
+                       os << " alignment=\"" << verbose_align(cell.align)
+                          << "\""
+                          << " valignment=\"" << verbose_valign(cell.valign)
+                          << "\""
+                          << write_attribute("topline", cell.topline)
+                          << write_attribute("bottomline", cell.bottomline)
+                          << 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 << " alignment=\"" << cell.align << "\""
-                                << " valignment=\"top\""
-                                << " usebox=\"none\""
-                                << ">"
-                          << "\n\\begin_inset Text"
-                          << "\n\n\\layout Standard\n\n"
+                       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\n"
+                          << "\n\\end_inset\n"
                           << "</cell>\n";
                }
                os << "</row>\n";
        }
-                       
+
        os << "</lyxtabular>\n";
 }