mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-12-27 06:19:36 +00:00
337c6d1577
read utf8 tex documents and translate them to lyxformat 249. There is still no code to discover the encoding and use it, but it is the easiest part (I hope). git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@27563 a592a061-630c-0410-9148-cb99ea01b6c8
1117 lines
29 KiB
C++
1117 lines
29 KiB
C++
/**
|
|
* \file table.cpp
|
|
* 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 "tex2lyx.h"
|
|
|
|
#include "support/lassert.h"
|
|
#include "support/convert.h"
|
|
#include "support/lstrings.h"
|
|
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <vector>
|
|
#include <map>
|
|
|
|
using namespace std;
|
|
|
|
namespace lyx {
|
|
|
|
// filled in preamble.cpp
|
|
map<char, int> special_columns;
|
|
|
|
|
|
namespace {
|
|
|
|
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;
|
|
};
|
|
|
|
|
|
/// row type for longtables
|
|
enum LTRowType
|
|
{
|
|
/// 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
|
|
};
|
|
|
|
|
|
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 newpage
|
|
bool newpage;
|
|
};
|
|
|
|
|
|
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)
|
|
{
|
|
switch (c) {
|
|
case 'c':
|
|
return "center";
|
|
case 'r':
|
|
return "right";
|
|
case 'l':
|
|
return "left";
|
|
default:
|
|
return "none";
|
|
}
|
|
}
|
|
|
|
|
|
/// 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".
|
|
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();
|
|
}
|
|
|
|
|
|
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:
|
|
|
|
\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';
|
|
|
|
|
|
/*!
|
|
* 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 (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';
|
|
}
|
|
|
|
|
|
/*!
|
|
* 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
|
|
|
|
// 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':
|
|
// new column, horizontal aligned
|
|
next.align = t.character();
|
|
if (!next.special.empty())
|
|
ci2special(next);
|
|
colinfo.push_back(next);
|
|
next = ColInfo();
|
|
break;
|
|
case 'p':
|
|
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 '|':
|
|
// 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().special += '|';
|
|
break;
|
|
case '>': {
|
|
// 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:
|
|
// 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;
|
|
}
|
|
}
|
|
// 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)
|
|
{
|
|
LASSERT(t.cat() == catEscape, return false);
|
|
|
|
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;
|
|
|
|
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();
|
|
|
|
#ifdef FILEDEBUG
|
|
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
|
|
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
|
|
} else {
|
|
// 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
|
|
|| t.cat() == catLetter
|
|
|| t.cat() == catSuper
|
|
|| t.cat() == catSub
|
|
|| t.cat() == catOther
|
|
|| t.cat() == catActive
|
|
|| t.cat() == catParameter)
|
|
os << t.cs();
|
|
|
|
else if (t.cat() == catBegin) {
|
|
os << '{';
|
|
parse_table(p, os, is_long_tabular, pos,
|
|
FLAG_BRACE_LAST);
|
|
os << '}';
|
|
}
|
|
|
|
else if (t.cat() == catEnd) {
|
|
if (flags & FLAG_BRACE_LAST)
|
|
return;
|
|
cerr << "unexpected '}'\n";
|
|
}
|
|
|
|
else if (t.cat() == catAlign) {
|
|
os << TAB;
|
|
p.skip_spaces();
|
|
}
|
|
|
|
else if (t.cat() == catComment)
|
|
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 << "\\]";
|
|
}
|
|
|
|
else if (t.cs() == "begin") {
|
|
string const name = p.getArg('{', '}');
|
|
active_environments.push_back(name);
|
|
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") {
|
|
if (flags & FLAG_END) {
|
|
// eat environment name
|
|
string const name = p.getArg('{', '}');
|
|
if (name != active_environment())
|
|
p.error("\\end{" + name + "} does not match \\begin{"
|
|
+ active_environment() + "}");
|
|
return;
|
|
}
|
|
p.error("found 'end' unexpectedly");
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
void handle_hline_above(RowInfo & ri, vector<CellInfo> & ci)
|
|
{
|
|
ri.topline = true;
|
|
for (size_t col = 0; col < ci.size(); ++col)
|
|
ci[col].topline = true;
|
|
}
|
|
|
|
|
|
void handle_hline_below(RowInfo & ri, vector<CellInfo> & ci)
|
|
{
|
|
ri.bottomline = true;
|
|
for (size_t col = 0; col < ci.size(); ++col)
|
|
ci[col].bottomline = true;
|
|
}
|
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
void handle_tabular(Parser & p, ostream & os, bool is_long_tabular,
|
|
Context & context)
|
|
{
|
|
string posopts = p.getOpt();
|
|
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;
|
|
|
|
// handle column formatting
|
|
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;
|
|
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) {
|
|
|
|
// init row
|
|
cellinfo[row].resize(colinfo.size());
|
|
|
|
// 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 && dummy.size() != 2) ||
|
|
row != rowinfo.size() - 1)
|
|
cerr << "unexpected dummy size: " << dummy.size()
|
|
<< " content: " << lines[row] << "\n";
|
|
dummy.resize(3);
|
|
}
|
|
lines[row] = dummy[1];
|
|
|
|
//cerr << "line: " << row << " above 0: " << dummy[0] << "\n";
|
|
//cerr << "line: " << row << " below 2: " << dummy[2] << "\n";
|
|
//cerr << "line: " << row << " cells 1: " << dummy[1] << "\n";
|
|
|
|
for (int i = 0; i <= 2; i += 2) {
|
|
//cerr << " reading from line string '" << dummy[i] << "'\n";
|
|
Parser p1(dummy[i]);
|
|
while (p1.good()) {
|
|
Token t = p1.get_token();
|
|
//cerr << "read token: " << t << "\n";
|
|
if (t.cs() == "hline") {
|
|
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 << "below row: " << row-1 << endl;
|
|
} else {
|
|
handle_hline_above(rowinfo[row], cellinfo[row]);
|
|
//cerr << "above row: " << row << endl;
|
|
}
|
|
} else {
|
|
//cerr << "below row: " << row << endl;
|
|
handle_hline_below(rowinfo[row], cellinfo[row]);
|
|
}
|
|
} else if (t.cs() == "cline") {
|
|
string arg = p1.verbatim_item();
|
|
//cerr << "read cline arg: '" << arg << "'\n";
|
|
vector<string> t;
|
|
split(arg, t, '-');
|
|
t.resize(2);
|
|
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;
|
|
cellinfo[row][col].topline = true;
|
|
} else {
|
|
rowinfo[row].bottomline = true;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// split into cells
|
|
vector<string> cells;
|
|
split(lines[row], cells, TAB);
|
|
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 =
|
|
convert<unsigned int>(p.verbatim_item());
|
|
|
|
// special cell properties alignment
|
|
vector<ColInfo> t;
|
|
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 = CELL_PART_OF_MULTICOLUMN;
|
|
cellinfo[row][col].align = 'c';
|
|
}
|
|
|
|
} 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();
|
|
}
|
|
}
|
|
|
|
//cerr << "// handle almost empty last row what we have\n";
|
|
// handle almost empty last row
|
|
if (row && lines[row].empty() && row + 1 == rowinfo.size()) {
|
|
//cerr << "remove empty last line\n";
|
|
if (rowinfo[row].topline)
|
|
rowinfo[row - 1].bottomline = true;
|
|
for (size_t col = 0; col < colinfo.size(); ++col)
|
|
if (cellinfo[row][col].topline)
|
|
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 << "\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=\""
|
|
<< 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";
|
|
}
|
|
//cerr << "// after cols\n";
|
|
|
|
for (size_t row = 0; row < rowinfo.size(); ++row) {
|
|
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 != CELL_NORMAL)
|
|
os << " multicolumn=\"" << cell.multi << "\"";
|
|
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 << " usebox=\"none\""
|
|
<< write_attribute("width", translate_len(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"
|
|
<< "</cell>\n";
|
|
}
|
|
os << "</row>\n";
|
|
}
|
|
|
|
os << "</lyxtabular>\n";
|
|
}
|
|
|
|
|
|
|
|
|
|
// }])
|
|
|
|
|
|
} // namespace lyx
|