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