]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/text.C
latest patch from Georg: support a syntax table for unsupported commands
[lyx.git] / src / tex2lyx / text.C
1 /**
2  * \file tex2lyx/text.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 #include "context.h"
18 #include "FloatList.h"
19 #include "support/lstrings.h"
20 #include "support/tostr.h"
21 #include "support/filetools.h"
22
23 #include <iostream>
24 #include <map>
25 #include <sstream>
26 #include <vector>
27
28 using std::cerr;
29 using std::endl;
30
31 using std::map;
32 using std::ostream;
33 using std::ostringstream;
34 using std::istringstream;
35 using std::string;
36 using std::vector;
37
38 using lyx::support::rtrim;
39 using lyx::support::suffixIs;
40
41
42 // thin wrapper around parse_text using a string
43 string parse_text(Parser & p, unsigned flags, const bool outer,
44                   Context & context)
45 {
46         ostringstream os;
47         parse_text(p, os, flags, outer, context);
48         return os.str();
49 }
50
51 // parses a subdocument, usually useful in insets (whence the name)
52 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
53                 Context & context)
54 {
55         Context newcontext(true, context.textclass);
56         parse_text(p, os, flags, outer, newcontext);
57         newcontext.check_end_layout(os);
58 }
59
60
61 // parses a paragraph snippet, useful for example for \emph{...}
62 void parse_text_snippet(Parser & p, ostream & os, unsigned flags, bool outer,
63                 Context & context)
64 {
65         Context newcontext(false, context.textclass);
66         parse_text(p, os, flags, outer, newcontext);
67         // should not be needed
68         newcontext.check_end_layout(os);
69 }
70
71
72 namespace {
73
74 char const * known_latex_commands[] = { "ref", "cite", "label", "index",
75 "printindex", "pageref", "url", "vref", "vpageref", "prettyref", "eqref", 0 };
76
77 // LaTeX names for quotes
78 char const * known_quotes[] = { "glqq", "grqq", "quotedblbase",
79 "textquotedblleft", "quotesinglbase", "guilsinglleft", "guilsinglright", 0};
80
81 // the same as known_quotes with .lyx names
82 char const * known_coded_quotes[] = { "gld", "grd", "gld",
83 "grd", "gls", "fls", "frd", 0};
84
85 char const * known_sizes[] = { "tiny", "scriptsize", "footnotesize",
86 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
87
88 char const * known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
89 "small", "normal", "large", "larger", "largest",  "huge", "giant", 0};
90
91 // splits "x=z, y=b" into a map
92 map<string, string> split_map(string const & s)
93 {
94         map<string, string> res;
95         vector<string> v;
96         split(s, v);
97         for (size_t i = 0; i < v.size(); ++i) {
98                 size_t const pos   = v[i].find('=');
99                 string const index = v[i].substr(0, pos);
100                 string const value = v[i].substr(pos + 1, string::npos);
101                 res[trim(index)] = trim(value);
102         }
103         return res;
104 }
105
106 // A simple function to translate a latex length to something lyx can
107 // understand. Not perfect, but rather best-effort.
108 string translate_len(string const & len)
109 {
110         const string::size_type i = len.find_first_not_of(" -0123456789.,");
111         //'4,5' is a valid LaTeX length number. Change it to '4.5'
112         string const length = lyx::support::subst(len, ',', '.');
113         // a normal length
114         if (i == string::npos || len[i]  != '\\')
115                 return length;
116         double val;
117         if (i == 0) {
118                 // We had something like \textwidth without a factor
119                 val = 100;
120         } else {
121                 istringstream iss(string(length, 0, i));
122                 iss >> val;
123                 val = val * 100;
124         }
125         ostringstream oss;
126         oss << val;
127         string const valstring = oss.str();
128         const string::size_type i2 = length.find(" ", i);
129         string const unit = string(len, i, i2 - i);
130         string const endlen = (i2 == string::npos) ? string() : string(len, i2);
131         if (unit == "\\textwidth")
132                 return valstring + "text%" + endlen;
133         else if (unit == "\\columnwidth")
134                 return valstring + "col%" + endlen;
135         else if (unit == "\\paperwidth")
136                 return valstring + "page%" + endlen;
137         else if (unit == "\\linewidth")
138                 return valstring + "line%" + endlen;
139         else if (unit == "\\paperheight")
140                 return valstring + "pheight%" + endlen;
141         else if (unit == "\\textheight")
142                 return valstring + "theight%" + endlen;
143         else
144                 return length;
145 }
146
147
148 void begin_inset(ostream & os, string const & name)
149 {
150         os << "\n\\begin_inset " << name;
151 }
152
153
154 void end_inset(ostream & os)
155 {
156         os << "\n\\end_inset \n\n";
157 }
158
159
160 void skip_braces(Parser & p)
161 {
162         if (p.next_token().cat() != catBegin)
163                 return;
164         p.get_token();
165         if (p.next_token().cat() == catEnd) {
166                 p.get_token();
167                 return;
168         }
169         p.putback();
170 }
171
172
173 void handle_ert(ostream & os, string const & s, Context & context, bool check_layout = true)
174 {
175         if (check_layout) {
176                 // We must have a valid layout before outputting the ERT inset.
177                 context.check_layout(os);
178         }
179         Context newcontext(true, context.textclass);
180         begin_inset(os, "ERT");
181         os << "\nstatus Collapsed\n";
182         newcontext.check_layout(os);
183         for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
184                 if (*it == '\\')
185                         os << "\n\\backslash \n";
186                 else
187                         os << *it;
188         }
189         newcontext.check_end_layout(os);
190         end_inset(os);
191 }
192
193
194 void handle_comment(ostream & os, string const & s, Context & context)
195 {
196         // TODO: Handle this better
197         Context newcontext(true, context.textclass);
198         begin_inset(os, "ERT");
199         os << "\nstatus Collapsed\n";
200         newcontext.check_layout(os);
201         for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
202                 if (*it == '\\')
203                         os << "\n\\backslash \n";
204                 else
205                         os << *it;
206         }
207         // make sure that our comment is the last thing on the line
208         os << "\n\\newline";
209         newcontext.check_end_layout(os);
210         end_inset(os);
211 }
212
213
214 struct isLayout {
215         isLayout(string const name) : name_(name) {}
216         bool operator()(LyXLayout_ptr const & ptr) {
217                 return ptr.get() && ptr->latexname() == name_;
218         }
219 private:
220         string const name_;
221 };
222
223
224 LyXLayout_ptr findLayout(LyXTextClass const & textclass,
225                          string const & name)
226 {
227         LyXTextClass::const_iterator it  = textclass.begin();
228         LyXTextClass::const_iterator end = textclass.end();
229         it = std::find_if(it, end, isLayout(name));
230         return (it == end) ? LyXLayout_ptr() : *it;
231 }
232
233
234 void output_command_layout(ostream & os, Parser & p, bool outer,
235                            Context & parent_context,
236                            LyXLayout_ptr newlayout)
237 {
238         parent_context.check_end_layout(os);
239         Context context(true, parent_context.textclass, newlayout,
240                         parent_context.layout);
241         context.check_deeper(os);
242         context.check_layout(os);
243         if (context.layout->optionalargs > 0) {
244                 p.skip_spaces();
245                 if (p.next_token().character() == '[') {
246                         p.get_token(); // eat '['
247                         begin_inset(os, "OptArg\n");
248                         os << "collapsed true\n\n";
249                         parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
250                         end_inset(os);
251                 }
252         }
253         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
254         context.check_end_layout(os);
255         context.check_end_deeper(os);
256         // We don't need really a new paragraph, but
257         // we must make sure that the next item gets a \begin_layout.
258         parent_context.new_paragraph(os);
259 }
260
261
262 /*!
263  * Output a space if necessary.
264  * This function gets called for every whitespace token.
265  *
266  * We have three cases here:
267  * 1. A space must be suppressed. Example: The lyxcode case below
268  * 2. A space may be suppressed. Example: Spaces before "\par"
269  * 3. A space must not be suppressed. Example: A space between two words
270  *
271  * We currently handle only 1. and 3 and from 2. only the case of
272  * spaces before newlines as a side effect.
273  *
274  * 2. could be used to suppress as many spaces as possible. This has two effects:
275  * - Reimporting LyX generated LaTeX files changes almost no whitespace
276  * - Superflous whitespace from non LyX generated LaTeX files is removed.
277  * The drawback is that the logic inside the function becomes
278  * complicated, and that is the reason why it is not implemented.
279  */
280 void check_space(Parser const & p, ostream & os, Context & context)
281 {
282         Token const next = p.next_token();
283         Token const curr = p.curr_token();
284         // A space before a single newline and vice versa must be ignored
285         // LyX emits a newline before \end{lyxcode}.
286         // This newline must be ignored,
287         // otherwise LyX will add an additional protected space.
288         if (next.cat() == catSpace ||
289             next.cat() == catNewline ||
290             (next.cs() == "end" && context.layout->free_spacing && curr.cat() == catNewline)) {
291                 return;
292         }
293         context.check_layout(os);
294         os << ' ';
295 }
296
297
298 /*!
299  * Check wether \param command is a known command. If yes,
300  * handle the command with all arguments.
301  * \return true if the command was parsed, false otherwise.
302  */
303 bool parse_command(string const & command, Parser & p, ostream & os,
304                    bool outer, Context & context)
305 {
306         if (known_commands.find(command) != known_commands.end()) {
307                 vector<ArgumentType> const & template_arguments = known_commands[command];
308                 string ert = command;
309                 size_t no_arguments = template_arguments.size();
310                 for (size_t i = 0; i < no_arguments; ++i) {
311                         switch (template_arguments[i]) {
312                         case required:
313                                 // This argument contains regular LaTeX
314                                 handle_ert(os, ert + '{', context);
315                                 parse_text(p, os, FLAG_ITEM, outer, context);
316                                 ert = "}";
317                                 break;
318                         case verbatim:
319                                 // This argument may contain special characters
320                                 ert += '{' + p.verbatim_item() + '}';
321                                 break;
322                         case optional:
323                                 ert += p.getOpt();
324                                 break;
325                         }
326                 }
327                 handle_ert(os, ert, context);
328                 return true;
329         }
330         return false;
331 }
332
333
334 void parse_environment(Parser & p, ostream & os, bool outer,
335                        Context & parent_context)
336 {
337         LyXLayout_ptr newlayout;
338         string const name = p.getArg('{', '}');
339         const bool is_starred = suffixIs(name, '*');
340         string const unstarred_name = rtrim(name, "*");
341         active_environments.push_back(name);
342         p.skip_spaces();
343
344         if (is_math_env(name)) {
345                 parent_context.check_layout(os);
346                 begin_inset(os, "Formula ");
347                 os << "\\begin{" << name << "}";
348                 parse_math(p, os, FLAG_END, MATH_MODE);
349                 os << "\\end{" << name << "}";
350                 end_inset(os);
351         }
352
353         else if (name == "tabular") {
354                 parent_context.check_layout(os);
355                 begin_inset(os, "Tabular ");
356                 handle_tabular(p, os, parent_context);
357                 end_inset(os);
358         }
359
360         else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
361                 parent_context.check_layout(os);
362                 begin_inset(os, "Float " + unstarred_name + "\n");
363                 if (p.next_token().asInput() == "[") {
364                         os << "placement " << p.getArg('[', ']') << '\n';
365                 }
366                 os << "wide " << tostr(is_starred)
367                    << "\ncollapsed false\n\n";
368                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
369                 end_inset(os);
370                 // We don't need really a new paragraph, but
371                 // we must make sure that the next item gets a \begin_layout.
372                 parent_context.new_paragraph(os);
373         }
374
375         else if (name == "minipage") {
376                 string position = "1";
377                 string inner_pos = "0";
378                 string height = "0pt";
379                 string latex_position;
380                 string latex_inner_pos;
381                 string latex_height;
382                 if (p.next_token().asInput() == "[") {
383                         latex_position = p.getArg('[', ']');
384                         switch(latex_position[0]) {
385                         case 't': position = "0"; break;
386                         case 'c': position = "1"; break;
387                         case 'b': position = "2"; break;
388                         default:
389                                 cerr << "invalid position for minipage"
390                                      << endl;
391                                 break;
392                         }
393                         if (p.next_token().asInput() == "[") {
394                                 latex_height = p.getArg('[', ']');
395                                 height = translate_len(latex_height);
396
397                                 if (p.next_token().asInput() == "[") {
398                                         latex_inner_pos = p.getArg('[', ']');
399                                         switch(latex_inner_pos[0]) {
400                                         case 'c': inner_pos = "0"; break;
401                                         case 't': inner_pos = "1"; break;
402                                         case 'b': inner_pos = "2"; break;
403                                         case 's': inner_pos = "3"; break;
404                                         default:
405                                                 cerr << "invalid inner_pos for minipage"
406                                                      << endl;
407                                                 break;
408                                         }
409                                 }
410                         }
411                 }
412                 string width = translate_len(p.verbatim_item());
413                 if (width[0] == '\\') {
414                         // lyx can't handle length variables
415                         ostringstream ss;
416                         ss << "\\begin{minipage}";
417                         if (!latex_position.empty())
418                                 ss << '[' << latex_position << ']';
419                         if (!latex_height.empty())
420                                 ss << '[' << latex_height << ']';
421                         if (!latex_inner_pos.empty())
422                                 ss << '[' << latex_inner_pos << ']';
423                         ss << "{" << width << "}";
424                         handle_ert(os, ss.str(), parent_context);
425                         parent_context.new_paragraph(os);
426                         parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
427                         handle_ert(os, "\\end{minipage}", parent_context);
428                 } else {
429                         parent_context.check_layout(os);
430                         begin_inset(os, "Minipage\n");
431                         os << "position " << position << '\n';
432                         os << "inner_position " << inner_pos << '\n';
433                         os << "height \"" << height << "\"\n";
434                         os << "width \"" << width << "\"\n";
435                         os << "collapsed false\n\n";
436                         parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
437                         end_inset(os);
438                 }
439         }
440
441         // Alignment settings
442         else if (name == "center" || name == "flushleft" || name == "flushright" ||
443                  name == "centering" || name == "raggedright" || name == "raggedleft") {
444                 // We must begin a new paragraph if not already done
445                 if (! parent_context.atParagraphStart()) {
446                         parent_context.check_end_layout(os);
447                         parent_context.new_paragraph(os);
448                 }
449                 if (name == "flushleft" || name == "raggedright")
450                         parent_context.extra_stuff += "\\align left ";
451                 else if (name == "flushright" || name == "raggedleft")
452                         parent_context.extra_stuff += "\\align right ";
453                 else
454                         parent_context.extra_stuff += "\\align center ";
455                 parse_text(p, os, FLAG_END, outer, parent_context);
456                 // Just in case the environment is empty ..
457                 parent_context.extra_stuff.erase();
458                 // We must begin a new paragraph to reset the alignment
459                 parent_context.new_paragraph(os);
460         }
461
462         // The single '=' is meant here.
463         else if ((newlayout = findLayout(parent_context.textclass, name)).get() &&
464                    newlayout->isEnvironment()) {
465                 Context context(true, parent_context.textclass, newlayout,
466                                 parent_context.layout);
467                 parent_context.check_end_layout(os);
468                 switch (context.layout->latextype) {
469                 case  LATEX_LIST_ENVIRONMENT:
470                         context.extra_stuff = "\\labelwidthstring "
471                                 + p.verbatim_item() + '\n';
472                         p.skip_spaces();
473                         break;
474                 case  LATEX_BIB_ENVIRONMENT:
475                         p.verbatim_item(); // swallow next arg
476                         p.skip_spaces();
477                         break;
478                 default:
479                         break;
480                 }
481                 context.check_deeper(os);
482                 parse_text(p, os, FLAG_END, outer, context);
483                 context.check_end_layout(os);
484                 context.check_end_deeper(os);
485                 parent_context.new_paragraph(os);
486         }
487
488         else if (name == "appendix") {
489                 // This is no good latex style, but it works and is used in some documents...
490                 parent_context.check_end_layout(os);
491                 Context context(true, parent_context.textclass, parent_context.layout,
492                                 parent_context.layout);
493                 context.check_layout(os);
494                 os << "\\start_of_appendix\n";
495                 parse_text(p, os, FLAG_END, outer, context);
496                 context.check_end_layout(os);
497         }
498
499         else if (name == "comment") {
500                 parent_context.check_layout(os);
501                 begin_inset(os, "Comment\n");
502                 os << "collapsed false\n";
503                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
504                 end_inset(os);
505         }
506
507         else if (name == "tabbing") {
508                 // We need to remember that we have to handle '\=' specially
509                 handle_ert(os, "\\begin{" + name + "}", parent_context);
510                 parse_text_snippet(p, os, FLAG_END | FLAG_TABBING, outer, parent_context);
511                 handle_ert(os, "\\end{" + name + "}", parent_context);
512         }
513
514         else {
515                 handle_ert(os, "\\begin{" + name + "}", parent_context);
516                 parse_text_snippet(p, os, FLAG_END, outer, parent_context);
517                 handle_ert(os, "\\end{" + name + "}", parent_context);
518         }
519
520         active_environments.pop_back();
521         if (name != "math")
522                 p.skip_spaces();
523 }
524
525 } // anonymous namespace
526
527
528
529
530 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
531                 Context & context)
532 {
533         LyXLayout_ptr newlayout;
534         // Store the latest bibliographystyle (needed for bibtex inset)
535         string bibliographystyle;
536         while (p.good()) {
537                 Token const & t = p.get_token();
538
539 #ifdef FILEDEBUG
540                 cerr << "t: " << t << " flags: " << flags << "\n";
541 #endif
542
543                 if (flags & FLAG_ITEM) {
544                         if (t.cat() == catSpace)
545                                 continue;
546
547                         flags &= ~FLAG_ITEM;
548                         if (t.cat() == catBegin) {
549                                 // skip the brace and collect everything to the next matching
550                                 // closing brace
551                                 flags |= FLAG_BRACE_LAST;
552                                 continue;
553                         }
554
555                         // handle only this single token, leave the loop if done
556                         flags |= FLAG_LEAVE;
557                 }
558
559                 if (t.character() == ']' && (flags & FLAG_BRACK_LAST))
560                         return;
561
562                 //
563                 // cat codes
564                 //
565                 if (t.cat() == catMath) {
566                         // we are inside some text mode thingy, so opening new math is allowed
567                         context.check_layout(os);
568                         begin_inset(os, "Formula ");
569                         Token const & n = p.get_token();
570                         if (n.cat() == catMath && outer) {
571                                 // TeX's $$...$$ syntax for displayed math
572                                 os << "\\[";
573                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
574                                 os << "\\]";
575                                 p.get_token(); // skip the second '$' token
576                         } else {
577                                 // simple $...$  stuff
578                                 p.putback();
579                                 os << '$';
580                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
581                                 os << '$';
582                         }
583                         end_inset(os);
584                 }
585
586                 else if (t.cat() == catSuper || t.cat() == catSub)
587                         cerr << "catcode " << t << " illegal in text mode\n";
588
589                 // Basic support for english quotes. This should be
590                 // extended to other quotes, but is not so easy (a
591                 // left english quote is the same as a right german
592                 // quote...)
593                 else if (t.asInput() == "`"
594                          && p.next_token().asInput() == "`") {
595                         context.check_layout(os);
596                         begin_inset(os, "Quotes ");
597                         os << "eld";
598                         end_inset(os);
599                         p.get_token();
600                         skip_braces(p);
601                 }
602                 else if (t.asInput() == "'"
603                          && p.next_token().asInput() == "'") {
604                         context.check_layout(os);
605                         begin_inset(os, "Quotes ");
606                         os << "erd";
607                         end_inset(os);
608                         p.get_token();
609                         skip_braces(p);
610                 }
611
612                 else if (t.cat() == catSpace || (t.cat() == catNewline && t.cs().size() == 1))
613                         check_space(p, os, context);
614
615                 else if (t.cat() == catLetter ||
616                                t.cat() == catOther ||
617                                t.cat() == catAlign ||
618                                t.cat() == catParameter) {
619                         context.check_layout(os);
620                         os << t.character();
621                 }
622
623                 else if (t.cat() == catNewline || (t.cat() == catEscape && t.cs() == "par")) {
624                         p.skip_spaces();
625                         context.new_paragraph(os);
626                 }
627
628                 else if (t.cat() == catActive) {
629                         context.check_layout(os);
630                         if (t.character() == '~') {
631                                 if (context.layout->free_spacing)
632                                         os << ' ';
633                                 else
634                                         os << "\\InsetSpace ~\n";
635                         } else
636                                 os << t.character();
637                 }
638
639                 else if (t.cat() == catBegin) {
640                         // special handling of size changes
641                         context.check_layout(os);
642                         bool const is_size = is_known(p.next_token().cs(), known_sizes);
643                         Token const prev = p.prev_token();
644                         string const s = parse_text(p, FLAG_BRACE_LAST, outer, context);
645                         if (s.empty() && (p.next_token().character() == '`' ||
646                                           (prev.character() == '-' && p.next_token().character())))
647                                 ; // ignore it in {}`` or -{}-
648                         else if (is_size || s == "[" || s == "]" || s == "*")
649                                 os << s;
650                         else {
651                                 handle_ert(os, "{", context, false);
652                                 // s will end the current layout and begin a new one if necessary
653                                 os << s;
654                                 handle_ert(os, "}", context);
655                         }
656                 }
657
658                 else if (t.cat() == catEnd) {
659                         if (flags & FLAG_BRACE_LAST) {
660                                 return;
661                         }
662                         cerr << "stray '}' in text\n";
663                         handle_ert(os, "}", context);
664                 }
665
666                 else if (t.cat() == catComment) {
667                         context.check_layout(os);
668                         if (!t.cs().empty()) {
669                                 handle_comment(os, '%' + t.cs(), context);
670                                 if (p.next_token().cat() == catNewline) {
671                                         // A newline after a comment line starts a new paragraph
672                                         context.new_paragraph(os);
673                                         p.skip_spaces();
674                                 }
675                         } else {
676                                 // "%\n" combination
677                                 p.skip_spaces();
678                         }
679                 }
680
681                 //
682                 // control sequences
683                 //
684
685                 else if (t.cs() == "(") {
686                         context.check_layout(os);
687                         begin_inset(os, "Formula");
688                         os << " \\(";
689                         parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
690                         os << "\\)";
691                         end_inset(os);
692                 }
693
694                 else if (t.cs() == "[") {
695                         context.check_layout(os);
696                         begin_inset(os, "Formula");
697                         os << " \\[";
698                         parse_math(p, os, FLAG_EQUATION, MATH_MODE);
699                         os << "\\]";
700                         end_inset(os);
701                 }
702
703                 else if (t.cs() == "begin")
704                         parse_environment(p, os, outer, context);
705
706                 else if (t.cs() == "end") {
707                         if (flags & FLAG_END) {
708                                 // eat environment name
709                                 string const name = p.getArg('{', '}');
710                                 if (name != active_environment())
711                                         cerr << "\\end{" + name + "} does not match \\begin{"
712                                                 + active_environment() + "}\n";
713                                 return;
714                         }
715                         p.error("found 'end' unexpectedly");
716                 }
717
718                 else if (t.cs() == "item") {
719                         p.skip_spaces();
720                         string s;
721                         bool optarg = false;
722                         if (p.next_token().character() == '[') {
723                                 p.get_token(); // eat '['
724                                 Context newcontext(false, context.textclass);
725                                 s = parse_text(p, FLAG_BRACK_LAST, outer, newcontext);
726                                 optarg = true;
727                         }
728                         context.set_item();
729                         context.check_layout(os);
730                         if (optarg) {
731                                 if (context.layout->labeltype != LABEL_MANUAL) {
732                                         // lyx does not support \item[\mybullet] in itemize environments
733                                         handle_ert(os, "[", context);
734                                         os << s;
735                                         handle_ert(os, "]", context);
736                                 } else if (!s.empty()) {
737                                         // The space is needed to separate the item from the rest of the sentence.
738                                         os << s << ' ';
739                                         p.skip_spaces();
740                                 }
741                         }
742                 }
743
744                 else if (t.cs() == "bibitem") {
745                         context.set_item();
746                         context.check_layout(os);
747                         os << "\\bibitem ";
748                         os << p.getOpt();
749                         os << '{' << p.verbatim_item() << '}' << "\n";
750                 }
751
752                 else if (t.cs() == "def") {
753                         p.skip_spaces();
754                         context.check_layout(os);
755                         string name = p.get_token().cs();
756                         while (p.next_token().cat() != catBegin)
757                                 name += p.get_token().asString();
758                         handle_ert(os, "\\def\\" + name + '{' + p.verbatim_item() + '}', context);
759                 }
760
761                 else if (t.cs() == "noindent") {
762                         p.skip_spaces();
763                         context.extra_stuff += "\\noindent ";
764                 }
765
766                 else if (t.cs() == "appendix") {
767                         p.skip_spaces();
768                         context.extra_stuff += "\\start_of_appendix ";
769                 }
770
771                 // Must attempt to parse "Section*" before "Section".
772                 else if ((p.next_token().asInput() == "*") &&
773                          // The single '=' is meant here.
774                          (newlayout = findLayout(context.textclass,
775                                                  t.cs() + '*')).get() &&
776                          newlayout->isCommand()) {
777                         p.get_token();
778                         output_command_layout(os, p, outer, context, newlayout);
779                         p.skip_spaces();
780                 }
781
782                 // The single '=' is meant here.
783                 else if ((newlayout = findLayout(context.textclass, t.cs())).get() &&
784                          newlayout->isCommand()) {
785                         output_command_layout(os, p, outer, context, newlayout);
786                         p.skip_spaces();
787                 }
788
789                 else if (t.cs() == "includegraphics") {
790                         map<string, string> opts = split_map(p.getArg('[', ']'));
791                         string name = p.verbatim_item();
792
793                         context.check_layout(os);
794                         begin_inset(os, "Graphics ");
795                         os << "\n\tfilename " << name << '\n';
796                         if (opts.find("width") != opts.end())
797                                 os << "\twidth "
798                                    << translate_len(opts["width"]) << '\n';
799                         if (opts.find("height") != opts.end())
800                                 os << "\theight "
801                                    << translate_len(opts["height"]) << '\n';
802                         if (opts.find("scale") != opts.end()) {
803                                 istringstream iss(opts["scale"]);
804                                 double val;
805                                 iss >> val;
806                                 val = val*100;
807                                 os << "\tscale " << val << '\n';
808                         }
809                         if (opts.find("angle") != opts.end())
810                                 os << "\trotateAngle "
811                                    << opts["angle"] << '\n';
812                         if (opts.find("origin") != opts.end()) {
813                                 ostringstream ss;
814                                 string const opt = opts["origin"];
815                                 if (opt.find('l') != string::npos) ss << "left";
816                                 if (opt.find('r') != string::npos) ss << "right";
817                                 if (opt.find('c') != string::npos) ss << "center";
818                                 if (opt.find('t') != string::npos) ss << "Top";
819                                 if (opt.find('b') != string::npos) ss << "Bottom";
820                                 if (opt.find('B') != string::npos) ss << "Baseline";
821                                 if (!ss.str().empty())
822                                         os << "\trotateOrigin " << ss.str() << '\n';
823                                 else
824                                         cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
825                         }
826                         if (opts.find("keepaspectratio") != opts.end())
827                                 os << "\tkeepAspectRatio\n";
828                         if (opts.find("clip") != opts.end())
829                                 os << "\tclip\n";
830                         if (opts.find("draft") != opts.end())
831                                 os << "\tdraft\n";
832                         if (opts.find("bb") != opts.end())
833                                 os << "\tBoundingBox "
834                                    << opts["bb"] << '\n';
835                         int numberOfbbOptions = 0;
836                         if (opts.find("bbllx") != opts.end())
837                                 numberOfbbOptions++;
838                         if (opts.find("bblly") != opts.end())
839                                 numberOfbbOptions++;
840                         if (opts.find("bburx") != opts.end())
841                                 numberOfbbOptions++;
842                         if (opts.find("bbury") != opts.end())
843                                 numberOfbbOptions++;
844                         if (numberOfbbOptions == 4)
845                                 os << "\tBoundingBox "
846                                    << opts["bbllx"] << opts["bblly"]
847                                    << opts["bburx"] << opts["bbury"] << '\n';
848                         else if (numberOfbbOptions > 0)
849                                 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
850                         numberOfbbOptions = 0;
851                         if (opts.find("natwidth") != opts.end())
852                                 numberOfbbOptions++;
853                         if (opts.find("natheight") != opts.end())
854                                 numberOfbbOptions++;
855                         if (numberOfbbOptions == 2)
856                                 os << "\tBoundingBox 0bp 0bp "
857                                    << opts["natwidth"] << opts["natheight"] << '\n';
858                         else if (numberOfbbOptions > 0)
859                                 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
860                         ostringstream special;
861                         if (opts.find("hiresbb") != opts.end())
862                                 special << "hiresbb,";
863                         if (opts.find("trim") != opts.end())
864                                 special << "trim,";
865                         if (opts.find("viewport") != opts.end())
866                                 special << "viewport=" << opts["viewport"] << ',';
867                         if (opts.find("totalheight") != opts.end())
868                                 special << "totalheight=" << opts["totalheight"] << ',';
869                         if (opts.find("type") != opts.end())
870                                 special << "type=" << opts["type"] << ',';
871                         if (opts.find("ext") != opts.end())
872                                 special << "ext=" << opts["ext"] << ',';
873                         if (opts.find("read") != opts.end())
874                                 special << "read=" << opts["read"] << ',';
875                         if (opts.find("command") != opts.end())
876                                 special << "command=" << opts["command"] << ',';
877                         string s_special = special.str();
878                         if (!s_special.empty()) {
879                                 // We had special arguments. Remove the trailing ','.
880                                 os << "\tspecial " << s_special.substr(0, s_special.size() - 1) << '\n';
881                         }
882                         // TODO: Handle the unknown settings better.
883                         // Warn about invalid options.
884                         // Check wether some option was given twice.
885                         end_inset(os);
886                 }
887
888                 else if (t.cs() == "footnote") {
889                         p.skip_spaces();
890                         context.check_layout(os);
891                         begin_inset(os, "Foot\n");
892                         os << "collapsed true\n\n";
893                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
894                         end_inset(os);
895                 }
896
897                 else if (t.cs() == "marginpar") {
898                         p.skip_spaces();
899                         context.check_layout(os);
900                         begin_inset(os, "Marginal\n");
901                         os << "collapsed true\n\n";
902                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
903                         end_inset(os);
904                 }
905
906                 else if (t.cs() == "ensuremath") {
907                         p.skip_spaces();
908                         context.check_layout(os);
909                         Context newcontext(false, context.textclass);
910                         string s = parse_text(p, FLAG_ITEM, false, newcontext);
911                         if (s == "±" || s == "³" || s == "²" || s == "µ")
912                                 os << s;
913                         else
914                                 handle_ert(os, "\\ensuremath{" + s + "}",
915                                            context);
916                 }
917
918                 else if (t.cs() == "hfill") {
919                         context.check_layout(os);
920                         os << "\n\\hfill\n";
921                         skip_braces(p);
922                         p.skip_spaces();
923                 }
924
925                 else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
926                         p.skip_spaces();
927                         skip_braces(p); // swallow this
928                 }
929
930                 else if (t.cs() == "tableofcontents") {
931                         p.skip_spaces();
932                         context.check_layout(os);
933                         begin_inset(os, "LatexCommand \\tableofcontents\n");
934                         end_inset(os);
935                         skip_braces(p); // swallow this
936                 }
937
938                 else if (t.cs() == "listoffigures") {
939                         p.skip_spaces();
940                         context.check_layout(os);
941                         begin_inset(os, "FloatList figure\n");
942                         end_inset(os);
943                         skip_braces(p); // swallow this
944                 }
945
946                 else if (t.cs() == "listoftables") {
947                         p.skip_spaces();
948                         context.check_layout(os);
949                         begin_inset(os, "FloatList table\n");
950                         end_inset(os);
951                         skip_braces(p); // swallow this
952                 }
953
954                 else if (t.cs() == "listof") {
955                         p.skip_spaces(true);
956                         string const name = p.get_token().asString();
957                         if (context.textclass.floats().typeExist(name)) {
958                                 context.check_layout(os);
959                                 begin_inset(os, "FloatList ");
960                                 os << name << "\n";
961                                 end_inset(os);
962                                 p.get_token(); // swallow second arg
963                         } else
964                                 handle_ert(os, "\\listof{" + name + "}", context);
965                 }
966
967                 else if (t.cs() == "textrm") {
968                         context.check_layout(os);
969                         os << "\n\\family roman \n";
970                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
971                         os << "\n\\family default \n";
972                 }
973
974                 else if (t.cs() == "textsf") {
975                         context.check_layout(os);
976                         os << "\n\\family sans \n";
977                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
978                         os << "\n\\family default \n";
979                 }
980
981                 else if (t.cs() == "textsl") {
982                         context.check_layout(os);
983                         os << "\n\\shape slanted \n";
984                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
985                         os << "\n\\shape default \n";
986                 }
987
988                 else if (t.cs() == "texttt") {
989                         context.check_layout(os);
990                         os << "\n\\family typewriter \n";
991                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
992                         os << "\n\\family default \n";
993                 }
994
995                 else if (t.cs() == "textit") {
996                         context.check_layout(os);
997                         os << "\n\\shape italic \n";
998                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
999                         os << "\n\\shape default \n";
1000                 }
1001
1002                 else if (t.cs() == "textsc") {
1003                         context.check_layout(os);
1004                         os << "\n\\noun on \n";
1005                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1006                         os << "\n\\noun default \n";
1007                 }
1008
1009                 else if (t.cs() == "textbf") {
1010                         context.check_layout(os);
1011                         os << "\n\\series bold \n";
1012                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1013                         os << "\n\\series default \n";
1014                 }
1015
1016                 else if (t.cs() == "underbar") {
1017                         context.check_layout(os);
1018                         os << "\n\\bar under \n";
1019                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1020                         os << "\n\\bar default \n";
1021                 }
1022
1023                 else if (t.cs() == "emph" || t.cs() == "noun") {
1024                         context.check_layout(os);
1025                         os << "\n\\" << t.cs() << " on \n";
1026                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1027                         os << "\n\\" << t.cs() << " default \n";
1028                 }
1029
1030                 else if (is_known(t.cs(), known_latex_commands)) {
1031                         context.check_layout(os);
1032                         begin_inset(os, "LatexCommand ");
1033                         os << '\\' << t.cs();
1034                         os << p.getOpt();
1035                         os << p.getOpt();
1036                         os << '{' << p.verbatim_item() << "}\n";
1037                         end_inset(os);
1038                 }
1039
1040                 else if (is_known(t.cs(), known_quotes)) {
1041                         char const ** where = is_known(t.cs(), known_quotes);
1042                         context.check_layout(os);
1043                         begin_inset(os, "Quotes ");
1044                         os << known_coded_quotes[where - known_quotes];
1045                         end_inset(os);
1046                         skip_braces(p);
1047                 }
1048
1049                 else if (is_known(t.cs(), known_sizes)) {
1050                         char const ** where = is_known(t.cs(), known_sizes);
1051                         context.check_layout(os);
1052                         os << "\n\\size " << known_coded_sizes[where - known_sizes] << "\n";
1053                         p.skip_spaces();
1054                 }
1055
1056                 else if (t.cs() == "LyX" || t.cs() == "TeX"
1057                          || t.cs() == "LaTeX") {
1058                         context.check_layout(os);
1059                         os << t.cs();
1060                         skip_braces(p); // eat {}
1061                 }
1062
1063                 else if (t.cs() == "LaTeXe") {
1064                         context.check_layout(os);
1065                         os << "LaTeX2e";
1066                         skip_braces(p); // eat {}
1067                 }
1068
1069                 else if (t.cs() == "ldots") {
1070                         context.check_layout(os);
1071                         skip_braces(p);
1072                         os << "\\SpecialChar \\ldots{}\n";
1073                 }
1074
1075                 else if (t.cs() == "lyxarrow") {
1076                         context.check_layout(os);
1077                         os << "\\SpecialChar \\menuseparator\n";
1078                         skip_braces(p);
1079                 }
1080
1081                 else if (t.cs() == "textcompwordmark") {
1082                         context.check_layout(os);
1083                         os << "\\SpecialChar \\textcompwordmark{}\n";
1084                         skip_braces(p);
1085                 }
1086
1087                 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
1088                         context.check_layout(os);
1089                         os << "\\SpecialChar \\@.\n";
1090                         p.get_token();
1091                 }
1092
1093                 else if (t.cs() == "-") {
1094                         context.check_layout(os);
1095                         os << "\\SpecialChar \\-\n";
1096                 }
1097
1098                 else if (t.cs() == "textasciitilde") {
1099                         context.check_layout(os);
1100                         os << '~';
1101                         skip_braces(p);
1102                 }
1103
1104                 else if (t.cs() == "textasciicircum") {
1105                         context.check_layout(os);
1106                         os << '^';
1107                         skip_braces(p);
1108                 }
1109
1110                 else if (t.cs() == "textbackslash") {
1111                         context.check_layout(os);
1112                         os << "\n\\backslash \n";
1113                         skip_braces(p);
1114                 }
1115
1116                 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
1117                             || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
1118                             || t.cs() == "%") {
1119                         context.check_layout(os);
1120                         os << t.cs();
1121                 }
1122
1123                 else if (t.cs() == "char") {
1124                         context.check_layout(os);
1125                         if (p.next_token().character() == '`') {
1126                                 p.get_token();
1127                                 if (p.next_token().cs() == "\"") {
1128                                         p.get_token();
1129                                         os << '"';
1130                                         skip_braces(p);
1131                                 } else {
1132                                         handle_ert(os, "\\char`", context);
1133                                 }
1134                         } else {
1135                                 handle_ert(os, "\\char", context);
1136                         }
1137                 }
1138
1139                 else if (t.cs() == "\"") {
1140                         context.check_layout(os);
1141                         string const name = p.verbatim_item();
1142                              if (name == "a") os << 'ä';
1143                         else if (name == "o") os << 'ö';
1144                         else if (name == "u") os << 'ü';
1145                         else if (name == "A") os << 'Ä';
1146                         else if (name == "O") os << 'Ö';
1147                         else if (name == "U") os << 'Ü';
1148                         else handle_ert(os, "\"{" + name + "}", context);
1149                 }
1150
1151                 // Problem: \= creates a tabstop inside the tabbing environment
1152                 // and else an accent. In the latter case we really would want
1153                 // \={o} instead of \= o.
1154                 else if (t.cs() == "H" || t.cs() == "c" || t.cs() == "^" || t.cs() == "'"
1155                       || t.cs() == "~" || t.cs() == "." || (t.cs() == "=" && ! (flags & FLAG_TABBING))) {
1156                         // we need the trim as the LyX parser chokes on such spaces
1157                         context.check_layout(os);
1158                         os << "\n\\i \\" << t.cs() << "{"
1159                            << trim(parse_text(p, FLAG_ITEM, outer, context), " ") << "}\n";
1160                 }
1161
1162                 else if (t.cs() == "ss") {
1163                         context.check_layout(os);
1164                         os << "ß";
1165                 }
1166
1167                 else if (t.cs() == "i" || t.cs() == "j") {
1168                         context.check_layout(os);
1169                         os << "\\" << t.cs() << ' ';
1170                 }
1171
1172                 else if (t.cs() == "\\") {
1173                         context.check_layout(os);
1174                         string const next = p.next_token().asInput();
1175                         if (next == "[")
1176                                 handle_ert(os, "\\\\" + p.getOpt(), context);
1177                         else if (next == "*") {
1178                                 p.get_token();
1179                                 handle_ert(os, "\\\\*" + p.getOpt(), context);
1180                         }
1181                         else {
1182                                 os << "\n\\newline \n";
1183                         }
1184                 }
1185
1186                 else if (t.cs() == "input" || t.cs() == "include"
1187                          || t.cs() == "verbatiminput") {
1188                         string name = '\\' + t.cs();
1189                         if (t.cs() == "verbatiminput"
1190                             && p.next_token().asInput() == "*")
1191                                 name += p.get_token().asInput();
1192                         context.check_layout(os);
1193                         begin_inset(os, "Include ");
1194                         string filename(p.getArg('{', '}'));
1195                         string lyxname(lyx::support::ChangeExtension(filename, ".lyx"));
1196                         if (tex2lyx(filename, lyxname)) {
1197                                 os << name << '{' << lyxname << "}\n";
1198                         } else {
1199                                 os << name << '{' << filename << "}\n";
1200                         }
1201                         os << "preview false\n";
1202                         end_inset(os);
1203                 }
1204
1205                 else if (t.cs() == "fancyhead") {
1206                         context.check_layout(os);
1207                         ostringstream ss;
1208                         ss << "\\fancyhead";
1209                         ss << p.getOpt();
1210                         ss << '{' << p.verbatim_item() << "}\n";
1211                         handle_ert(os, ss.str(), context);
1212                 }
1213
1214                 else if (t.cs() == "bibliographystyle") {
1215                         // store new bibliographystyle
1216                         bibliographystyle = p.verbatim_item();
1217                         // output new bibliographystyle.
1218                         // This is only necessary if used in some other macro than \bibliography.
1219                         handle_ert(os, "\\bibliographystyle{" + bibliographystyle + "}", context);
1220                 }
1221
1222                 else if (t.cs() == "bibliography") {
1223                         context.check_layout(os);
1224                         begin_inset(os, "LatexCommand ");
1225                         os << "\\bibtex";
1226                         // Do we have a bibliographystyle set?
1227                         if (!bibliographystyle.empty()) {
1228                                 os << '[' << bibliographystyle << ']';
1229                         }
1230                         os << '{' << p.verbatim_item() << "}\n";
1231                         end_inset(os);
1232                 }
1233
1234                 else if ( t.cs() == "smallskip" ||
1235                           t.cs() == "medskip" ||
1236                           t.cs() == "bigskip" ||
1237                           t.cs() == "vfill" ||
1238                          (t.cs() == "vspace" && p.next_token().asInput() != "*")) {
1239                         string arg;
1240                         if (t.cs() == "vspace")
1241                                 arg = p.getArg('{', '}');
1242                         else
1243                                 arg = t.cs();
1244                         // We may only add the vspace to the current context if the
1245                         // current paragraph is not empty.
1246                         if (context.atParagraphStart()
1247                             && (p.next_token().cat() != catNewline || p.next_token().cs().size() == 1)
1248                             && (! (p.next_token().cat() == catEscape && p.next_token().cs() == "end"))
1249                             && (! (p.next_token().cat() == catEscape && p.next_token().cs() == "par"))) {
1250                                 context.extra_stuff += "\\added_space_top " + arg + " ";
1251                                 p.skip_spaces();
1252                         } else {
1253                                 if (t.cs() == "vspace")
1254                                         handle_ert(os, t.asInput() + '{' + arg + '}', context);
1255                                 else
1256                                         handle_ert(os, t.asInput(), context);
1257                         }
1258                         // Would be nice to recognize added_space_bottom too...
1259                         // At the moment this is parsed as added_space_top of the
1260                         // next paragraph.
1261                 }
1262
1263                 else {
1264                         //cerr << "#: " << t << " mode: " << mode << endl;
1265                         // heuristic: read up to next non-nested space
1266                         /*
1267                         string s = t.asInput();
1268                         string z = p.verbatim_item();
1269                         while (p.good() && z != " " && z.size()) {
1270                                 //cerr << "read: " << z << endl;
1271                                 s += z;
1272                                 z = p.verbatim_item();
1273                         }
1274                         cerr << "found ERT: " << s << endl;
1275                         handle_ert(os, s + ' ', context);
1276                         */
1277                         string name = t.asInput();
1278                         if (p.next_token().asInput() == "*") {
1279                                 // Starred commands like \vspace*{}
1280                                 p.get_token();                          // Eat '*'
1281                                 name += '*';
1282                         }
1283                         if (! parse_command(t.asInput(), p, os, outer, context))
1284                                 handle_ert(os, name, context);
1285                 }
1286
1287                 if (flags & FLAG_LEAVE) {
1288                         flags &= ~FLAG_LEAVE;
1289                         break;
1290                 }
1291         }
1292 }
1293
1294
1295 // }])