]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/table.C
If I ever see another licence blurb again, it'll be too soon...
[lyx.git] / src / tex2lyx / table.C
1 /**
2  * \file table.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  * \author Jean-Marc Lasgouttes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 // {[(
13
14 #include <config.h>
15
16 #include "tex2lyx.h"
17
18 #include <cctype>
19 #include <fstream>
20 #include <iostream>
21 #include <sstream>
22 #include <vector>
23 #include <map>
24
25 using std::cerr;
26 using std::endl;
27 using std::istringstream;
28 using std::ostream;
29 using std::ostringstream;
30 using std::string;
31 using std::vector;
32 using std::map;
33
34 #include "mathed/math_gridinfo.h"
35
36 // filled in preamble.C
37 std::map<char, int> special_columns;
38
39
40 namespace {
41
42 int string2int(string const & s, int deflt = 0)
43 {
44         istringstream is(s);
45         int i = deflt;
46         is >> i;
47         return i;
48 }
49
50
51 string read_hlines(Parser & p)
52 {
53         ostringstream os;
54         p.skip_spaces();
55         while (p.good()) {
56                 if (p.next_token().cs() == "hline") {
57                         p.get_token();
58                         os << "\\hline";
59                 } else if (p.next_token().cs() == "cline") {
60                         p.get_token();
61                         os << "\\cline{" << p.verbatim_item() << "}";
62                 } else
63                         break;
64                 p.skip_spaces();
65         };
66         //cerr << "read_hlines(), read: '" << os.str() << "'\n";
67         //cerr << "read_hlines(), next token: " << p.next_token() << "\n";
68         return os.str();
69 }
70
71
72 /* rather brutish way to code table structure in a string:
73
74   \begin{tabular}{ccc}
75     1 & 2 & 3\\ \hline
76     \multicolumn{2}{c}{4} & 5 //
77     6 & 7 \\
78   \end{tabular}
79
80  gets "translated" to:
81
82   1 TAB 2 TAB 3 LINE
83   \hline HLINE  TAB 5 LINE
84   5 TAB 7 LINE
85 */
86
87 char const TAB   = '\001';
88 char const LINE  = '\002';
89 char const HLINE = '\004';
90
91
92 void handle_colalign(Parser & p, vector<ColInfo> & colinfo)
93 {
94         if (p.get_token().cat() != catBegin)
95                 cerr << "wrong syntax for table column alignment. '{' expected\n";
96
97         char nextalign = 'b';
98         bool leftline = false;
99         for (Token t=p.get_token(); p.good() && t.cat() != catEnd; t = p.get_token()){
100 #ifdef FILEDEBUG
101                 cerr << "t: " << t << "  c: '" << t.character() << "'\n";
102 #endif
103
104                 switch (t.character()) {
105                         case 'c':
106                         case 'l':
107                         case 'r': {
108                                 ColInfo ci;
109                                 ci.align = t.character();
110                                 if (colinfo.size() && colinfo.back().rightline > 1) {
111                                         ci.leftline = true;
112                                         --colinfo.back().rightline;
113                                 }
114                                 colinfo.push_back(ci);
115                                 break;
116                         }
117                         case 'p':
118                                 colinfo.push_back(ColInfo());
119                                 colinfo.back().align = nextalign;
120                                 colinfo.back().width = p.verbatim_item();
121                                 nextalign = 'b';
122                                 break;
123                         case '|':
124                                 if (colinfo.empty())
125                                         leftline = true;
126                                 else
127                                         ++colinfo.back().rightline;
128                                 break;
129                         case '>': {
130                                 string s = p.verbatim_item();
131                                 if (s == "\\raggedleft ")
132                                         nextalign = 'l';
133                                 else if (s == "\\raggedright ")
134                                         nextalign = 'r';
135                                 else
136                                         cerr << "unknown '>' column '" << s << "'\n";
137                                 break;
138                         }
139                         default:
140                                 if (special_columns.find(t.character()) != special_columns.end()) {
141                                         ColInfo ci;
142                                         ci.align = 'c';
143                                         ci.special += t.character();
144                                         int const nargs = special_columns[t.character()];
145                                         for (int i = 0; i < nargs; ++i)
146                                                 ci.special += "{" + p.verbatim_item() + "}"; 
147                                         //cerr << "handling special column '" << t << "' " << nargs
148                                         //      << "  '" << ci.special << "'\n";
149                                         colinfo.push_back(ci);
150                                 } else {
151                                         cerr << "ignoring special separator '" << t << "'\n";
152                                 }
153                                 break;
154                         }
155         }
156         if (colinfo.size() && leftline)
157                 colinfo[0].leftline = true;
158 }
159
160
161 } // anonymous namespace
162
163
164 void parse_table(Parser & p, ostream & os, unsigned flags)
165 {
166         string hlines;
167
168         while (p.good()) {
169                 Token const & t = p.get_token();
170
171 #ifdef FILEDEBUG
172                 cerr << "t: " << t << " flags: " << flags << "\n";
173 #endif
174
175                 //
176                 // cat codes
177                 //
178                 if (t.cat() == catMath) {
179                                 // we are inside some text mode thingy, so opening new math is allowed
180                         Token const & n = p.get_token();
181                         if (n.cat() == catMath) {
182                                 // TeX's $$...$$ syntax for displayed math
183                                 os << "\\[";
184                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
185                                 os << "\\]";
186                                 p.get_token(); // skip the second '$' token
187                         } else {
188                                 // simple $...$  stuff
189                                 p.putback();
190                                 os << '$';
191                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
192                                 os << '$';
193                         }
194                 }
195
196                 else if (t.cat() == catLetter ||
197                                t.cat() == catSpace ||
198                                t.cat() == catSuper ||
199                                t.cat() == catSub ||
200                                t.cat() == catOther ||
201                                t.cat() == catActive ||
202                                t.cat() == catNewline ||
203                                t.cat() == catParameter)
204                         os << t.character();
205
206                 else if (t.cat() == catBegin) {
207                         os << '{';
208                         parse_table(p, os, FLAG_BRACE_LAST);
209                         os << '}';
210                 }
211
212                 else if (t.cat() == catEnd) {
213                         if (flags & FLAG_BRACE_LAST)
214                                 return;
215                         cerr << "unexpected '}'\n";
216                 }
217
218                 else if (t.cat() == catAlign) {
219                         os << TAB;
220                 }
221
222                 else if (t.cs() == "tabularnewline" || t.cs() == "\\") {
223                         // stuff before the line break
224                         // and look ahead for stuff after the line break
225                         os << HLINE << hlines << HLINE << LINE << read_hlines(p) << HLINE;
226                         hlines.erase();
227                 }
228
229                 else if (t.cs() == "hline")
230                         hlines += "\\hline";
231
232                 else if (t.cs() == "cline")
233                         hlines += "\\cline{" + p.verbatim_item() + '}';
234
235                 else if (t.cat() == catComment)
236                         handle_comment(p);
237
238                 else if (t.cs() == "(") {
239                         os << "\\(";
240                         parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
241                         os << "\\)";
242                 }
243
244                 else if (t.cs() == "[") {
245                         os << "\\[";
246                         parse_math(p, os, FLAG_EQUATION, MATH_MODE);
247                         os << "\\]";
248                 }
249
250                 else if (t.cs() == "begin") {
251                         string const name = p.getArg('{', '}');
252                         active_environments.push_back(name);
253                         parse_table(p, os, FLAG_END);
254                 }
255
256                 else if (t.cs() == "end") {
257                         if (flags & FLAG_END) {
258                                 // eat environment name
259                                 string const name = p.getArg('{', '}');
260                                 if (name != active_environment())
261                                         p.error("\\end{" + name + "} does not match \\begin{"
262                                                 + active_environment() + "}");
263                                 active_environments.pop_back();
264                                 return;
265                         }
266                         p.error("found 'end' unexpectedly");
267                 }
268
269                 else 
270                         os << t.asInput();
271         }
272 }
273
274
275 void handle_hline_above(RowInfo & ri, vector<CellInfo> & ci)
276 {
277         ri.topline = true;
278         for (size_t col = 0; col < ci.size(); ++col)
279                 ci[col].topline = true;
280 }
281
282
283 void handle_hline_below(RowInfo & ri, vector<CellInfo> & ci)
284 {
285         ri.bottomline = true;
286         for (size_t col = 0; col < ci.size(); ++col)
287                 ci[col].bottomline = true;
288 }
289
290
291 void handle_tabular(Parser & p, ostream & os,
292                     Context & context)
293 {
294         string posopts = p.getOpt();
295         if (posopts.size())
296                 cerr << "vertical tabular positioning '" << posopts << "' ignored\n";
297
298         vector<ColInfo>            colinfo;
299
300         // handle column formatting
301         handle_colalign(p, colinfo);
302
303         // handle initial hlines
304
305         // first scan of cells
306         // use table mode to keep it minimal-invasive
307         // not exactly what's TeX doing...
308         vector<string> lines;
309         ostringstream ss;
310         ss << read_hlines(p) << HLINE; // handle initial hlines
311         parse_table(p, ss, FLAG_END);
312         split(ss.str(), lines, LINE);
313
314         vector< vector<CellInfo> > cellinfo(lines.size());
315         vector<RowInfo> rowinfo(lines.size());
316         
317         // split into rows
318         //cerr << "// split into rows\n";
319         for (size_t row = 0; row < rowinfo.size(); ++row) {
320
321                 // init row
322                 cellinfo[row].resize(colinfo.size());
323
324                 // split row    
325                 vector<string> dummy;
326                 //cerr << "\n########### LINE: " << lines[row] << "########\n";
327                 split(lines[row], dummy, HLINE);
328
329                 // handle horizontal line fragments
330                 if (dummy.size() != 3) {
331                         if (dummy.size() != 1)
332                                 cerr << "unexpected dummy size: " << dummy.size()
333                                         << " content: " << lines[row] << "\n";
334                         dummy.resize(3);
335                 }
336                 lines[row] = dummy[1];
337
338                 //cerr << "line: " << row << " above 0: " << dummy[0] << "\n";
339                 //cerr << "line: " << row << " below 2: " << dummy[2] <<  "\n";
340                 //cerr << "line: " << row << " cells 1: " << dummy[1] <<  "\n";
341
342                 for (int i = 0; i <= 2; i += 2) {       
343                         //cerr << "   reading from line string '" << dummy[i] << "'\n";
344                         Parser p1(dummy[i]);
345                         while (p1.good()) {
346                                 Token t = p1.get_token();
347                                 //cerr << "read token: " << t << "\n";
348                                 if (t.cs() == "hline") {
349                                         if (i == 0) {
350                                                 if (rowinfo[row].topline) {
351                                                         if (row > 0) // extra bottomline above
352                                                                 handle_hline_below(rowinfo[row - 1], cellinfo[row - 1]);
353                                                         else
354                                                                 cerr << "dropping extra hline\n";
355                                                         //cerr << "below row: " << row-1 << endl;
356                                                 } else {
357                                                         handle_hline_above(rowinfo[row], cellinfo[row]);
358                                                         //cerr << "above row: " << row << endl;
359                                                 }
360                                         } else {        
361                                                 //cerr << "below row: " << row << endl;
362                                                 handle_hline_below(rowinfo[row], cellinfo[row]);
363                                         }
364                                 } else if (t.cs() == "cline") {
365                                         string arg = p1.verbatim_item();
366                                         //cerr << "read cline arg: '" << arg << "'\n";
367                                         vector<string> t;
368                                         split(arg, t, '-');
369                                         t.resize(2);
370                                         size_t from = string2int(t[0]) - 1;
371                                         size_t to = string2int(t[1]);
372                                         for (size_t col = from; col < to; ++col) {
373                                                 //cerr << "row: " << row << " col: " << col << " i: " << i << endl;
374                                                 if (i == 0) {
375                                                         rowinfo[row].topline = true;
376                                                         cellinfo[row][col].topline = true;
377                                                 } else {
378                                                         rowinfo[row].bottomline = true;
379                                                         cellinfo[row][col].bottomline = true;
380                                                 }
381                                         }
382                                 } else {
383                                         cerr << "unexpected line token: " << t << endl;
384                                 }
385                         }
386                 }
387
388                 // split into cells
389                 vector<string> cells;
390                 split(lines[row], cells, TAB);
391                 for (size_t col = 0, cell = 0;
392                                 cell < cells.size() && col < colinfo.size(); ++col, ++cell) {
393                         //cerr << "cell content: '" << cells[cell] << "'\n";
394                         Parser p(cells[cell]);
395                         p.skip_spaces();        
396                         //cells[cell] << "'\n";
397                         if (p.next_token().cs() == "multicolumn") {
398                                 // how many cells?
399                                 p.get_token();
400                                 size_t const ncells = string2int(p.verbatim_item());
401
402                                 // special cell properties alignment    
403                                 vector<ColInfo> t;
404                                 handle_colalign(p, t);
405                                 cellinfo[row][col].multi     = 1;
406                                 cellinfo[row][col].align     = t.front().align;
407                                 ostringstream os;
408                                 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
409                                 cellinfo[row][col].content   = os.str();
410                                 cellinfo[row][col].leftline  |= t.front().leftline;
411                                 cellinfo[row][col].rightline |= t.front().rightline;
412
413                                 // add dummy cells for multicol
414                                 for (size_t i = 0; i < ncells - 1 && col < colinfo.size(); ++i) {
415                                         ++col;
416                                         cellinfo[row][col].multi = 2;
417                                         cellinfo[row][col].align = 'c';
418                                 }
419
420                                 // more than one line on the right?
421                                 if (t.front().rightline > 1)
422                                         cellinfo[row][col + 1].leftline = true;
423
424                         } else {        
425                                 // FLAG_END is a hack, we need to read all of it
426                                 cellinfo[row][col].leftline = colinfo[col].leftline;
427                                 cellinfo[row][col].rightline = colinfo[col].rightline;
428                                 cellinfo[row][col].align = colinfo[col].align;
429                                 ostringstream os;
430                                 parse_text_in_inset(p, os, FLAG_CELL, false, context);
431                                 cellinfo[row][col].content   = os.str();
432                         }
433                 }
434
435                 //cerr << "//  handle almost empty last row what we have\n";
436                 // handle almost empty last row
437                 if (row && lines[row].empty() && row + 1 == rowinfo.size()) {
438                         //cerr << "remove empty last line\n";
439                         if (rowinfo[row].topline)
440                                 rowinfo[row - 1].bottomline = true;
441                         for (size_t col = 0; col < colinfo.size(); ++col)
442                                 if (cellinfo[row][col].topline)
443                                         cellinfo[row - 1][col].bottomline = true;
444                         rowinfo.pop_back();
445                 }
446
447         }
448
449         //cerr << "// output what we have\n";
450         // output what we have
451         os << "\n<lyxtabular version=\"3\" rows=\"" << rowinfo.size()
452                  << "\" columns=\"" << colinfo.size() << "\">\n"
453                  << "<features>\n";
454
455         //cerr << "// after header\n";
456         for (size_t col = 0; col < colinfo.size(); ++col) {
457                 os << "<column alignment=\""
458                    << verbose_align(colinfo[col].align) << "\"";
459                 os << " valignment=\"top\"";
460                 if (colinfo[col].leftline)
461                         os << " leftline=\"true\"";
462                 if (colinfo[col].rightline)
463                         os << " rightline=\"true\"";
464                 if (colinfo[col].width.size())
465                         os << " width=\"" << colinfo[col].width << "\"";
466                 if (colinfo[col].special.size())
467                         os << " special=\"" << colinfo[col].special << "\"";
468                 os << ">\n";
469         }
470         //cerr << "// after cols\n";
471
472         for (size_t row = 0; row < rowinfo.size(); ++row) {
473                 os << "<row";
474                 if (rowinfo[row].topline)
475                         os << " topline=\"true\"";
476                 if (rowinfo[row].bottomline)
477                         os << " bottomline=\"true\"";
478                 os << ">\n";
479                 for (size_t col = 0; col < colinfo.size(); ++col) {
480                         CellInfo const & cell = cellinfo[row][col];
481                         os << "<cell";
482                         if (cell.multi)
483                                 os << " multicolumn=\"" << cell.multi << "\"";
484                         os << " alignment=\"" << verbose_align(cell.align) 
485                            << "\""
486                            << " valignment=\"top\"";
487                         if (cell.topline)
488                                 os << " topline=\"true\"";
489                         if (cell.bottomline)
490                                 os << " bottomline=\"true\"";
491                         if (cell.leftline)
492                                 os << " leftline=\"true\"";
493                         if (cell.rightline)
494                                 os << " rightline=\"true\"";
495                         //cerr << "\nrow: " << row << " col: " << col;
496                         //if (cell.topline)
497                         //      cerr << " topline=\"true\"";
498                         //if (cell.bottomline)
499                         //      cerr << " bottomline=\"true\"";
500                         os << " usebox=\"none\""
501                            << ">"
502                            << "\n\\begin_inset Text\n"
503                            << cell.content
504                            << "\n\\end_inset \n"
505                            << "</cell>\n";
506                 }
507                 os << "</row>\n";
508         }
509                         
510         os << "</lyxtabular>\n";
511 }
512
513
514
515
516 // }])