]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/text.C
handle \cr in math arrays and tables
[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 "lengthcommon.h"
20 #include "support/lstrings.h"
21 #include "support/tostr.h"
22 #include "support/filetools.h"
23
24 #include <iostream>
25 #include <map>
26 #include <sstream>
27 #include <vector>
28
29 using lyx::support::rtrim;
30 using lyx::support::suffixIs;
31 using lyx::support::contains;
32 using lyx::support::subst;
33
34 using std::cerr;
35 using std::endl;
36
37 using std::map;
38 using std::ostream;
39 using std::ostringstream;
40 using std::istringstream;
41 using std::string;
42 using std::vector;
43
44
45 /// thin wrapper around parse_text using a string
46 string parse_text(Parser & p, unsigned flags, const bool outer,
47                   Context & context)
48 {
49         ostringstream os;
50         parse_text(p, os, flags, outer, context);
51         return os.str();
52 }
53
54
55 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
56                 Context & context)
57 {
58         Context newcontext(true, context.textclass);
59         newcontext.font = context.font;
60         parse_text(p, os, flags, outer, newcontext);
61         newcontext.check_end_layout(os);
62 }
63
64
65 /// parses a paragraph snippet, useful for example for \emph{...}
66 void parse_text_snippet(Parser & p, ostream & os, unsigned flags, bool outer,
67                 Context & context)
68 {
69         Context newcontext(false, context.textclass);
70         newcontext.font = context.font;
71         parse_text(p, os, flags, outer, newcontext);
72         // should not be needed
73         newcontext.check_end_layout(os);
74 }
75
76
77 namespace {
78
79 char const * const known_latex_commands[] = { "ref", "cite", "label", "index",
80 "printindex", "pageref", "url", "vref", "vpageref", "prettyref", "eqref", 0 };
81
82 /// LaTeX names for quotes
83 char const * const known_quotes[] = { "glqq", "grqq", "quotedblbase",
84 "textquotedblleft", "quotesinglbase", "guilsinglleft", "guilsinglright", 0};
85
86 /// the same as known_quotes with .lyx names
87 char const * const known_coded_quotes[] = { "gld", "grd", "gld",
88 "grd", "gls", "fls", "frd", 0};
89
90 /// LaTeX names for font sizes
91 char const * const known_sizes[] = { "tiny", "scriptsize", "footnotesize",
92 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
93
94 /// the same as known_sizes with .lyx names
95 char const * const known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
96 "small", "normal", "large", "larger", "largest",  "huge", "giant", 0};
97
98 /// LaTeX 2.09 names for font families
99 char const * const known_old_font_families[] = { "rm", "sf", "tt", 0};
100
101 /// LaTeX names for font families
102 char const * const known_font_families[] = { "rmfamily", "sffamily",
103 "ttfamily", 0};
104
105 /// the same as known_old_font_families and known_font_families with .lyx names
106 char const * const known_coded_font_families[] = { "roman", "sans",
107 "typewriter", 0};
108
109 /// LaTeX 2.09 names for font series
110 char const * const known_old_font_series[] = { "bf", 0};
111
112 /// LaTeX names for font series
113 char const * const known_font_series[] = { "bfseries", "mdseries", 0};
114
115 /// the same as known_old_font_series and known_font_series with .lyx names
116 char const * const known_coded_font_series[] = { "bold", "medium", 0};
117
118 /// LaTeX 2.09 names for font shapes
119 char const * const known_old_font_shapes[] = { "it", "sl", "sc", 0};
120
121 /// LaTeX names for font shapes
122 char const * const known_font_shapes[] = { "itshape", "slshape", "scshape",
123 "upshape", 0};
124
125 /// the same as known_old_font_shapes and known_font_shapes with .lyx names
126 char const * const known_coded_font_shapes[] = { "italic", "slanted",
127 "smallcaps", "up", 0};
128
129
130 /// splits "x=z, y=b" into a map
131 map<string, string> split_map(string const & s)
132 {
133         map<string, string> res;
134         vector<string> v;
135         split(s, v);
136         for (size_t i = 0; i < v.size(); ++i) {
137                 size_t const pos   = v[i].find('=');
138                 string const index = v[i].substr(0, pos);
139                 string const value = v[i].substr(pos + 1, string::npos);
140                 res[trim(index)] = trim(value);
141         }
142         return res;
143 }
144
145
146 /*!
147  * Split a LaTeX length into value and unit.
148  * The latter can be a real unit like "pt", or a latex length variable
149  * like "\textwidth". The unit may contain additional stuff like glue
150  * lengths, but we don't care, because such lengths are ERT anyway.
151  * \return true if \param value and \param unit are valid.
152  */
153 bool splitLatexLength(string const & len, string & value, string & unit)
154 {
155         if (len.empty())
156                 return false;
157         const string::size_type i = len.find_first_not_of(" -+0123456789.,");
158         //'4,5' is a valid LaTeX length number. Change it to '4.5'
159         string const length = subst(len, ',', '.');
160         if (i == string::npos)
161                 return false;
162         if (i == 0) {
163                 if (len[0] == '\\') {
164                         // We had something like \textwidth without a factor
165                         value = "1.0";
166                 } else {
167                         return false;
168                 }
169         } else {
170                 value = trim(string(length, 0, i));
171         }
172         if (value == "-")
173                 value = "-1.0";
174         // 'cM' is a valid LaTeX length unit. Change it to 'cm'
175         if (contains(len, '\\'))
176                 unit = trim(string(len, i));
177         else
178                 unit = lyx::support::lowercase(trim(string(len, i)));
179         return true;
180 }
181
182
183 /// A simple function to translate a latex length to something lyx can
184 /// understand. Not perfect, but rather best-effort.
185 bool translate_len(string const & length, string & valstring, string & unit)
186 {
187         if (!splitLatexLength(length, valstring, unit))
188                 return false;
189         // LyX uses percent values
190         double value;
191         istringstream iss(valstring);
192         iss >> value;
193         value *= 100;
194         ostringstream oss;
195         oss << value;
196         string const percentval = oss.str();
197         // a normal length
198         if (unit.empty() || unit[0] != '\\')
199                 return true;
200         string::size_type const i = unit.find(' ');
201         string const endlen = (i == string::npos) ? string() : string(unit, i);
202         if (unit == "\\textwidth") {
203                 valstring = percentval;
204                 unit = "text%" + endlen;
205         } else if (unit == "\\columnwidth") {
206                 valstring = percentval;
207                 unit = "col%" + endlen;
208         } else if (unit == "\\paperwidth") {
209                 valstring = percentval;
210                 unit = "page%" + endlen;
211         } else if (unit == "\\linewidth") {
212                 valstring = percentval;
213                 unit = "line%" + endlen;
214         } else if (unit == "\\paperheight") {
215                 valstring = percentval;
216                 unit = "pheight%" + endlen;
217         } else if (unit == "\\textheight") {
218                 valstring = percentval;
219                 unit = "theight%" + endlen;
220         }
221         return true;
222 }
223
224
225 string translate_len(string const & length)
226 {
227         string unit;
228         string value;
229         if (translate_len(length, value, unit))
230                 return value + unit;
231         // If the input is invalid, return what we have.
232         return length;
233 }
234
235
236 /*!
237  * Translates a LaTeX length into \param value, \param unit and
238  * \param special parts suitable for a box inset.
239  * The difference from translate_len() is that a box inset knows about
240  * some special "units" that are stored in \param special.
241  */
242 void translate_box_len(string const & length, string & value, string & unit, string & special)
243 {
244         if (translate_len(length, value, unit)) {
245                 if (unit == "\\height" || unit == "\\depth" ||
246                     unit == "\\totalheight" || unit == "\\width") {
247                         special = unit.substr(1);
248                         // The unit is not used, but LyX requires a dummy setting
249                         unit = "in";
250                 } else
251                         special = "none";
252         } else {
253                 value.clear();
254                 unit = length;
255                 special = "none";
256         }
257 }
258
259
260 void begin_inset(ostream & os, string const & name)
261 {
262         os << "\n\\begin_inset " << name;
263 }
264
265
266 void end_inset(ostream & os)
267 {
268         os << "\n\\end_inset \n\n";
269 }
270
271
272 void skip_braces(Parser & p)
273 {
274         if (p.next_token().cat() != catBegin)
275                 return;
276         p.get_token();
277         if (p.next_token().cat() == catEnd) {
278                 p.get_token();
279                 return;
280         }
281         p.putback();
282 }
283
284
285 void handle_ert(ostream & os, string const & s, Context & context, bool check_layout = true)
286 {
287         if (check_layout) {
288                 // We must have a valid layout before outputting the ERT inset.
289                 context.check_layout(os);
290         }
291         Context newcontext(true, context.textclass);
292         begin_inset(os, "ERT");
293         os << "\nstatus collapsed\n";
294         newcontext.check_layout(os);
295         for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
296                 if (*it == '\\')
297                         os << "\n\\backslash \n";
298                 else if (*it == '\n')
299                         os << "\n\\newline \n";
300                 else
301                         os << *it;
302         }
303         newcontext.check_end_layout(os);
304         end_inset(os);
305 }
306
307
308 void handle_comment(ostream & os, string const & s, Context & context)
309 {
310         // TODO: Handle this better
311         Context newcontext(true, context.textclass);
312         begin_inset(os, "ERT");
313         os << "\nstatus collapsed\n";
314         newcontext.check_layout(os);
315         for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
316                 if (*it == '\\')
317                         os << "\n\\backslash \n";
318                 else
319                         os << *it;
320         }
321         // make sure that our comment is the last thing on the line
322         os << "\n\\newline";
323         newcontext.check_end_layout(os);
324         end_inset(os);
325 }
326
327
328 class isLayout : public std::unary_function<LyXLayout_ptr, bool> {
329 public:
330         isLayout(string const name) : name_(name) {}
331         bool operator()(LyXLayout_ptr const & ptr) const {
332                 return ptr->latexname() == name_;
333         }
334 private:
335         string const name_;
336 };
337
338
339 LyXLayout_ptr findLayout(LyXTextClass const & textclass,
340                          string const & name)
341 {
342         LyXTextClass::const_iterator beg  = textclass.begin();
343         LyXTextClass::const_iterator end = textclass.end();
344
345         LyXTextClass::const_iterator
346                 it = std::find_if(beg, end, isLayout(name));
347
348         return (it == end) ? LyXLayout_ptr() : *it;
349 }
350
351
352 void eat_whitespace(Parser &, ostream &, Context &, bool);
353
354
355 void output_command_layout(ostream & os, Parser & p, bool outer,
356                            Context & parent_context,
357                            LyXLayout_ptr newlayout)
358 {
359         parent_context.check_end_layout(os);
360         Context context(true, parent_context.textclass, newlayout,
361                         parent_context.layout, parent_context.font);
362         if (parent_context.deeper_paragraph) {
363                 // We are beginning a nested environment after a
364                 // deeper paragraph inside the outer list environment.
365                 // Therefore we don't need to output a "begin deeper".
366                 context.need_end_deeper = true;
367         }
368         context.check_deeper(os);
369         context.check_layout(os);
370         if (context.layout->optionalargs > 0) {
371                 eat_whitespace(p, os, context, false);
372                 if (p.next_token().character() == '[') {
373                         p.get_token(); // eat '['
374                         begin_inset(os, "OptArg\n");
375                         os << "status collapsed\n\n";
376                         parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
377                         end_inset(os);
378                         eat_whitespace(p, os, context, false);
379                 }
380         }
381         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
382         context.check_end_layout(os);
383         if (parent_context.deeper_paragraph) {
384                 // We must suppress the "end deeper" because we
385                 // suppressed the "begin deeper" above.
386                 context.need_end_deeper = false;
387         }
388         context.check_end_deeper(os);
389         // We don't need really a new paragraph, but
390         // we must make sure that the next item gets a \begin_layout.
391         parent_context.new_paragraph(os);
392 }
393
394
395 /*!
396  * Output a space if necessary.
397  * This function gets called for every whitespace token.
398  *
399  * We have three cases here:
400  * 1. A space must be suppressed. Example: The lyxcode case below
401  * 2. A space may be suppressed. Example: Spaces before "\par"
402  * 3. A space must not be suppressed. Example: A space between two words
403  *
404  * We currently handle only 1. and 3 and from 2. only the case of
405  * spaces before newlines as a side effect.
406  *
407  * 2. could be used to suppress as many spaces as possible. This has two effects:
408  * - Reimporting LyX generated LaTeX files changes almost no whitespace
409  * - Superflous whitespace from non LyX generated LaTeX files is removed.
410  * The drawback is that the logic inside the function becomes
411  * complicated, and that is the reason why it is not implemented.
412  */
413 void check_space(Parser const & p, ostream & os, Context & context)
414 {
415         Token const next = p.next_token();
416         Token const curr = p.curr_token();
417         // A space before a single newline and vice versa must be ignored
418         // LyX emits a newline before \end{lyxcode}.
419         // This newline must be ignored,
420         // otherwise LyX will add an additional protected space.
421         if (next.cat() == catSpace ||
422             next.cat() == catNewline ||
423             (next.cs() == "end" && context.layout->free_spacing && curr.cat() == catNewline)) {
424                 return;
425         }
426         context.check_layout(os);
427         os << ' ';
428 }
429
430
431 /*!
432  * Check whether \p command is a known command. If yes,
433  * handle the command with all arguments.
434  * \return true if the command was parsed, false otherwise.
435  */
436 bool parse_command(string const & command, Parser & p, ostream & os,
437                    bool outer, Context & context)
438 {
439         if (known_commands.find(command) != known_commands.end()) {
440                 vector<ArgumentType> const & template_arguments = known_commands[command];
441                 string ert = command;
442                 size_t no_arguments = template_arguments.size();
443                 for (size_t i = 0; i < no_arguments; ++i) {
444                         switch (template_arguments[i]) {
445                         case required:
446                                 // This argument contains regular LaTeX
447                                 handle_ert(os, ert + '{', context);
448                                 parse_text(p, os, FLAG_ITEM, outer, context);
449                                 ert = "}";
450                                 break;
451                         case verbatim:
452                                 // This argument may contain special characters
453                                 ert += '{' + p.verbatim_item() + '}';
454                                 break;
455                         case optional:
456                                 ert += p.getOpt();
457                                 break;
458                         }
459                 }
460                 handle_ert(os, ert, context);
461                 return true;
462         }
463         return false;
464 }
465
466
467 /// Parses a minipage or parbox
468 void parse_box(Parser & p, ostream & os, unsigned flags, bool outer,
469                Context & parent_context, bool use_parbox)
470 {
471         string position;
472         string inner_pos;
473         string height_value = "0";
474         string height_unit = "pt";
475         string height_special = "none";
476         string latex_height;
477         if (p.next_token().asInput() == "[") {
478                 position = p.getArg('[', ']');
479                 if (position != "t" && position != "c" && position != "b") {
480                         position = "c";
481                         cerr << "invalid position for minipage/parbox" << endl;
482                 }
483                 if (p.next_token().asInput() == "[") {
484                         latex_height = p.getArg('[', ']');
485                         translate_box_len(latex_height, height_value, height_unit, height_special);
486
487                         if (p.next_token().asInput() == "[") {
488                                 inner_pos = p.getArg('[', ']');
489                                 if (inner_pos != "c" && inner_pos != "t" &&
490                                     inner_pos != "b" && inner_pos != "s") {
491                                         inner_pos = position;
492                                         cerr << "invalid inner_pos for minipage/parbox"
493                                              << endl;
494                                 }
495                         }
496                 }
497         }
498         string width_value;
499         string width_unit;
500         string const latex_width = p.verbatim_item();
501         translate_len(latex_width, width_value, width_unit);
502         if (contains(width_unit, '\\') || contains(height_unit, '\\')) {
503                 // LyX can't handle length variables
504                 ostringstream ss;
505                 if (use_parbox)
506                         ss << "\\parbox";
507                 else
508                         ss << "\\begin{minipage}";
509                 if (!position.empty())
510                         ss << '[' << position << ']';
511                 if (!latex_height.empty())
512                         ss << '[' << latex_height << ']';
513                 if (!inner_pos.empty())
514                         ss << '[' << inner_pos << ']';
515                 ss << "{" << latex_width << "}";
516                 if (use_parbox)
517                         ss << '{';
518                 handle_ert(os, ss.str(), parent_context);
519                 parent_context.new_paragraph(os);
520                 parse_text_in_inset(p, os, flags, outer, parent_context);
521                 if (use_parbox)
522                         handle_ert(os, "}", parent_context);
523                 else
524                         handle_ert(os, "\\end{minipage}", parent_context);
525         } else {
526                 // LyX does not like empty positions, so we have
527                 // to set them to the LaTeX default values here.
528                 if (position.empty())
529                         position = "c";
530                 if (inner_pos.empty())
531                         inner_pos = position;
532                 parent_context.check_layout(os);
533                 begin_inset(os, "Box Frameless\n");
534                 os << "position \"" << position << "\"\n";
535                 os << "hor_pos \"c\"\n";
536                 os << "has_inner_box 1\n";
537                 os << "inner_pos \"" << inner_pos << "\"\n";
538                 os << "use_parbox " << use_parbox << "\n";
539                 os << "width \"" << width_value << width_unit << "\"\n";
540                 os << "special \"none\"\n";
541                 os << "height \"" << height_value << height_unit << "\"\n";
542                 os << "height_special \"" << height_special << "\"\n";
543                 os << "status open\n\n";
544                 parse_text_in_inset(p, os, flags, outer, parent_context);
545                 end_inset(os);
546 #ifdef PRESERVE_LAYOUT
547                 // lyx puts a % after the end of the minipage
548                 if (p.next_token().cat() == catNewline && p.next_token().cs().size() > 1) {
549                         // new paragraph
550                         //handle_comment(os, "%dummy", parent_context);
551                         p.get_token();
552                         p.skip_spaces();
553                         parent_context.new_paragraph(os);
554                 }
555                 else if (p.next_token().cat() == catSpace || p.next_token().cat() == catNewline) {
556                         //handle_comment(os, "%dummy", parent_context);
557                         p.get_token();
558                         p.skip_spaces();
559                         // We add a protected space if something real follows
560                         if (p.good() && p.next_token().cat() != catComment) {
561                                 os << "\\InsetSpace ~\n";
562                         }
563                 }
564 #endif
565         }
566 }
567
568
569 void parse_environment(Parser & p, ostream & os, bool outer,
570                        Context & parent_context)
571 {
572         LyXLayout_ptr newlayout;
573         string const name = p.getArg('{', '}');
574         const bool is_starred = suffixIs(name, '*');
575         string const unstarred_name = rtrim(name, "*");
576         eat_whitespace(p, os, parent_context, false);
577         active_environments.push_back(name);
578
579         if (is_math_env(name)) {
580                 parent_context.check_layout(os);
581                 begin_inset(os, "Formula ");
582                 os << "\\begin{" << name << "}";
583                 parse_math(p, os, FLAG_END, MATH_MODE);
584                 os << "\\end{" << name << "}";
585                 end_inset(os);
586         }
587
588         else if (name == "tabular") {
589                 parent_context.check_layout(os);
590                 begin_inset(os, "Tabular ");
591                 handle_tabular(p, os, parent_context);
592                 end_inset(os);
593         }
594
595         else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
596                 parent_context.check_layout(os);
597                 begin_inset(os, "Float " + unstarred_name + "\n");
598                 if (p.next_token().asInput() == "[") {
599                         os << "placement " << p.getArg('[', ']') << '\n';
600                 }
601                 os << "wide " << tostr(is_starred)
602                    << "\nstatus open\n\n";
603                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
604                 end_inset(os);
605                 // We don't need really a new paragraph, but
606                 // we must make sure that the next item gets a \begin_layout.
607                 parent_context.new_paragraph(os);
608         }
609
610         else if (name == "minipage")
611                 parse_box(p, os, FLAG_END, outer, parent_context, false);
612
613         // Alignment settings
614         else if (name == "center" || name == "flushleft" || name == "flushright" ||
615                  name == "centering" || name == "raggedright" || name == "raggedleft") {
616                 // We must begin a new paragraph if not already done
617                 if (! parent_context.atParagraphStart()) {
618                         parent_context.check_end_layout(os);
619                         parent_context.new_paragraph(os);
620                 }
621                 if (name == "flushleft" || name == "raggedright")
622                         parent_context.add_extra_stuff("\\align left ");
623                 else if (name == "flushright" || name == "raggedleft")
624                         parent_context.add_extra_stuff("\\align right ");
625                 else
626                         parent_context.add_extra_stuff("\\align center ");
627                 parse_text(p, os, FLAG_END, outer, parent_context);
628                 // Just in case the environment is empty ..
629                 parent_context.extra_stuff.erase();
630                 // We must begin a new paragraph to reset the alignment
631                 parent_context.new_paragraph(os);
632         }
633
634         // The single '=' is meant here.
635         else if ((newlayout = findLayout(parent_context.textclass, name)).get() &&
636                    newlayout->isEnvironment()) {
637                 Context context(true, parent_context.textclass, newlayout,
638                                 parent_context.layout, parent_context.font);
639                 if (parent_context.deeper_paragraph) {
640                         // We are beginning a nested environment after a
641                         // deeper paragraph inside the outer list environment.
642                         // Therefore we don't need to output a "begin deeper".
643                         context.need_end_deeper = true;
644                 }
645                 parent_context.check_end_layout(os);
646                 switch (context.layout->latextype) {
647                 case  LATEX_LIST_ENVIRONMENT:
648                         context.extra_stuff = "\\labelwidthstring "
649                                 + p.verbatim_item() + '\n';
650                         p.skip_spaces();
651                         break;
652                 case  LATEX_BIB_ENVIRONMENT:
653                         p.verbatim_item(); // swallow next arg
654                         p.skip_spaces();
655                         break;
656                 default:
657                         break;
658                 }
659                 context.check_deeper(os);
660                 parse_text(p, os, FLAG_END, outer, context);
661                 context.check_end_layout(os);
662                 if (parent_context.deeper_paragraph) {
663                         // We must suppress the "end deeper" because we
664                         // suppressed the "begin deeper" above.
665                         context.need_end_deeper = false;
666                 }
667                 context.check_end_deeper(os);
668                 parent_context.new_paragraph(os);
669         }
670
671         else if (name == "appendix") {
672                 // This is no good latex style, but it works and is used in some documents...
673                 parent_context.check_end_layout(os);
674                 Context context(true, parent_context.textclass, parent_context.layout,
675                                 parent_context.layout, parent_context.font);
676                 context.check_layout(os);
677                 os << "\\start_of_appendix\n";
678                 parse_text(p, os, FLAG_END, outer, context);
679                 context.check_end_layout(os);
680         }
681
682         else if (name == "comment") {
683                 parent_context.check_layout(os);
684                 begin_inset(os, "Note Comment\n");
685                 os << "status open\n";
686                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
687                 end_inset(os);
688         }
689
690         else if (name == "lyxgreyedout") {
691                 parent_context.check_layout(os);
692                 begin_inset(os, "Note Greyedout\n");
693                 os << "status open\n";
694                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
695                 end_inset(os);
696         }
697
698         else if (name == "tabbing") {
699                 // We need to remember that we have to handle '\=' specially
700                 handle_ert(os, "\\begin{" + name + "}", parent_context);
701                 parse_text_snippet(p, os, FLAG_END | FLAG_TABBING, outer, parent_context);
702                 handle_ert(os, "\\end{" + name + "}", parent_context);
703         }
704
705         else {
706                 handle_ert(os, "\\begin{" + name + "}", parent_context);
707                 parse_text_snippet(p, os, FLAG_END, outer, parent_context);
708                 handle_ert(os, "\\end{" + name + "}", parent_context);
709         }
710
711         active_environments.pop_back();
712         if (name != "math")
713                 p.skip_spaces();
714 }
715
716 /// parses a comment and outputs it to \p os.
717 void parse_comment(Parser & p, ostream & os, Token const & t, Context & context)
718 {
719         BOOST_ASSERT(t.cat() == catComment);
720         context.check_layout(os);
721         if (!t.cs().empty()) {
722                 handle_comment(os, '%' + t.cs(), context);
723                 if (p.next_token().cat() == catNewline) {
724                         // A newline after a comment line starts a new
725                         // paragraph
726                         if(!context.atParagraphStart()) {
727                                 // Only start a new paragraph if not already
728                                 // done (we might get called recursively)
729                                 context.new_paragraph(os);
730                         }
731                         eat_whitespace(p, os, context, true);
732                 }
733         } else {
734                 // "%\n" combination
735                 p.skip_spaces();
736         }
737 }
738
739
740 /*!
741  * Reads spaces and comments until the first non-space, non-comment token.
742  * New paragraphs (double newlines or \\par) are handled like simple spaces
743  * if \p eatParagraph is true.
744  * Spaces are skipped, but comments are written to \p os.
745  */
746 void eat_whitespace(Parser & p, ostream & os, Context & context,
747                     bool eatParagraph)
748 {
749         while (p.good()) {
750                 Token const & t = p.get_token();
751                 if (t.cat() == catComment)
752                         parse_comment(p, os, t, context);
753                 else if ((! eatParagraph && p.isParagraph()) ||
754                          (t.cat() != catSpace && t.cat() != catNewline)) {
755                         p.putback();
756                         return;
757                 }
758         }
759 }
760
761
762 /*!
763  * Set a font attribute, parse text and reset the font attribute.
764  * \param attribute Attribute name (e.g. \\family, \\shape etc.)
765  * \param currentvalue Current value of the attribute. Is set to the new
766  * value during parsing.
767  * \param newvalue New value of the attribute
768  */
769 void parse_text_attributes(Parser & p, ostream & os, unsigned flags, bool outer,
770                            Context & context, string const & attribute,
771                            string & currentvalue, string const & newvalue)
772 {
773         context.check_layout(os);
774         string oldvalue = currentvalue;
775         currentvalue = newvalue;
776         os << '\n' << attribute << ' ' << newvalue << " \n";
777         parse_text_snippet(p, os, flags, outer, context);
778         currentvalue = oldvalue;
779         os << '\n' << attribute << ' ' << oldvalue << " \n";
780 }
781
782 } // anonymous namespace
783
784
785 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
786                 Context & context)
787 {
788         LyXLayout_ptr newlayout;
789         // Store the latest bibliographystyle (needed for bibtex inset)
790         string bibliographystyle;
791         while (p.good()) {
792                 Token const & t = p.get_token();
793
794 #ifdef FILEDEBUG
795                 cerr << "t: " << t << " flags: " << flags << "\n";
796 #endif
797
798                 if (flags & FLAG_ITEM) {
799                         if (t.cat() == catSpace)
800                                 continue;
801
802                         flags &= ~FLAG_ITEM;
803                         if (t.cat() == catBegin) {
804                                 // skip the brace and collect everything to the next matching
805                                 // closing brace
806                                 flags |= FLAG_BRACE_LAST;
807                                 continue;
808                         }
809
810                         // handle only this single token, leave the loop if done
811                         flags |= FLAG_LEAVE;
812                 }
813
814                 if (t.character() == ']' && (flags & FLAG_BRACK_LAST))
815                         return;
816
817                 //
818                 // cat codes
819                 //
820                 if (t.cat() == catMath) {
821                         // we are inside some text mode thingy, so opening new math is allowed
822                         context.check_layout(os);
823                         begin_inset(os, "Formula ");
824                         Token const & n = p.get_token();
825                         if (n.cat() == catMath && outer) {
826                                 // TeX's $$...$$ syntax for displayed math
827                                 os << "\\[";
828                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
829                                 os << "\\]";
830                                 p.get_token(); // skip the second '$' token
831                         } else {
832                                 // simple $...$  stuff
833                                 p.putback();
834                                 os << '$';
835                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
836                                 os << '$';
837                         }
838                         end_inset(os);
839                 }
840
841                 else if (t.cat() == catSuper || t.cat() == catSub)
842                         cerr << "catcode " << t << " illegal in text mode\n";
843
844                 // Basic support for english quotes. This should be
845                 // extended to other quotes, but is not so easy (a
846                 // left english quote is the same as a right german
847                 // quote...)
848                 else if (t.asInput() == "`"
849                          && p.next_token().asInput() == "`") {
850                         context.check_layout(os);
851                         begin_inset(os, "Quotes ");
852                         os << "eld";
853                         end_inset(os);
854                         p.get_token();
855                         skip_braces(p);
856                 }
857                 else if (t.asInput() == "'"
858                          && p.next_token().asInput() == "'") {
859                         context.check_layout(os);
860                         begin_inset(os, "Quotes ");
861                         os << "erd";
862                         end_inset(os);
863                         p.get_token();
864                         skip_braces(p);
865                 }
866
867                 else if (t.cat() == catSpace || (t.cat() == catNewline && ! p.isParagraph()))
868                         check_space(p, os, context);
869
870                 else if (t.cat() == catLetter ||
871                                t.cat() == catOther ||
872                                t.cat() == catAlign ||
873                                t.cat() == catParameter) {
874                         context.check_layout(os);
875                         os << t.character();
876                 }
877
878                 else if (p.isParagraph()) {
879                         context.new_paragraph(os);
880                         eat_whitespace(p, os, context, true);
881                 }
882
883                 else if (t.cat() == catActive) {
884                         context.check_layout(os);
885                         if (t.character() == '~') {
886                                 if (context.layout->free_spacing)
887                                         os << ' ';
888                                 else
889                                         os << "\\InsetSpace ~\n";
890                         } else
891                                 os << t.character();
892                 }
893
894                 else if (t.cat() == catBegin) {
895                         context.check_layout(os);
896                         // special handling of font attribute changes
897                         Token const prev = p.prev_token();
898                         Token const next = p.next_token();
899                         Font const oldFont = context.font;
900                         string const s = parse_text(p, FLAG_BRACE_LAST, outer,
901                                                     context);
902                         context.font = oldFont;
903                         if (s.empty() && (p.next_token().character() == '`' ||
904                                           (prev.character() == '-' &&
905                                            p.next_token().character() == '-')))
906                                 ; // ignore it in {}`` or -{}-
907                         else if (s == "[" || s == "]" || s == "*")
908                                 os << s;
909                         else if (is_known(next.cs(), known_sizes))
910                                 // s will change the size, so we must reset
911                                 // it here
912                                 os << s << "\n\\size " << context.font.size
913                                    << " \n";
914                         else if (is_known(next.cs(), known_font_families))
915                                 // s will change the font family, so we must
916                                 // reset it here
917                                 os << s << "\n\\family "
918                                    << context.font.family << " \n";
919                         else if (is_known(next.cs(), known_font_series))
920                                 // s will change the font series, so we must
921                                 // reset it here
922                                 os << s << "\n\\series "
923                                    << context.font.series << " \n";
924                         else if (is_known(next.cs(), known_font_shapes))
925                                 // s will change the font shape, so we must
926                                 // reset it here
927                                 os << s << "\n\\shape "
928                                    << context.font.shape << " \n";
929                         else if (is_known(next.cs(), known_old_font_families) ||
930                                  is_known(next.cs(), known_old_font_series) ||
931                                  is_known(next.cs(), known_old_font_shapes))
932                                 // s will change the font family, series
933                                 // and shape, so we must reset it here
934                                 os << s
935                                    <<  "\n\\family " << context.font.family
936                                    << " \n\\series " << context.font.series
937                                    << " \n\\shape "  << context.font.shape
938                                    << " \n";
939                         else {
940                                 handle_ert(os, "{", context, false);
941                                 // s will end the current layout and begin a
942                                 // new one if necessary
943                                 os << s;
944                                 handle_ert(os, "}", context);
945                         }
946                 }
947
948                 else if (t.cat() == catEnd) {
949                         if (flags & FLAG_BRACE_LAST) {
950                                 return;
951                         }
952                         cerr << "stray '}' in text\n";
953                         handle_ert(os, "}", context);
954                 }
955
956                 else if (t.cat() == catComment)
957                         parse_comment(p, os, t, context);
958
959                 //
960                 // control sequences
961                 //
962
963                 else if (t.cs() == "(") {
964                         context.check_layout(os);
965                         begin_inset(os, "Formula");
966                         os << " \\(";
967                         parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
968                         os << "\\)";
969                         end_inset(os);
970                 }
971
972                 else if (t.cs() == "[") {
973                         context.check_layout(os);
974                         begin_inset(os, "Formula");
975                         os << " \\[";
976                         parse_math(p, os, FLAG_EQUATION, MATH_MODE);
977                         os << "\\]";
978                         end_inset(os);
979                 }
980
981                 else if (t.cs() == "begin")
982                         parse_environment(p, os, outer, context);
983
984                 else if (t.cs() == "end") {
985                         if (flags & FLAG_END) {
986                                 // eat environment name
987                                 string const name = p.getArg('{', '}');
988                                 if (name != active_environment())
989                                         cerr << "\\end{" + name + "} does not match \\begin{"
990                                                 + active_environment() + "}\n";
991                                 return;
992                         }
993                         p.error("found 'end' unexpectedly");
994                 }
995
996                 else if (t.cs() == "item") {
997                         p.skip_spaces();
998                         string s;
999                         bool optarg = false;
1000                         if (p.next_token().character() == '[') {
1001                                 p.get_token(); // eat '['
1002                                 Context newcontext(false, context.textclass);
1003                                 newcontext.font = context.font;
1004                                 s = parse_text(p, FLAG_BRACK_LAST, outer, newcontext);
1005                                 optarg = true;
1006                         }
1007                         context.set_item();
1008                         context.check_layout(os);
1009                         if (optarg) {
1010                                 if (context.layout->labeltype != LABEL_MANUAL) {
1011                                         // lyx does not support \item[\mybullet]
1012                                         // in itemize environments
1013                                         handle_ert(os, "[", context);
1014                                         os << s;
1015                                         handle_ert(os, "]", context);
1016                                 } else if (!s.empty()) {
1017                                         // The space is needed to separate the
1018                                         // item from the rest of the sentence.
1019                                         os << s << ' ';
1020                                         eat_whitespace(p, os, context, false);
1021                                 }
1022                         }
1023                 }
1024
1025                 else if (t.cs() == "bibitem") {
1026                         context.set_item();
1027                         context.check_layout(os);
1028                         os << "\\bibitem ";
1029                         os << p.getOpt();
1030                         os << '{' << p.verbatim_item() << '}' << "\n";
1031                 }
1032
1033                 else if (t.cs() == "def") {
1034                         context.check_layout(os);
1035                         eat_whitespace(p, os, context, false);
1036                         string name = p.get_token().cs();
1037                         while (p.next_token().cat() != catBegin)
1038                                 name += p.get_token().asString();
1039                         handle_ert(os, "\\def\\" + name + '{' + p.verbatim_item() + '}', context);
1040                 }
1041
1042                 else if (t.cs() == "noindent") {
1043                         p.skip_spaces();
1044                         context.add_extra_stuff("\\noindent ");
1045                 }
1046
1047                 else if (t.cs() == "appendix") {
1048                         p.skip_spaces();
1049                         context.add_extra_stuff("\\start_of_appendix ");
1050                 }
1051
1052                 // Must attempt to parse "Section*" before "Section".
1053                 else if ((p.next_token().asInput() == "*") &&
1054                          // The single '=' is meant here.
1055                          (newlayout = findLayout(context.textclass,
1056                                                  t.cs() + '*')).get() &&
1057                          newlayout->isCommand()) {
1058                         p.get_token();
1059                         output_command_layout(os, p, outer, context, newlayout);
1060                         p.skip_spaces();
1061                 }
1062
1063                 // The single '=' is meant here.
1064                 else if ((newlayout = findLayout(context.textclass, t.cs())).get() &&
1065                          newlayout->isCommand()) {
1066                         output_command_layout(os, p, outer, context, newlayout);
1067                         p.skip_spaces();
1068                 }
1069
1070                 else if (t.cs() == "includegraphics") {
1071                         map<string, string> opts = split_map(p.getArg('[', ']'));
1072                         string name = subst(p.verbatim_item(), "\\lyxdot ", ".");
1073
1074                         context.check_layout(os);
1075                         begin_inset(os, "Graphics ");
1076                         os << "\n\tfilename " << name << '\n';
1077                         if (opts.find("width") != opts.end())
1078                                 os << "\twidth "
1079                                    << translate_len(opts["width"]) << '\n';
1080                         if (opts.find("height") != opts.end())
1081                                 os << "\theight "
1082                                    << translate_len(opts["height"]) << '\n';
1083                         if (opts.find("scale") != opts.end()) {
1084                                 istringstream iss(opts["scale"]);
1085                                 double val;
1086                                 iss >> val;
1087                                 val = val*100;
1088                                 os << "\tscale " << val << '\n';
1089                         }
1090                         if (opts.find("angle") != opts.end())
1091                                 os << "\trotateAngle "
1092                                    << opts["angle"] << '\n';
1093                         if (opts.find("origin") != opts.end()) {
1094                                 ostringstream ss;
1095                                 string const opt = opts["origin"];
1096                                 if (opt.find('l') != string::npos) ss << "left";
1097                                 if (opt.find('r') != string::npos) ss << "right";
1098                                 if (opt.find('c') != string::npos) ss << "center";
1099                                 if (opt.find('t') != string::npos) ss << "Top";
1100                                 if (opt.find('b') != string::npos) ss << "Bottom";
1101                                 if (opt.find('B') != string::npos) ss << "Baseline";
1102                                 if (!ss.str().empty())
1103                                         os << "\trotateOrigin " << ss.str() << '\n';
1104                                 else
1105                                         cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
1106                         }
1107                         if (opts.find("keepaspectratio") != opts.end())
1108                                 os << "\tkeepAspectRatio\n";
1109                         if (opts.find("clip") != opts.end())
1110                                 os << "\tclip\n";
1111                         if (opts.find("draft") != opts.end())
1112                                 os << "\tdraft\n";
1113                         if (opts.find("bb") != opts.end())
1114                                 os << "\tBoundingBox "
1115                                    << opts["bb"] << '\n';
1116                         int numberOfbbOptions = 0;
1117                         if (opts.find("bbllx") != opts.end())
1118                                 numberOfbbOptions++;
1119                         if (opts.find("bblly") != opts.end())
1120                                 numberOfbbOptions++;
1121                         if (opts.find("bburx") != opts.end())
1122                                 numberOfbbOptions++;
1123                         if (opts.find("bbury") != opts.end())
1124                                 numberOfbbOptions++;
1125                         if (numberOfbbOptions == 4)
1126                                 os << "\tBoundingBox "
1127                                    << opts["bbllx"] << opts["bblly"]
1128                                    << opts["bburx"] << opts["bbury"] << '\n';
1129                         else if (numberOfbbOptions > 0)
1130                                 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
1131                         numberOfbbOptions = 0;
1132                         if (opts.find("natwidth") != opts.end())
1133                                 numberOfbbOptions++;
1134                         if (opts.find("natheight") != opts.end())
1135                                 numberOfbbOptions++;
1136                         if (numberOfbbOptions == 2)
1137                                 os << "\tBoundingBox 0bp 0bp "
1138                                    << opts["natwidth"] << opts["natheight"] << '\n';
1139                         else if (numberOfbbOptions > 0)
1140                                 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
1141                         ostringstream special;
1142                         if (opts.find("hiresbb") != opts.end())
1143                                 special << "hiresbb,";
1144                         if (opts.find("trim") != opts.end())
1145                                 special << "trim,";
1146                         if (opts.find("viewport") != opts.end())
1147                                 special << "viewport=" << opts["viewport"] << ',';
1148                         if (opts.find("totalheight") != opts.end())
1149                                 special << "totalheight=" << opts["totalheight"] << ',';
1150                         if (opts.find("type") != opts.end())
1151                                 special << "type=" << opts["type"] << ',';
1152                         if (opts.find("ext") != opts.end())
1153                                 special << "ext=" << opts["ext"] << ',';
1154                         if (opts.find("read") != opts.end())
1155                                 special << "read=" << opts["read"] << ',';
1156                         if (opts.find("command") != opts.end())
1157                                 special << "command=" << opts["command"] << ',';
1158                         string s_special = special.str();
1159                         if (!s_special.empty()) {
1160                                 // We had special arguments. Remove the trailing ','.
1161                                 os << "\tspecial " << s_special.substr(0, s_special.size() - 1) << '\n';
1162                         }
1163                         // TODO: Handle the unknown settings better.
1164                         // Warn about invalid options.
1165                         // Check whether some option was given twice.
1166                         end_inset(os);
1167                 }
1168
1169                 else if (t.cs() == "footnote") {
1170                         p.skip_spaces();
1171                         context.check_layout(os);
1172                         begin_inset(os, "Foot\n");
1173                         os << "status collapsed\n\n";
1174                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
1175                         end_inset(os);
1176                 }
1177
1178                 else if (t.cs() == "marginpar") {
1179                         p.skip_spaces();
1180                         context.check_layout(os);
1181                         begin_inset(os, "Marginal\n");
1182                         os << "status collapsed\n\n";
1183                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
1184                         end_inset(os);
1185                 }
1186
1187                 else if (t.cs() == "ensuremath") {
1188                         p.skip_spaces();
1189                         context.check_layout(os);
1190                         Context newcontext(false, context.textclass);
1191                         newcontext.font = context.font;
1192                         string s = parse_text(p, FLAG_ITEM, false, newcontext);
1193                         if (s == "±" || s == "³" || s == "²" || s == "µ")
1194                                 os << s;
1195                         else
1196                                 handle_ert(os, "\\ensuremath{" + s + "}",
1197                                            context);
1198                 }
1199
1200                 else if (t.cs() == "hfill") {
1201                         context.check_layout(os);
1202                         os << "\n\\hfill \n";
1203                         skip_braces(p);
1204                         p.skip_spaces();
1205                 }
1206
1207                 else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
1208                         p.skip_spaces();
1209                         skip_braces(p); // swallow this
1210                 }
1211
1212                 else if (t.cs() == "tableofcontents") {
1213                         p.skip_spaces();
1214                         context.check_layout(os);
1215                         begin_inset(os, "LatexCommand \\tableofcontents\n");
1216                         end_inset(os);
1217                         skip_braces(p); // swallow this
1218                 }
1219
1220                 else if (t.cs() == "listoffigures") {
1221                         p.skip_spaces();
1222                         context.check_layout(os);
1223                         begin_inset(os, "FloatList figure\n");
1224                         end_inset(os);
1225                         skip_braces(p); // swallow this
1226                 }
1227
1228                 else if (t.cs() == "listoftables") {
1229                         p.skip_spaces();
1230                         context.check_layout(os);
1231                         begin_inset(os, "FloatList table\n");
1232                         end_inset(os);
1233                         skip_braces(p); // swallow this
1234                 }
1235
1236                 else if (t.cs() == "listof") {
1237                         p.skip_spaces(true);
1238                         string const name = p.get_token().asString();
1239                         if (context.textclass.floats().typeExist(name)) {
1240                                 context.check_layout(os);
1241                                 begin_inset(os, "FloatList ");
1242                                 os << name << "\n";
1243                                 end_inset(os);
1244                                 p.get_token(); // swallow second arg
1245                         } else
1246                                 handle_ert(os, "\\listof{" + name + "}", context);
1247                 }
1248
1249                 else if (t.cs() == "textrm")
1250                         parse_text_attributes(p, os, FLAG_ITEM, outer,
1251                                               context, "\\family",
1252                                               context.font.family, "roman");
1253
1254                 else if (t.cs() == "textsf")
1255                         parse_text_attributes(p, os, FLAG_ITEM, outer,
1256                                               context, "\\family",
1257                                               context.font.family, "sans");
1258
1259                 else if (t.cs() == "texttt")
1260                         parse_text_attributes(p, os, FLAG_ITEM, outer,
1261                                               context, "\\family",
1262                                               context.font.family, "typewriter");
1263
1264                 else if (t.cs() == "textmd")
1265                         parse_text_attributes(p, os, FLAG_ITEM, outer,
1266                                               context, "\\series",
1267                                               context.font.series, "medium");
1268
1269                 else if (t.cs() == "textbf")
1270                         parse_text_attributes(p, os, FLAG_ITEM, outer,
1271                                               context, "\\series",
1272                                               context.font.series, "bold");
1273
1274                 else if (t.cs() == "textup")
1275                         parse_text_attributes(p, os, FLAG_ITEM, outer,
1276                                               context, "\\shape",
1277                                               context.font.shape, "up");
1278
1279                 else if (t.cs() == "textit")
1280                         parse_text_attributes(p, os, FLAG_ITEM, outer,
1281                                               context, "\\shape",
1282                                               context.font.shape, "italic");
1283
1284                 else if (t.cs() == "textsl")
1285                         parse_text_attributes(p, os, FLAG_ITEM, outer,
1286                                               context, "\\shape",
1287                                               context.font.shape, "slanted");
1288
1289                 else if (t.cs() == "textsc")
1290                         parse_text_attributes(p, os, FLAG_ITEM, outer,
1291                                               context, "\\shape",
1292                                               context.font.shape, "smallcaps");
1293
1294                 else if (t.cs() == "textnormal" || t.cs() == "normalfont") {
1295                         context.check_layout(os);
1296                         Font oldFont = context.font;
1297                         context.font.init();
1298                         context.font.size = oldFont.size;
1299                         os << "\n\\family " << context.font.family << " \n";
1300                         os << "\n\\series " << context.font.series << " \n";
1301                         os << "\n\\shape " << context.font.shape << " \n";
1302                         if (t.cs() == "textnormal") {
1303                                 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1304                                 context.font = oldFont;
1305                                 os << "\n\\shape " << oldFont.shape << " \n";
1306                                 os << "\n\\series " << oldFont.series << " \n";
1307                                 os << "\n\\family " << oldFont.family << " \n";
1308                         } else
1309                                 eat_whitespace(p, os, context, false);
1310                 }
1311
1312                 else if (t.cs() == "underbar") {
1313                         context.check_layout(os);
1314                         os << "\n\\bar under \n";
1315                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1316                         os << "\n\\bar default \n";
1317                 }
1318
1319                 else if (t.cs() == "emph" || t.cs() == "noun") {
1320                         context.check_layout(os);
1321                         os << "\n\\" << t.cs() << " on \n";
1322                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1323                         os << "\n\\" << t.cs() << " default \n";
1324                 }
1325
1326                 else if (is_known(t.cs(), known_latex_commands)) {
1327                         context.check_layout(os);
1328                         begin_inset(os, "LatexCommand ");
1329                         os << '\\' << t.cs();
1330                         os << p.getOpt();
1331                         os << p.getOpt();
1332                         os << '{' << p.verbatim_item() << "}\n";
1333                         end_inset(os);
1334                 }
1335
1336                 else if (is_known(t.cs(), known_quotes)) {
1337                         char const * const * where = is_known(t.cs(), known_quotes);
1338                         context.check_layout(os);
1339                         begin_inset(os, "Quotes ");
1340                         os << known_coded_quotes[where - known_quotes];
1341                         end_inset(os);
1342                         // LyX adds {} after the quote, so we have to eat
1343                         // spaces here if there are any before a possible
1344                         // {} pair.
1345                         eat_whitespace(p, os, context, false);
1346                         skip_braces(p);
1347                 }
1348
1349                 else if (is_known(t.cs(), known_sizes)) {
1350                         char const * const * where = is_known(t.cs(), known_sizes);
1351                         context.check_layout(os);
1352                         context.font.size = known_coded_sizes[where - known_sizes];
1353                         os << "\n\\size " << context.font.size << '\n';
1354                         eat_whitespace(p, os, context, false);
1355                 }
1356
1357                 else if (is_known(t.cs(), known_font_families)) {
1358                         char const * const * where =
1359                                 is_known(t.cs(), known_font_families);
1360                         context.check_layout(os);
1361                         context.font.family =
1362                                 known_coded_font_families[where - known_font_families];
1363                         os << "\n\\family " << context.font.family << '\n';
1364                         eat_whitespace(p, os, context, false);
1365                 }
1366
1367                 else if (is_known(t.cs(), known_font_series)) {
1368                         char const * const * where =
1369                                 is_known(t.cs(), known_font_series);
1370                         context.check_layout(os);
1371                         context.font.series =
1372                                 known_coded_font_series[where - known_font_series];
1373                         os << "\n\\series " << context.font.series << '\n';
1374                         eat_whitespace(p, os, context, false);
1375                 }
1376
1377                 else if (is_known(t.cs(), known_font_shapes)) {
1378                         char const * const * where =
1379                                 is_known(t.cs(), known_font_shapes);
1380                         context.check_layout(os);
1381                         context.font.shape =
1382                                 known_coded_font_shapes[where - known_font_shapes];
1383                         os << "\n\\shape " << context.font.shape << '\n';
1384                         eat_whitespace(p, os, context, false);
1385                 }
1386                 else if (is_known(t.cs(), known_old_font_families)) {
1387                         char const * const * where =
1388                                 is_known(t.cs(), known_old_font_families);
1389                         context.check_layout(os);
1390                         string oldsize = context.font.size;
1391                         context.font.init();
1392                         context.font.size = oldsize;
1393                         context.font.family =
1394                                 known_coded_font_families[where - known_old_font_families];
1395                         os << "\n\\family " << context.font.family << " \n"
1396                            <<   "\\series " << context.font.series << " \n"
1397                            <<   "\\shape "  << context.font.shape  << " \n";
1398                         eat_whitespace(p, os, context, false);
1399                 }
1400
1401                 else if (is_known(t.cs(), known_old_font_series)) {
1402                         char const * const * where =
1403                                 is_known(t.cs(), known_old_font_series);
1404                         context.check_layout(os);
1405                         string oldsize = context.font.size;
1406                         context.font.init();
1407                         context.font.size = oldsize;
1408                         context.font.series =
1409                                 known_coded_font_series[where - known_old_font_series];
1410                         os << "\n\\family " << context.font.family << " \n"
1411                            <<   "\\series " << context.font.series << " \n"
1412                            <<   "\\shape "  << context.font.shape  << " \n";
1413                         eat_whitespace(p, os, context, false);
1414                 }
1415
1416                 else if (is_known(t.cs(), known_old_font_shapes)) {
1417                         char const * const * where =
1418                                 is_known(t.cs(), known_old_font_shapes);
1419                         context.check_layout(os);
1420                         string oldsize = context.font.size;
1421                         context.font.init();
1422                         context.font.size = oldsize;
1423                         context.font.shape =
1424                                 known_coded_font_shapes[where - known_old_font_shapes];
1425                         os << "\n\\family " << context.font.family << " \n"
1426                            <<   "\\series " << context.font.series << " \n"
1427                            <<   "\\shape "  << context.font.shape  << " \n";
1428                         eat_whitespace(p, os, context, false);
1429                 }
1430
1431                 else if (t.cs() == "LyX" || t.cs() == "TeX"
1432                          || t.cs() == "LaTeX") {
1433                         context.check_layout(os);
1434                         os << t.cs();
1435                         skip_braces(p); // eat {}
1436                 }
1437
1438                 else if (t.cs() == "LaTeXe") {
1439                         context.check_layout(os);
1440                         os << "LaTeX2e";
1441                         skip_braces(p); // eat {}
1442                 }
1443
1444                 else if (t.cs() == "ldots") {
1445                         context.check_layout(os);
1446                         skip_braces(p);
1447                         os << "\\SpecialChar \\ldots{}\n";
1448                 }
1449
1450                 else if (t.cs() == "lyxarrow") {
1451                         context.check_layout(os);
1452                         os << "\\SpecialChar \\menuseparator\n";
1453                         skip_braces(p);
1454                 }
1455
1456                 else if (t.cs() == "textcompwordmark") {
1457                         context.check_layout(os);
1458                         os << "\\SpecialChar \\textcompwordmark{}\n";
1459                         skip_braces(p);
1460                 }
1461
1462                 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
1463                         context.check_layout(os);
1464                         os << "\\SpecialChar \\@.\n";
1465                         p.get_token();
1466                 }
1467
1468                 else if (t.cs() == "-") {
1469                         context.check_layout(os);
1470                         os << "\\SpecialChar \\-\n";
1471                 }
1472
1473                 else if (t.cs() == "textasciitilde") {
1474                         context.check_layout(os);
1475                         os << '~';
1476                         skip_braces(p);
1477                 }
1478
1479                 else if (t.cs() == "textasciicircum") {
1480                         context.check_layout(os);
1481                         os << '^';
1482                         skip_braces(p);
1483                 }
1484
1485                 else if (t.cs() == "textbackslash") {
1486                         context.check_layout(os);
1487                         os << "\n\\backslash \n";
1488                         skip_braces(p);
1489                 }
1490
1491                 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
1492                             || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
1493                             || t.cs() == "%") {
1494                         context.check_layout(os);
1495                         os << t.cs();
1496                 }
1497
1498                 else if (t.cs() == "char") {
1499                         context.check_layout(os);
1500                         if (p.next_token().character() == '`') {
1501                                 p.get_token();
1502                                 if (p.next_token().cs() == "\"") {
1503                                         p.get_token();
1504                                         os << '"';
1505                                         skip_braces(p);
1506                                 } else {
1507                                         handle_ert(os, "\\char`", context);
1508                                 }
1509                         } else {
1510                                 handle_ert(os, "\\char", context);
1511                         }
1512                 }
1513
1514                 else if (t.cs() == "\"") {
1515                         context.check_layout(os);
1516                         string const name = p.verbatim_item();
1517                              if (name == "a") os << 'ä';
1518                         else if (name == "o") os << 'ö';
1519                         else if (name == "u") os << 'ü';
1520                         else if (name == "A") os << 'Ä';
1521                         else if (name == "O") os << 'Ö';
1522                         else if (name == "U") os << 'Ü';
1523                         else handle_ert(os, "\"{" + name + "}", context);
1524                 }
1525
1526                 // Problem: \= creates a tabstop inside the tabbing environment
1527                 // and else an accent. In the latter case we really would want
1528                 // \={o} instead of \= o.
1529                 else if (t.cs() == "=" && (flags & FLAG_TABBING))
1530                         handle_ert(os, t.asInput(), context);
1531
1532                 else if (t.cs() == "H" || t.cs() == "c" || t.cs() == "^" || t.cs() == "'"
1533                       || t.cs() == "~" || t.cs() == "." || t.cs() == "=") {
1534                         // we need the trim as the LyX parser chokes on such spaces
1535                         context.check_layout(os);
1536                         os << "\n\\i \\" << t.cs() << "{"
1537                            << trim(parse_text(p, FLAG_ITEM, outer, context), " ") << "}\n";
1538                 }
1539
1540                 else if (t.cs() == "ss") {
1541                         context.check_layout(os);
1542                         os << "ß";
1543                 }
1544
1545                 else if (t.cs() == "i" || t.cs() == "j") {
1546                         context.check_layout(os);
1547                         os << "\\" << t.cs() << ' ';
1548                 }
1549
1550                 else if (t.cs() == "\\") {
1551                         context.check_layout(os);
1552                         string const next = p.next_token().asInput();
1553                         if (next == "[")
1554                                 handle_ert(os, "\\\\" + p.getOpt(), context);
1555                         else if (next == "*") {
1556                                 p.get_token();
1557                                 handle_ert(os, "\\\\*" + p.getOpt(), context);
1558                         }
1559                         else {
1560                                 os << "\n\\newline \n";
1561                         }
1562                 }
1563
1564                 else if (t.cs() == "input" || t.cs() == "include"
1565                          || t.cs() == "verbatiminput") {
1566                         string name = '\\' + t.cs();
1567                         if (t.cs() == "verbatiminput"
1568                             && p.next_token().asInput() == "*")
1569                                 name += p.get_token().asInput();
1570                         context.check_layout(os);
1571                         begin_inset(os, "Include ");
1572                         string filename(p.getArg('{', '}'));
1573                         string lyxname(lyx::support::ChangeExtension(filename, ".lyx"));
1574                         if (tex2lyx(filename, lyxname)) {
1575                                 os << name << '{' << lyxname << "}\n";
1576                         } else {
1577                                 os << name << '{' << filename << "}\n";
1578                         }
1579                         os << "preview false\n";
1580                         end_inset(os);
1581                 }
1582
1583                 else if (t.cs() == "fancyhead") {
1584                         context.check_layout(os);
1585                         ostringstream ss;
1586                         ss << "\\fancyhead";
1587                         ss << p.getOpt();
1588                         ss << '{' << p.verbatim_item() << "}\n";
1589                         handle_ert(os, ss.str(), context);
1590                 }
1591
1592                 else if (t.cs() == "bibliographystyle") {
1593                         // store new bibliographystyle
1594                         bibliographystyle = p.verbatim_item();
1595                         // output new bibliographystyle.
1596                         // This is only necessary if used in some other macro than \bibliography.
1597                         handle_ert(os, "\\bibliographystyle{" + bibliographystyle + "}", context);
1598                 }
1599
1600                 else if (t.cs() == "bibliography") {
1601                         context.check_layout(os);
1602                         begin_inset(os, "LatexCommand ");
1603                         os << "\\bibtex";
1604                         // Do we have a bibliographystyle set?
1605                         if (!bibliographystyle.empty()) {
1606                                 os << '[' << bibliographystyle << ']';
1607                         }
1608                         os << '{' << p.verbatim_item() << "}\n";
1609                         end_inset(os);
1610                 }
1611
1612                 else if (t.cs() == "parbox")
1613                         parse_box(p, os, FLAG_ITEM, outer, context, true);
1614
1615                 else if (t.cs() == "smallskip" ||
1616                          t.cs() == "medskip" ||
1617                          t.cs() == "bigskip" ||
1618                          t.cs() == "vfill") {
1619                         context.check_layout(os);
1620                         begin_inset(os, "VSpace ");
1621                         os << t.cs();
1622                         end_inset(os);
1623                         skip_braces(p);
1624                 }
1625
1626                 else if (t.cs() == "vspace") {
1627                         bool starred = false;
1628                         if (p.next_token().asInput() == "*") {
1629                                 p.get_token();
1630                                 starred = true;
1631                         }
1632                         string const length = p.verbatim_item();
1633                         string unit;
1634                         string valstring;
1635                         bool valid = splitLatexLength(length, valstring, unit);
1636                         bool known_vspace = false;
1637                         bool known_unit = false;
1638                         double value;
1639                         if (valid) {
1640                                 istringstream iss(valstring);
1641                                 iss >> value;
1642                                 if (value == 1.0) {
1643                                         if (unit == "\\smallskipamount") {
1644                                                 unit = "smallskip";
1645                                                 known_vspace = true;
1646                                         } else if (unit == "\\medskipamount") {
1647                                                 unit = "medskip";
1648                                                 known_vspace = true;
1649                                         } else if (unit == "\\bigskipamount") {
1650                                                 unit = "bigskip";
1651                                                 known_vspace = true;
1652                                         } else if (unit == "\\fill") {
1653                                                 unit = "vfill";
1654                                                 known_vspace = true;
1655                                         }
1656                                 }
1657                                 if (!known_vspace) {
1658                                         switch (unitFromString(unit)) {
1659                                         case LyXLength::SP:
1660                                         case LyXLength::PT:
1661                                         case LyXLength::BP:
1662                                         case LyXLength::DD:
1663                                         case LyXLength::MM:
1664                                         case LyXLength::PC:
1665                                         case LyXLength::CC:
1666                                         case LyXLength::CM:
1667                                         case LyXLength::IN:
1668                                         case LyXLength::EX:
1669                                         case LyXLength::EM:
1670                                         case LyXLength::MU:
1671                                                 known_unit = true;
1672                                                 break;
1673                                         default:
1674                                                 break;
1675                                         }
1676                                 }
1677                         }
1678
1679                         if (known_unit || known_vspace) {
1680                                 // Literal length or known variable
1681                                 context.check_layout(os);
1682                                 begin_inset(os, "VSpace ");
1683                                 if (known_unit)
1684                                         os << value;
1685                                 os << unit;
1686                                 if (starred)
1687                                         os << '*';
1688                                 end_inset(os);
1689                         } else {
1690                                 // LyX can't handle other length variables in Inset VSpace
1691                                 string name = t.asInput();
1692                                 if (starred)
1693                                         name += '*';
1694                                 if (valid) {
1695                                         if (value == 1.0)
1696                                                 handle_ert(os, name + '{' + unit + '}', context);
1697                                         else if (value == -1.0)
1698                                                 handle_ert(os, name + "{-" + unit + '}', context);
1699                                         else
1700                                                 handle_ert(os, name + '{' + valstring + unit + '}', context);
1701                                 } else
1702                                         handle_ert(os, name + '{' + length + '}', context);
1703                         }
1704                 }
1705
1706                 else {
1707                         //cerr << "#: " << t << " mode: " << mode << endl;
1708                         // heuristic: read up to next non-nested space
1709                         /*
1710                         string s = t.asInput();
1711                         string z = p.verbatim_item();
1712                         while (p.good() && z != " " && z.size()) {
1713                                 //cerr << "read: " << z << endl;
1714                                 s += z;
1715                                 z = p.verbatim_item();
1716                         }
1717                         cerr << "found ERT: " << s << endl;
1718                         handle_ert(os, s + ' ', context);
1719                         */
1720                         string name = t.asInput();
1721                         if (p.next_token().asInput() == "*") {
1722                                 // Starred commands like \vspace*{}
1723                                 p.get_token();                          // Eat '*'
1724                                 name += '*';
1725                         }
1726                         if (! parse_command(name, p, os, outer, context))
1727                                 handle_ert(os, name, context);
1728                 }
1729
1730                 if (flags & FLAG_LEAVE) {
1731                         flags &= ~FLAG_LEAVE;
1732                         break;
1733                 }
1734         }
1735 }
1736
1737
1738 // }])