]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/text.cpp
92934cce94bdc3cdc879633697de16fda0885713
[lyx.git] / src / tex2lyx / text.cpp
1 /**
2  * \file tex2lyx/text.cpp
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  * \author Uwe Stöhr
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 // {[(
14
15 #include <config.h>
16
17 #include "tex2lyx.h"
18
19 #include "Context.h"
20 #include "Encoding.h"
21 #include "FloatList.h"
22 #include "LaTeXPackages.h"
23 #include "Layout.h"
24 #include "Length.h"
25 #include "Preamble.h"
26
27 #include "insets/ExternalTemplate.h"
28
29 #include "support/lassert.h"
30 #include "support/convert.h"
31 #include "support/FileName.h"
32 #include "support/filetools.h"
33 #include "support/lstrings.h"
34 #include "support/lyxtime.h"
35
36 #include <algorithm>
37 #include <iostream>
38 #include <map>
39 #include <sstream>
40 #include <vector>
41
42 using namespace std;
43 using namespace lyx::support;
44
45 namespace lyx {
46
47
48 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
49                 Context const & context, InsetLayout const * layout)
50 {
51         bool const forcePlainLayout =
52                 layout ? layout->forcePlainLayout() : false;
53         Context newcontext(true, context.textclass);
54         if (forcePlainLayout)
55                 newcontext.layout = &context.textclass.plainLayout();
56         else
57                 newcontext.font = context.font;
58         parse_text(p, os, flags, outer, newcontext);
59         newcontext.check_end_layout(os);
60 }
61
62
63 namespace {
64
65 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
66                 Context const & context, string const & name)
67 {
68         InsetLayout const * layout = 0;
69         DocumentClass::InsetLayouts::const_iterator it =
70                 context.textclass.insetLayouts().find(from_ascii(name));
71         if (it != context.textclass.insetLayouts().end())
72                 layout = &(it->second);
73         parse_text_in_inset(p, os, flags, outer, context, layout);
74 }
75
76 /// parses a paragraph snippet, useful for example for \\emph{...}
77 void parse_text_snippet(Parser & p, ostream & os, unsigned flags, bool outer,
78                 Context & context)
79 {
80         Context newcontext(context);
81         // Don't inherit the paragraph-level extra stuff
82         newcontext.par_extra_stuff.clear();
83         parse_text(p, os, flags, outer, newcontext);
84         // Make sure that we don't create invalid .lyx files
85         context.need_layout = newcontext.need_layout;
86         context.need_end_layout = newcontext.need_end_layout;
87 }
88
89
90 /*!
91  * Thin wrapper around parse_text_snippet() using a string.
92  *
93  * We completely ignore \c context.need_layout and \c context.need_end_layout,
94  * because our return value is not used directly (otherwise the stream version
95  * of parse_text_snippet() could be used). That means that the caller needs
96  * to do layout management manually.
97  * This is intended to parse text that does not create any layout changes.
98  */
99 string parse_text_snippet(Parser & p, unsigned flags, const bool outer,
100                   Context & context)
101 {
102         Context newcontext(context);
103         newcontext.need_layout = false;
104         newcontext.need_end_layout = false;
105         newcontext.new_layout_allowed = false;
106         // Avoid warning by Context::~Context()
107         newcontext.par_extra_stuff.clear();
108         ostringstream os;
109         parse_text_snippet(p, os, flags, outer, newcontext);
110         return os.str();
111 }
112
113
114 char const * const known_ref_commands[] = { "ref", "pageref", "vref",
115  "vpageref", "prettyref", "eqref", 0 };
116
117 char const * const known_coded_ref_commands[] = { "ref", "pageref", "vref",
118  "vpageref", "formatted", "eqref", 0 };
119
120 /**
121  * known polyglossia language names (inluding synomyms)
122  */
123 const char * const polyglossia_languages[] = {
124 "albanian", "croatian", "hebrew", "norsk", "swedish", "amharic", "czech", "hindi",
125 "nynorsk", "syriac", "arabic", "danish", "icelandic", "occitan", "tamil",
126 "armenian", "divehi", "interlingua", "polish", "telugu", "asturian", "dutch",
127 "irish", "portuges", "thai", "bahasai", "english", "italian", "romanian", "turkish",
128 "bahasam", "esperanto", "lao", "russian", "turkmen", "basque", "estonian", "latin",
129 "samin", "ukrainian", "bengali", "farsi", "latvian", "sanskrit", "urdu", "brazil",
130 "brazilian", "finnish", "lithuanian", "scottish", "usorbian", "breton", "french",
131 "lsorbian", "serbian", "vietnamese", "bulgarian", "galician", "magyar", "slovak",
132 "welsh", "catalan", "german", "malayalam", "slovenian", "coptic", "greek",
133 "marathi", "spanish", 0};
134
135 /**
136  * the same as polyglossia_languages with .lyx names
137  * please keep this in sync with polyglossia_languages line by line!
138  */
139 const char * const coded_polyglossia_languages[] = {
140 "albanian", "croatian", "hebrew", "norsk", "swedish", "amharic", "czech", "hindi",
141 "nynorsk", "syriac", "arabic_arabi", "danish", "icelandic", "occitan", "tamil",
142 "armenian", "divehi", "interlingua", "polish", "telugu", "asturian", "dutch",
143 "irish", "portuges", "thai", "bahasa", "english", "italian", "romanian", "turkish",
144 "bahasam", "esperanto", "lao", "russian", "turkmen", "basque", "estonian", "latin",
145 "samin", "ukrainian", "bengali", "farsi", "latvian", "sanskrit", "urdu", "brazilian",
146 "brazilian", "finnish", "lithuanian", "scottish", "uppersorbian", "breton", "french",
147 "lowersorbian", "serbian", "vietnamese", "bulgarian", "galician", "magyar", "slovak",
148 "welsh", "catalan", "ngerman", "malayalam", "slovene", "coptic", "greek",
149 "marathi", "spanish", 0};
150
151 string polyglossia2lyx(string const & language)
152 {
153         char const * const * where = is_known(language, polyglossia_languages);
154         if (where)
155                 return coded_polyglossia_languages[where - polyglossia_languages];
156         return language;
157 }
158
159 /*!
160  * natbib commands.
161  * The starred forms are also known except for "citefullauthor",
162  * "citeyear" and "citeyearpar".
163  */
164 char const * const known_natbib_commands[] = { "cite", "citet", "citep",
165 "citealt", "citealp", "citeauthor", "citeyear", "citeyearpar",
166 "citefullauthor", "Citet", "Citep", "Citealt", "Citealp", "Citeauthor", 0 };
167
168 /*!
169  * jurabib commands.
170  * No starred form other than "cite*" known.
171  */
172 char const * const known_jurabib_commands[] = { "cite", "citet", "citep",
173 "citealt", "citealp", "citeauthor", "citeyear", "citeyearpar",
174 // jurabib commands not (yet) supported by LyX:
175 // "fullcite",
176 // "footcite", "footcitet", "footcitep", "footcitealt", "footcitealp",
177 // "footciteauthor", "footciteyear", "footciteyearpar",
178 "citefield", "citetitle", 0 };
179
180 /// LaTeX names for quotes
181 char const * const known_quotes[] = { "dq", "guillemotleft", "flqq", "og",
182 "guillemotright", "frqq", "fg", "glq", "glqq", "textquoteleft", "grq", "grqq",
183 "quotedblbase", "textquotedblleft", "quotesinglbase", "textquoteright", "flq",
184 "guilsinglleft", "frq", "guilsinglright", 0};
185
186 /// the same as known_quotes with .lyx names
187 char const * const known_coded_quotes[] = { "prd", "ard", "ard", "ard",
188 "ald", "ald", "ald", "gls", "gld", "els", "els", "grd",
189 "gld", "grd", "gls", "ers", "fls",
190 "fls", "frs", "frs", 0};
191
192 /// LaTeX names for font sizes
193 char const * const known_sizes[] = { "tiny", "scriptsize", "footnotesize",
194 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
195
196 /// the same as known_sizes with .lyx names
197 char const * const known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
198 "small", "normal", "large", "larger", "largest", "huge", "giant", 0};
199
200 /// LaTeX 2.09 names for font families
201 char const * const known_old_font_families[] = { "rm", "sf", "tt", 0};
202
203 /// LaTeX names for font families
204 char const * const known_font_families[] = { "rmfamily", "sffamily",
205 "ttfamily", 0};
206
207 /// LaTeX names for font family changing commands
208 char const * const known_text_font_families[] = { "textrm", "textsf",
209 "texttt", 0};
210
211 /// The same as known_old_font_families, known_font_families and
212 /// known_text_font_families with .lyx names
213 char const * const known_coded_font_families[] = { "roman", "sans",
214 "typewriter", 0};
215
216 /// LaTeX 2.09 names for font series
217 char const * const known_old_font_series[] = { "bf", 0};
218
219 /// LaTeX names for font series
220 char const * const known_font_series[] = { "bfseries", "mdseries", 0};
221
222 /// LaTeX names for font series changing commands
223 char const * const known_text_font_series[] = { "textbf", "textmd", 0};
224
225 /// The same as known_old_font_series, known_font_series and
226 /// known_text_font_series with .lyx names
227 char const * const known_coded_font_series[] = { "bold", "medium", 0};
228
229 /// LaTeX 2.09 names for font shapes
230 char const * const known_old_font_shapes[] = { "it", "sl", "sc", 0};
231
232 /// LaTeX names for font shapes
233 char const * const known_font_shapes[] = { "itshape", "slshape", "scshape",
234 "upshape", 0};
235
236 /// LaTeX names for font shape changing commands
237 char const * const known_text_font_shapes[] = { "textit", "textsl", "textsc",
238 "textup", 0};
239
240 /// The same as known_old_font_shapes, known_font_shapes and
241 /// known_text_font_shapes with .lyx names
242 char const * const known_coded_font_shapes[] = { "italic", "slanted",
243 "smallcaps", "up", 0};
244
245 /// Known special characters which need skip_spaces_braces() afterwards
246 char const * const known_special_chars[] = {"ldots", "lyxarrow",
247 "textcompwordmark", "slash", 0};
248
249 /// the same as known_special_chars with .lyx names
250 char const * const known_coded_special_chars[] = {"ldots{}", "menuseparator",
251 "textcompwordmark{}", "slash{}", 0};
252
253 /*!
254  * Graphics file extensions known by the dvips driver of the graphics package.
255  * These extensions are used to complete the filename of an included
256  * graphics file if it does not contain an extension.
257  * The order must be the same that latex uses to find a file, because we
258  * will use the first extension that matches.
259  * This is only an approximation for the common cases. If we would want to
260  * do it right in all cases, we would need to know which graphics driver is
261  * used and know the extensions of every driver of the graphics package.
262  */
263 char const * const known_dvips_graphics_formats[] = {"eps", "ps", "eps.gz",
264 "ps.gz", "eps.Z", "ps.Z", 0};
265
266 /*!
267  * Graphics file extensions known by the pdftex driver of the graphics package.
268  * \sa known_dvips_graphics_formats
269  */
270 char const * const known_pdftex_graphics_formats[] = {"png", "pdf", "jpg",
271 "mps", "tif", 0};
272
273 /*!
274  * Known file extensions for TeX files as used by \\include.
275  */
276 char const * const known_tex_extensions[] = {"tex", 0};
277
278 /// spaces known by InsetSpace
279 char const * const known_spaces[] = { " ", "space", ",",
280 "thinspace", "quad", "qquad", "enspace", "enskip",
281 "negthinspace", "negmedspace", "negthickspace", "textvisiblespace",
282 "hfill", "dotfill", "hrulefill", "leftarrowfill", "rightarrowfill",
283 "upbracefill", "downbracefill", 0};
284
285 /// the same as known_spaces with .lyx names
286 char const * const known_coded_spaces[] = { "space{}", "space{}",
287 "thinspace{}", "thinspace{}", "quad{}", "qquad{}", "enspace{}", "enskip{}",
288 "negthinspace{}", "negmedspace{}", "negthickspace{}", "textvisiblespace{}",
289 "hfill{}", "dotfill{}", "hrulefill{}", "leftarrowfill{}", "rightarrowfill{}",
290 "upbracefill{}", "downbracefill{}", 0};
291
292 /// These are translated by LyX to commands like "\\LyX{}", so we have to put
293 /// them in ERT. "LaTeXe" must come before "LaTeX"!
294 char const * const known_phrases[] = {"LyX", "TeX", "LaTeXe", "LaTeX", 0};
295 char const * const known_coded_phrases[] = {"LyX", "TeX", "LaTeX2e", "LaTeX", 0};
296 int const known_phrase_lengths[] = {3, 5, 7, 0};
297
298 // string to store the float type to be able to determine the type of subfloats
299 string float_type = "";
300
301
302 /// splits "x=z, y=b" into a map and an ordered keyword vector
303 void split_map(string const & s, map<string, string> & res, vector<string> & keys)
304 {
305         vector<string> v;
306         split(s, v);
307         res.clear();
308         keys.resize(v.size());
309         for (size_t i = 0; i < v.size(); ++i) {
310                 size_t const pos   = v[i].find('=');
311                 string const index = trimSpaceAndEol(v[i].substr(0, pos));
312                 string const value = trimSpaceAndEol(v[i].substr(pos + 1, string::npos));
313                 res[index] = value;
314                 keys[i] = index;
315         }
316 }
317
318
319 /*!
320  * Split a LaTeX length into value and unit.
321  * The latter can be a real unit like "pt", or a latex length variable
322  * like "\textwidth". The unit may contain additional stuff like glue
323  * lengths, but we don't care, because such lengths are ERT anyway.
324  * \returns true if \p value and \p unit are valid.
325  */
326 bool splitLatexLength(string const & len, string & value, string & unit)
327 {
328         if (len.empty())
329                 return false;
330         const string::size_type i = len.find_first_not_of(" -+0123456789.,");
331         //'4,5' is a valid LaTeX length number. Change it to '4.5'
332         string const length = subst(len, ',', '.');
333         if (i == string::npos)
334                 return false;
335         if (i == 0) {
336                 if (len[0] == '\\') {
337                         // We had something like \textwidth without a factor
338                         value = "1.0";
339                 } else {
340                         return false;
341                 }
342         } else {
343                 value = trimSpaceAndEol(string(length, 0, i));
344         }
345         if (value == "-")
346                 value = "-1.0";
347         // 'cM' is a valid LaTeX length unit. Change it to 'cm'
348         if (contains(len, '\\'))
349                 unit = trimSpaceAndEol(string(len, i));
350         else
351                 unit = ascii_lowercase(trimSpaceAndEol(string(len, i)));
352         return true;
353 }
354
355
356 /// A simple function to translate a latex length to something LyX can
357 /// understand. Not perfect, but rather best-effort.
358 bool translate_len(string const & length, string & valstring, string & unit)
359 {
360         if (!splitLatexLength(length, valstring, unit))
361                 return false;
362         // LyX uses percent values
363         double value;
364         istringstream iss(valstring);
365         iss >> value;
366         value *= 100;
367         ostringstream oss;
368         oss << value;
369         string const percentval = oss.str();
370         // a normal length
371         if (unit.empty() || unit[0] != '\\')
372                 return true;
373         string::size_type const i = unit.find(' ');
374         string const endlen = (i == string::npos) ? string() : string(unit, i);
375         if (unit == "\\textwidth") {
376                 valstring = percentval;
377                 unit = "text%" + endlen;
378         } else if (unit == "\\columnwidth") {
379                 valstring = percentval;
380                 unit = "col%" + endlen;
381         } else if (unit == "\\paperwidth") {
382                 valstring = percentval;
383                 unit = "page%" + endlen;
384         } else if (unit == "\\linewidth") {
385                 valstring = percentval;
386                 unit = "line%" + endlen;
387         } else if (unit == "\\paperheight") {
388                 valstring = percentval;
389                 unit = "pheight%" + endlen;
390         } else if (unit == "\\textheight") {
391                 valstring = percentval;
392                 unit = "theight%" + endlen;
393         }
394         return true;
395 }
396
397 }
398
399
400 string translate_len(string const & length)
401 {
402         string unit;
403         string value;
404         if (translate_len(length, value, unit))
405                 return value + unit;
406         // If the input is invalid, return what we have.
407         return length;
408 }
409
410
411 namespace {
412
413 /*!
414  * Translates a LaTeX length into \p value, \p unit and
415  * \p special parts suitable for a box inset.
416  * The difference from translate_len() is that a box inset knows about
417  * some special "units" that are stored in \p special.
418  */
419 void translate_box_len(string const & length, string & value, string & unit, string & special)
420 {
421         if (translate_len(length, value, unit)) {
422                 if (unit == "\\height" || unit == "\\depth" ||
423                     unit == "\\totalheight" || unit == "\\width") {
424                         special = unit.substr(1);
425                         // The unit is not used, but LyX requires a dummy setting
426                         unit = "in";
427                 } else
428                         special = "none";
429         } else {
430                 value.clear();
431                 unit = length;
432                 special = "none";
433         }
434 }
435
436
437 /*!
438  * Find a file with basename \p name in path \p path and an extension
439  * in \p extensions.
440  */
441 string find_file(string const & name, string const & path,
442                  char const * const * extensions)
443 {
444         for (char const * const * what = extensions; *what; ++what) {
445                 string const trial = addExtension(name, *what);
446                 if (makeAbsPath(trial, path).exists())
447                         return trial;
448         }
449         return string();
450 }
451
452
453 void begin_inset(ostream & os, string const & name)
454 {
455         os << "\n\\begin_inset " << name;
456 }
457
458
459 void begin_command_inset(ostream & os, string const & name,
460                          string const & latexname)
461 {
462         begin_inset(os, "CommandInset ");
463         os << name << "\nLatexCommand " << latexname << '\n';
464 }
465
466
467 void end_inset(ostream & os)
468 {
469         os << "\n\\end_inset\n\n";
470 }
471
472
473 bool skip_braces(Parser & p)
474 {
475         if (p.next_token().cat() != catBegin)
476                 return false;
477         p.get_token();
478         if (p.next_token().cat() == catEnd) {
479                 p.get_token();
480                 return true;
481         }
482         p.putback();
483         return false;
484 }
485
486
487 /// replace LaTeX commands in \p s from the unicodesymbols file with their
488 /// unicode points
489 docstring convert_unicodesymbols(docstring s)
490 {
491         odocstringstream os;
492         for (size_t i = 0; i < s.size();) {
493                 if (s[i] != '\\') {
494                         os.put(s[i++]);
495                         continue;
496                 }
497                 s = s.substr(i);
498                 bool termination;
499                 docstring rem;
500                 set<string> req;
501                 docstring parsed = encodings.fromLaTeXCommand(s,
502                                 Encodings::TEXT_CMD, termination, rem, &req);
503                 set<string>::const_iterator it = req.begin();
504                 set<string>::const_iterator en = req.end();
505                 for (; it != en; ++it)
506                         preamble.registerAutomaticallyLoadedPackage(*it);
507                 os << parsed;
508                 s = rem;
509                 if (s.empty() || s[0] != '\\')
510                         i = 0;
511                 else
512                         i = 1;
513         }
514         return os.str();
515 }
516
517
518 /// try to convert \p s to a valid InsetCommand argument
519 string convert_command_inset_arg(string s)
520 {
521         if (isAscii(s))
522                 // since we don't know the input encoding we can't use from_utf8
523                 s = to_utf8(convert_unicodesymbols(from_ascii(s)));
524         // LyX cannot handle newlines in a latex command
525         return subst(s, "\n", " ");
526 }
527
528
529 void handle_backslash(ostream & os, string const & s)
530 {
531         for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
532                 if (*it == '\\')
533                         os << "\n\\backslash\n";
534                 else
535                         os << *it;
536         }
537 }
538
539
540 void handle_ert(ostream & os, string const & s, Context & context)
541 {
542         // We must have a valid layout before outputting the ERT inset.
543         context.check_layout(os);
544         Context newcontext(true, context.textclass);
545         begin_inset(os, "ERT");
546         os << "\nstatus collapsed\n";
547         newcontext.check_layout(os);
548         for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
549                 if (*it == '\\')
550                         os << "\n\\backslash\n";
551                 else if (*it == '\n') {
552                         newcontext.new_paragraph(os);
553                         newcontext.check_layout(os);
554                 } else
555                         os << *it;
556         }
557         newcontext.check_end_layout(os);
558         end_inset(os);
559 }
560
561
562 void handle_comment(ostream & os, string const & s, Context & context)
563 {
564         // TODO: Handle this better
565         Context newcontext(true, context.textclass);
566         begin_inset(os, "ERT");
567         os << "\nstatus collapsed\n";
568         newcontext.check_layout(os);
569         handle_backslash(os, s);
570         // make sure that our comment is the last thing on the line
571         newcontext.new_paragraph(os);
572         newcontext.check_layout(os);
573         newcontext.check_end_layout(os);
574         end_inset(os);
575 }
576
577
578 Layout const * findLayout(TextClass const & textclass, string const & name, bool command)
579 {
580         Layout const * layout = findLayoutWithoutModule(textclass, name, command);
581         if (layout)
582                 return layout;
583         if (checkModule(name, command))
584                 return findLayoutWithoutModule(textclass, name, command);
585         return layout;
586 }
587
588
589 InsetLayout const * findInsetLayout(TextClass const & textclass, string const & name, bool command)
590 {
591         InsetLayout const * insetlayout = findInsetLayoutWithoutModule(textclass, name, command);
592         if (insetlayout)
593                 return insetlayout;
594         if (checkModule(name, command))
595                 return findInsetLayoutWithoutModule(textclass, name, command);
596         return insetlayout;
597 }
598
599
600 void eat_whitespace(Parser &, ostream &, Context &, bool);
601
602
603 /*!
604  * Skips whitespace and braces.
605  * This should be called after a command has been parsed that is not put into
606  * ERT, and where LyX adds "{}" if needed.
607  */
608 void skip_spaces_braces(Parser & p, bool keepws = false)
609 {
610         /* The following four examples produce the same typeset output and
611            should be handled by this function:
612            - abc \j{} xyz
613            - abc \j {} xyz
614            - abc \j
615              {} xyz
616            - abc \j %comment
617              {} xyz
618          */
619         // Unfortunately we need to skip comments, too.
620         // We can't use eat_whitespace since writing them after the {}
621         // results in different output in some cases.
622         bool const skipped_spaces = p.skip_spaces(true);
623         bool const skipped_braces = skip_braces(p);
624         if (keepws && skipped_spaces && !skipped_braces)
625                 // put back the space (it is better handled by check_space)
626                 p.unskip_spaces(true);
627 }
628
629
630 void output_command_layout(ostream & os, Parser & p, bool outer,
631                            Context & parent_context,
632                            Layout const * newlayout)
633 {
634         TeXFont const oldFont = parent_context.font;
635         // save the current font size
636         string const size = oldFont.size;
637         // reset the font size to default, because the font size switches
638         // don't affect section headings and the like
639         parent_context.font.size = Context::normalfont.size;
640         // we only need to write the font change if we have an open layout
641         if (!parent_context.atParagraphStart())
642                 output_font_change(os, oldFont, parent_context.font);
643         parent_context.check_end_layout(os);
644         Context context(true, parent_context.textclass, newlayout,
645                         parent_context.layout, parent_context.font);
646         if (parent_context.deeper_paragraph) {
647                 // We are beginning a nested environment after a
648                 // deeper paragraph inside the outer list environment.
649                 // Therefore we don't need to output a "begin deeper".
650                 context.need_end_deeper = true;
651         }
652         context.check_deeper(os);
653         context.check_layout(os);
654         unsigned int optargs = 0;
655         while (optargs < context.layout->optargs) {
656                 eat_whitespace(p, os, context, false);
657                 if (p.next_token().cat() == catEscape ||
658                     p.next_token().character() != '[')
659                         break;
660                 p.get_token(); // eat '['
661                 begin_inset(os, "Argument\n");
662                 os << "status collapsed\n\n";
663                 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
664                 end_inset(os);
665                 eat_whitespace(p, os, context, false);
666                 ++optargs;
667         }
668         unsigned int reqargs = 0;
669         while (reqargs < context.layout->reqargs) {
670                 eat_whitespace(p, os, context, false);
671                 if (p.next_token().cat() != catBegin)
672                         break;
673                 p.get_token(); // eat '{'
674                 begin_inset(os, "Argument\n");
675                 os << "status collapsed\n\n";
676                 parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context);
677                 end_inset(os);
678                 eat_whitespace(p, os, context, false);
679                 ++reqargs;
680         }
681         parse_text(p, os, FLAG_ITEM, outer, context);
682         context.check_end_layout(os);
683         if (parent_context.deeper_paragraph) {
684                 // We must suppress the "end deeper" because we
685                 // suppressed the "begin deeper" above.
686                 context.need_end_deeper = false;
687         }
688         context.check_end_deeper(os);
689         // We don't need really a new paragraph, but
690         // we must make sure that the next item gets a \begin_layout.
691         parent_context.new_paragraph(os);
692         // Set the font size to the original value. No need to output it here
693         // (Context::begin_layout() will do that if needed)
694         parent_context.font.size = size;
695 }
696
697
698 /*!
699  * Output a space if necessary.
700  * This function gets called for every whitespace token.
701  *
702  * We have three cases here:
703  * 1. A space must be suppressed. Example: The lyxcode case below
704  * 2. A space may be suppressed. Example: Spaces before "\par"
705  * 3. A space must not be suppressed. Example: A space between two words
706  *
707  * We currently handle only 1. and 3 and from 2. only the case of
708  * spaces before newlines as a side effect.
709  *
710  * 2. could be used to suppress as many spaces as possible. This has two effects:
711  * - Reimporting LyX generated LaTeX files changes almost no whitespace
712  * - Superflous whitespace from non LyX generated LaTeX files is removed.
713  * The drawback is that the logic inside the function becomes
714  * complicated, and that is the reason why it is not implemented.
715  */
716 void check_space(Parser & p, ostream & os, Context & context)
717 {
718         Token const next = p.next_token();
719         Token const curr = p.curr_token();
720         // A space before a single newline and vice versa must be ignored
721         // LyX emits a newline before \end{lyxcode}.
722         // This newline must be ignored,
723         // otherwise LyX will add an additional protected space.
724         if (next.cat() == catSpace ||
725             next.cat() == catNewline ||
726             (next.cs() == "end" && context.layout->free_spacing && curr.cat() == catNewline)) {
727                 return;
728         }
729         context.check_layout(os);
730         os << ' ';
731 }
732
733
734 /*!
735  * Parse all arguments of \p command
736  */
737 void parse_arguments(string const & command,
738                      vector<ArgumentType> const & template_arguments,
739                      Parser & p, ostream & os, bool outer, Context & context)
740 {
741         string ert = command;
742         size_t no_arguments = template_arguments.size();
743         for (size_t i = 0; i < no_arguments; ++i) {
744                 switch (template_arguments[i]) {
745                 case required:
746                 case req_group:
747                         // This argument contains regular LaTeX
748                         handle_ert(os, ert + '{', context);
749                         eat_whitespace(p, os, context, false);
750                         if (template_arguments[i] == required)
751                                 parse_text(p, os, FLAG_ITEM, outer, context);
752                         else
753                                 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
754                         ert = "}";
755                         break;
756                 case item:
757                         // This argument consists only of a single item.
758                         // The presence of '{' or not must be preserved.
759                         p.skip_spaces();
760                         if (p.next_token().cat() == catBegin)
761                                 ert += '{' + p.verbatim_item() + '}';
762                         else
763                                 ert += p.verbatim_item();
764                         break;
765                 case displaymath:
766                 case verbatim:
767                         // This argument may contain special characters
768                         ert += '{' + p.verbatim_item() + '}';
769                         break;
770                 case optional:
771                 case opt_group:
772                         // true because we must not eat whitespace
773                         // if an optional arg follows we must not strip the
774                         // brackets from this one
775                         if (i < no_arguments - 1 &&
776                             template_arguments[i+1] == optional)
777                                 ert += p.getFullOpt(true);
778                         else
779                                 ert += p.getOpt(true);
780                         break;
781                 }
782         }
783         handle_ert(os, ert, context);
784 }
785
786
787 /*!
788  * Check whether \p command is a known command. If yes,
789  * handle the command with all arguments.
790  * \return true if the command was parsed, false otherwise.
791  */
792 bool parse_command(string const & command, Parser & p, ostream & os,
793                    bool outer, Context & context)
794 {
795         if (known_commands.find(command) != known_commands.end()) {
796                 parse_arguments(command, known_commands[command], p, os,
797                                 outer, context);
798                 return true;
799         }
800         return false;
801 }
802
803
804 /// Parses a minipage or parbox
805 void parse_box(Parser & p, ostream & os, unsigned outer_flags,
806                unsigned inner_flags, bool outer, Context & parent_context,
807                string const & outer_type, string const & special,
808                string const & inner_type)
809 {
810         string position;
811         string inner_pos;
812         string hor_pos = "c";
813         // We need to set the height to the LaTeX default of 1\\totalheight
814         // for the case when no height argument is given
815         string height_value = "1";
816         string height_unit = "in";
817         string height_special = "totalheight";
818         string latex_height;
819         string width_value;
820         string width_unit;
821         string latex_width;
822         string width_special = "none";
823         if (!inner_type.empty() && p.hasOpt()) {
824                 if (inner_type != "makebox")
825                         position = p.getArg('[', ']');
826                 else {
827                         latex_width = p.getArg('[', ']');
828                         translate_box_len(latex_width, width_value, width_unit, width_special);
829                         position = "t";
830                 }
831                 if (position != "t" && position != "c" && position != "b") {
832                         cerr << "invalid position " << position << " for "
833                              << inner_type << endl;
834                         position = "c";
835                 }
836                 if (p.hasOpt()) {
837                         if (inner_type != "makebox") {
838                                 latex_height = p.getArg('[', ']');
839                                 translate_box_len(latex_height, height_value, height_unit, height_special);
840                         } else
841                                 hor_pos = p.getArg('[', ']');
842
843                         if (p.hasOpt()) {
844                                 inner_pos = p.getArg('[', ']');
845                                 if (inner_pos != "c" && inner_pos != "t" &&
846                                     inner_pos != "b" && inner_pos != "s") {
847                                         cerr << "invalid inner_pos "
848                                              << inner_pos << " for "
849                                              << inner_type << endl;
850                                         inner_pos = position;
851                                 }
852                         }
853                 }
854         }
855         if (inner_type.empty()) {
856                 if (special.empty() && outer_type != "framebox")
857                         latex_width = "1\\columnwidth";
858                 else {
859                         Parser p2(special);
860                         latex_width = p2.getArg('[', ']');
861                         string const opt = p2.getArg('[', ']');
862                         if (!opt.empty()) {
863                                 hor_pos = opt;
864                                 if (hor_pos != "l" && hor_pos != "c" &&
865                                     hor_pos != "r") {
866                                         cerr << "invalid hor_pos " << hor_pos
867                                              << " for " << outer_type << endl;
868                                         hor_pos = "c";
869                                 }
870                         }
871                 }
872         } else if (inner_type != "makebox")
873                 latex_width = p.verbatim_item();
874         // if e.g. only \ovalbox{content} was used, set the width to 1\columnwidth
875         // as this is LyX's standard for such cases (except for makebox)
876         // \framebox is more special and handled below
877         if (latex_width.empty() && inner_type != "makebox"
878                 && outer_type != "framebox")
879                 latex_width = "1\\columnwidth";
880
881         translate_len(latex_width, width_value, width_unit);
882
883         bool shadedparbox = false;
884         if (inner_type == "shaded") {
885                 eat_whitespace(p, os, parent_context, false);
886                 if (outer_type == "parbox") {
887                         // Eat '{'
888                         if (p.next_token().cat() == catBegin)
889                                 p.get_token();
890                         eat_whitespace(p, os, parent_context, false);
891                         shadedparbox = true;
892                 }
893                 p.get_token();
894                 p.getArg('{', '}');
895         }
896         // If we already read the inner box we have to push the inner env
897         if (!outer_type.empty() && !inner_type.empty() &&
898             (inner_flags & FLAG_END))
899                 active_environments.push_back(inner_type);
900         // LyX can't handle length variables
901         bool use_ert = contains(width_unit, '\\') || contains(height_unit, '\\');
902         if (!use_ert && !outer_type.empty() && !inner_type.empty()) {
903                 // Look whether there is some content after the end of the
904                 // inner box, but before the end of the outer box.
905                 // If yes, we need to output ERT.
906                 p.pushPosition();
907                 if (inner_flags & FLAG_END)
908                         p.verbatimEnvironment(inner_type);
909                 else
910                         p.verbatim_item();
911                 p.skip_spaces(true);
912                 bool const outer_env(outer_type == "framed" || outer_type == "minipage");
913                 if ((outer_env && p.next_token().asInput() != "\\end") ||
914                     (!outer_env && p.next_token().cat() != catEnd)) {
915                         // something is between the end of the inner box and
916                         // the end of the outer box, so we need to use ERT.
917                         use_ert = true;
918                 }
919                 p.popPosition();
920         }
921         // if only \makebox{content} was used we can set its width to 1\width
922         // because this identic and also identic to \mbox
923         // this doesn't work for \framebox{content}, thus we have to use ERT for this
924         if (latex_width.empty() && inner_type == "makebox") {
925                 width_value = "1";
926                 width_unit = "in";
927                 width_special = "width";
928         } else if (latex_width.empty() && outer_type == "framebox") {
929                 use_ert = true;
930         }
931         if (use_ert) {
932                 ostringstream ss;
933                 if (!outer_type.empty()) {
934                         if (outer_flags & FLAG_END)
935                                 ss << "\\begin{" << outer_type << '}';
936                         else {
937                                 ss << '\\' << outer_type << '{';
938                                 if (!special.empty())
939                                         ss << special;
940                         }
941                 }
942                 if (!inner_type.empty()) {
943                         if (inner_type != "shaded") {
944                                 if (inner_flags & FLAG_END)
945                                         ss << "\\begin{" << inner_type << '}';
946                                 else
947                                         ss << '\\' << inner_type;
948                         }
949                         if (!position.empty())
950                                 ss << '[' << position << ']';
951                         if (!latex_height.empty())
952                                 ss << '[' << latex_height << ']';
953                         if (!inner_pos.empty())
954                                 ss << '[' << inner_pos << ']';
955                         ss << '{' << latex_width << '}';
956                         if (!(inner_flags & FLAG_END))
957                                 ss << '{';
958                 }
959                 if (inner_type == "shaded")
960                         ss << "\\begin{shaded}";
961                 handle_ert(os, ss.str(), parent_context);
962                 if (!inner_type.empty()) {
963                         parse_text(p, os, inner_flags, outer, parent_context);
964                         if (inner_flags & FLAG_END)
965                                 handle_ert(os, "\\end{" + inner_type + '}',
966                                            parent_context);
967                         else
968                                 handle_ert(os, "}", parent_context);
969                 }
970                 if (!outer_type.empty()) {
971                         // If we already read the inner box we have to pop
972                         // the inner env
973                         if (!inner_type.empty() && (inner_flags & FLAG_END))
974                                 active_environments.pop_back();
975
976                         // Ensure that the end of the outer box is parsed correctly:
977                         // The opening brace has been eaten by parse_outer_box()
978                         if (!outer_type.empty() && (outer_flags & FLAG_ITEM)) {
979                                 outer_flags &= ~FLAG_ITEM;
980                                 outer_flags |= FLAG_BRACE_LAST;
981                         }
982                         parse_text(p, os, outer_flags, outer, parent_context);
983                         if (outer_flags & FLAG_END)
984                                 handle_ert(os, "\\end{" + outer_type + '}',
985                                            parent_context);
986                         else if (inner_type.empty() && outer_type == "framebox")
987                                 // in this case it is already closed later
988                                 ;
989                         else
990                                 handle_ert(os, "}", parent_context);
991                 }
992         } else {
993                 // LyX does not like empty positions, so we have
994                 // to set them to the LaTeX default values here.
995                 if (position.empty())
996                         position = "c";
997                 if (inner_pos.empty())
998                         inner_pos = position;
999                 parent_context.check_layout(os);
1000                 begin_inset(os, "Box ");
1001                 if (outer_type == "framed")
1002                         os << "Framed\n";
1003                 else if (outer_type == "framebox")
1004                         os << "Boxed\n";
1005                 else if (outer_type == "shadowbox")
1006                         os << "Shadowbox\n";
1007                 else if ((outer_type == "shaded" && inner_type.empty()) ||
1008                              (outer_type == "minipage" && inner_type == "shaded") ||
1009                              (outer_type == "parbox" && inner_type == "shaded")) {
1010                         os << "Shaded\n";
1011                         preamble.registerAutomaticallyLoadedPackage("color");
1012                 } else if (outer_type == "doublebox")
1013                         os << "Doublebox\n";
1014                 else if (outer_type.empty())
1015                         os << "Frameless\n";
1016                 else
1017                         os << outer_type << '\n';
1018                 os << "position \"" << position << "\"\n";
1019                 os << "hor_pos \"" << hor_pos << "\"\n";
1020                 os << "has_inner_box " << !inner_type.empty() << "\n";
1021                 os << "inner_pos \"" << inner_pos << "\"\n";
1022                 os << "use_parbox " << (inner_type == "parbox" || shadedparbox)
1023                    << '\n';
1024                 os << "use_makebox " << (inner_type == "makebox") << '\n';
1025                 os << "width \"" << width_value << width_unit << "\"\n";
1026                 os << "special \"" << width_special << "\"\n";
1027                 os << "height \"" << height_value << height_unit << "\"\n";
1028                 os << "height_special \"" << height_special << "\"\n";
1029                 os << "status open\n\n";
1030
1031                 // Unfortunately we can't use parse_text_in_inset:
1032                 // InsetBox::forcePlainLayout() is hard coded and does not
1033                 // use the inset layout. Apart from that do we call parse_text
1034                 // up to two times, but need only one check_end_layout.
1035                 bool const forcePlainLayout =
1036                         (!inner_type.empty() || inner_type == "makebox") &&
1037                         outer_type != "shaded" && outer_type != "framed";
1038                 Context context(true, parent_context.textclass);
1039                 if (forcePlainLayout)
1040                         context.layout = &context.textclass.plainLayout();
1041                 else
1042                         context.font = parent_context.font;
1043
1044                 // If we have no inner box the contents will be read with the outer box
1045                 if (!inner_type.empty())
1046                         parse_text(p, os, inner_flags, outer, context);
1047
1048                 // Ensure that the end of the outer box is parsed correctly:
1049                 // The opening brace has been eaten by parse_outer_box()
1050                 if (!outer_type.empty() && (outer_flags & FLAG_ITEM)) {
1051                         outer_flags &= ~FLAG_ITEM;
1052                         outer_flags |= FLAG_BRACE_LAST;
1053                 }
1054
1055                 // Find end of outer box, output contents if inner_type is
1056                 // empty and output possible comments
1057                 if (!outer_type.empty()) {
1058                         // If we already read the inner box we have to pop
1059                         // the inner env
1060                         if (!inner_type.empty() && (inner_flags & FLAG_END))
1061                                 active_environments.pop_back();
1062                         // This does not output anything but comments if
1063                         // inner_type is not empty (see use_ert)
1064                         parse_text(p, os, outer_flags, outer, context);
1065                 }
1066
1067                 context.check_end_layout(os);
1068                 end_inset(os);
1069 #ifdef PRESERVE_LAYOUT
1070                 // LyX puts a % after the end of the minipage
1071                 if (p.next_token().cat() == catNewline && p.next_token().cs().size() > 1) {
1072                         // new paragraph
1073                         //handle_comment(os, "%dummy", parent_context);
1074                         p.get_token();
1075                         p.skip_spaces();
1076                         parent_context.new_paragraph(os);
1077                 }
1078                 else if (p.next_token().cat() == catSpace || p.next_token().cat() == catNewline) {
1079                         //handle_comment(os, "%dummy", parent_context);
1080                         p.get_token();
1081                         p.skip_spaces();
1082                         // We add a protected space if something real follows
1083                         if (p.good() && p.next_token().cat() != catComment) {
1084                                 begin_inset(os, "space ~\n");
1085                                 end_inset(os);
1086                         }
1087                 }
1088 #endif
1089         }
1090 }
1091
1092
1093 void parse_outer_box(Parser & p, ostream & os, unsigned flags, bool outer,
1094                      Context & parent_context, string const & outer_type,
1095                      string const & special)
1096 {
1097         eat_whitespace(p, os, parent_context, false);
1098         if (flags & FLAG_ITEM) {
1099                 // Eat '{'
1100                 if (p.next_token().cat() == catBegin)
1101                         p.get_token();
1102                 else
1103                         cerr << "Warning: Ignoring missing '{' after \\"
1104                              << outer_type << '.' << endl;
1105                 eat_whitespace(p, os, parent_context, false);
1106         }
1107         string inner;
1108         unsigned int inner_flags = 0;
1109         p.pushPosition();
1110         if (outer_type == "minipage" || outer_type == "parbox") {
1111                 p.skip_spaces(true);
1112                 while (p.hasOpt()) {
1113                         p.getArg('[', ']');
1114                         p.skip_spaces(true);
1115                 }
1116                 p.getArg('{', '}');
1117                 p.skip_spaces(true);
1118                 if (outer_type == "parbox") {
1119                         // Eat '{'
1120                         if (p.next_token().cat() == catBegin)
1121                                 p.get_token();
1122                         p.skip_spaces(true);
1123                 }
1124         }
1125         if (outer_type == "shaded") {
1126                 // These boxes never have an inner box
1127                 ;
1128         } else if (p.next_token().asInput() == "\\parbox") {
1129                 inner = p.get_token().cs();
1130                 inner_flags = FLAG_ITEM;
1131         } else if (p.next_token().asInput() == "\\begin") {
1132                 // Is this a minipage or shaded box?
1133                 p.pushPosition();
1134                 p.get_token();
1135                 inner = p.getArg('{', '}');
1136                 p.popPosition();
1137                 if (inner == "minipage" || inner == "shaded")
1138                         inner_flags = FLAG_END;
1139                 else
1140                         inner = "";
1141         }
1142         p.popPosition();
1143         if (inner_flags == FLAG_END) {
1144                 if (inner != "shaded")
1145                 {
1146                         p.get_token();
1147                         p.getArg('{', '}');
1148                         eat_whitespace(p, os, parent_context, false);
1149                 }
1150                 parse_box(p, os, flags, FLAG_END, outer, parent_context,
1151                           outer_type, special, inner);
1152         } else {
1153                 if (inner_flags == FLAG_ITEM) {
1154                         p.get_token();
1155                         eat_whitespace(p, os, parent_context, false);
1156                 }
1157                 parse_box(p, os, flags, inner_flags, outer, parent_context,
1158                           outer_type, special, inner);
1159         }
1160 }
1161
1162
1163 void parse_listings(Parser & p, ostream & os, Context & parent_context, bool in_line)
1164 {
1165         parent_context.check_layout(os);
1166         begin_inset(os, "listings\n");
1167         if (p.hasOpt()) {
1168                 string arg = p.verbatimOption();
1169                 os << "lstparams " << '"' << arg << '"' << '\n';
1170         }
1171         if (in_line)
1172                 os << "inline true\n";
1173         else
1174                 os << "inline false\n";
1175         os << "status collapsed\n";
1176         Context context(true, parent_context.textclass);
1177         context.layout = &parent_context.textclass.plainLayout();
1178         string s;
1179         if (in_line) {
1180                 s = p.plainCommand('!', '!', "lstinline");
1181                 context.new_paragraph(os);
1182                 context.check_layout(os);
1183         } else
1184                 s = p.plainEnvironment("lstlisting");
1185         for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
1186                 if (*it == '\\')
1187                         os << "\n\\backslash\n";
1188                 else if (*it == '\n') {
1189                         // avoid adding an empty paragraph at the end
1190                         if (it + 1 != et) {
1191                                 context.new_paragraph(os);
1192                                 context.check_layout(os);
1193                         }
1194                 } else
1195                         os << *it;
1196         }
1197         context.check_end_layout(os);
1198         end_inset(os);
1199 }
1200
1201
1202 /// parse an unknown environment
1203 void parse_unknown_environment(Parser & p, string const & name, ostream & os,
1204                                unsigned flags, bool outer,
1205                                Context & parent_context)
1206 {
1207         if (name == "tabbing")
1208                 // We need to remember that we have to handle '\=' specially
1209                 flags |= FLAG_TABBING;
1210
1211         // We need to translate font changes and paragraphs inside the
1212         // environment to ERT if we have a non standard font.
1213         // Otherwise things like
1214         // \large\begin{foo}\huge bar\end{foo}
1215         // will not work.
1216         bool const specialfont =
1217                 (parent_context.font != parent_context.normalfont);
1218         bool const new_layout_allowed = parent_context.new_layout_allowed;
1219         if (specialfont)
1220                 parent_context.new_layout_allowed = false;
1221         handle_ert(os, "\\begin{" + name + "}", parent_context);
1222         parse_text_snippet(p, os, flags, outer, parent_context);
1223         handle_ert(os, "\\end{" + name + "}", parent_context);
1224         if (specialfont)
1225                 parent_context.new_layout_allowed = new_layout_allowed;
1226 }
1227
1228
1229 void parse_environment(Parser & p, ostream & os, bool outer,
1230                        string & last_env, Context & parent_context)
1231 {
1232         Layout const * newlayout;
1233         InsetLayout const * newinsetlayout = 0;
1234         string const name = p.getArg('{', '}');
1235         const bool is_starred = suffixIs(name, '*');
1236         string const unstarred_name = rtrim(name, "*");
1237         active_environments.push_back(name);
1238
1239         if (is_math_env(name)) {
1240                 parent_context.check_layout(os);
1241                 begin_inset(os, "Formula ");
1242                 os << "\\begin{" << name << "}";
1243                 parse_math(p, os, FLAG_END, MATH_MODE);
1244                 os << "\\end{" << name << "}";
1245                 end_inset(os);
1246                 if (is_display_math_env(name)) {
1247                         // Prevent the conversion of a line break to a space
1248                         // (bug 7668). This does not change the output, but
1249                         // looks ugly in LyX.
1250                         eat_whitespace(p, os, parent_context, false);
1251                 }
1252         }
1253
1254         else if (is_known(name, polyglossia_languages)) {
1255                 // We must begin a new paragraph if not already done
1256                 if (! parent_context.atParagraphStart()) {
1257                         parent_context.check_end_layout(os);
1258                         parent_context.new_paragraph(os);
1259                 }
1260                 // save the language for the case that a
1261                 // \textLANGUAGE is used
1262                 parent_context.font.language = polyglossia2lyx(name);
1263                 os << "\n\\lang " << parent_context.font.language << "\n";
1264                 parse_text(p, os, FLAG_END, outer, parent_context);
1265                 // Just in case the environment is empty
1266                 parent_context.extra_stuff.erase();
1267                 // We must begin a new paragraph to reset the language
1268                 parent_context.new_paragraph(os);
1269                 p.skip_spaces();
1270         }
1271
1272         else if (unstarred_name == "tabular" || name == "longtable") {
1273                 eat_whitespace(p, os, parent_context, false);
1274                 string width = "0pt";
1275                 if (name == "tabular*") {
1276                         width = lyx::translate_len(p.getArg('{', '}'));
1277                         eat_whitespace(p, os, parent_context, false);
1278                 }
1279                 parent_context.check_layout(os);
1280                 begin_inset(os, "Tabular ");
1281                 handle_tabular(p, os, name, width, parent_context);
1282                 end_inset(os);
1283                 p.skip_spaces();
1284         }
1285
1286         else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
1287                 eat_whitespace(p, os, parent_context, false);
1288                 string const opt = p.hasOpt() ? p.getArg('[', ']') : string();
1289                 eat_whitespace(p, os, parent_context, false);
1290                 parent_context.check_layout(os);
1291                 begin_inset(os, "Float " + unstarred_name + "\n");
1292                 // store the float type for subfloats
1293                 // subfloats only work with figures and tables
1294                 if (unstarred_name == "figure")
1295                         float_type = unstarred_name;
1296                 else if (unstarred_name == "table")
1297                         float_type = unstarred_name;
1298                 else
1299                         float_type = "";
1300                 if (!opt.empty())
1301                         os << "placement " << opt << '\n';
1302                 if (contains(opt, "H"))
1303                         preamble.registerAutomaticallyLoadedPackage("float");
1304                 else {
1305                         Floating const & fl = parent_context.textclass.floats()
1306                                 .getType(unstarred_name);
1307                         if (!fl.floattype().empty() && fl.usesFloatPkg())
1308                                 preamble.registerAutomaticallyLoadedPackage("float");
1309                 }
1310
1311                 os << "wide " << convert<string>(is_starred)
1312                    << "\nsideways false"
1313                    << "\nstatus open\n\n";
1314                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1315                 end_inset(os);
1316                 // We don't need really a new paragraph, but
1317                 // we must make sure that the next item gets a \begin_layout.
1318                 parent_context.new_paragraph(os);
1319                 p.skip_spaces();
1320                 // the float is parsed thus delete the type
1321                 float_type = "";
1322         }
1323
1324         else if (unstarred_name == "sidewaysfigure"
1325                 || unstarred_name == "sidewaystable") {
1326                 eat_whitespace(p, os, parent_context, false);
1327                 parent_context.check_layout(os);
1328                 if (unstarred_name == "sidewaysfigure")
1329                         begin_inset(os, "Float figure\n");
1330                 else
1331                         begin_inset(os, "Float table\n");
1332                 os << "wide " << convert<string>(is_starred)
1333                    << "\nsideways true"
1334                    << "\nstatus open\n\n";
1335                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1336                 end_inset(os);
1337                 // We don't need really a new paragraph, but
1338                 // we must make sure that the next item gets a \begin_layout.
1339                 parent_context.new_paragraph(os);
1340                 p.skip_spaces();
1341                 preamble.registerAutomaticallyLoadedPackage("rotfloat");
1342         }
1343
1344         else if (name == "wrapfigure" || name == "wraptable") {
1345                 // syntax is \begin{wrapfigure}[lines]{placement}[overhang]{width}
1346                 eat_whitespace(p, os, parent_context, false);
1347                 parent_context.check_layout(os);
1348                 // default values
1349                 string lines = "0";
1350                 string overhang = "0col%";
1351                 // parse
1352                 if (p.hasOpt())
1353                         lines = p.getArg('[', ']');
1354                 string const placement = p.getArg('{', '}');
1355                 if (p.hasOpt())
1356                         overhang = p.getArg('[', ']');
1357                 string const width = p.getArg('{', '}');
1358                 // write
1359                 if (name == "wrapfigure")
1360                         begin_inset(os, "Wrap figure\n");
1361                 else
1362                         begin_inset(os, "Wrap table\n");
1363                 os << "lines " << lines
1364                    << "\nplacement " << placement
1365                    << "\noverhang " << lyx::translate_len(overhang)
1366                    << "\nwidth " << lyx::translate_len(width)
1367                    << "\nstatus open\n\n";
1368                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1369                 end_inset(os);
1370                 // We don't need really a new paragraph, but
1371                 // we must make sure that the next item gets a \begin_layout.
1372                 parent_context.new_paragraph(os);
1373                 p.skip_spaces();
1374                 preamble.registerAutomaticallyLoadedPackage("wrapfig");
1375         }
1376
1377         else if (name == "minipage") {
1378                 eat_whitespace(p, os, parent_context, false);
1379                 // Test whether this is an outer box of a shaded box
1380                 p.pushPosition();
1381                 // swallow arguments
1382                 while (p.hasOpt()) {
1383                         p.getArg('[', ']');
1384                         p.skip_spaces(true);
1385                 }
1386                 p.getArg('{', '}');
1387                 p.skip_spaces(true);
1388                 Token t = p.get_token();
1389                 bool shaded = false;
1390                 if (t.asInput() == "\\begin") {
1391                         p.skip_spaces(true);
1392                         if (p.getArg('{', '}') == "shaded")
1393                                 shaded = true;
1394                 }
1395                 p.popPosition();
1396                 if (shaded)
1397                         parse_outer_box(p, os, FLAG_END, outer,
1398                                         parent_context, name, "shaded");
1399                 else
1400                         parse_box(p, os, 0, FLAG_END, outer, parent_context,
1401                                   "", "", name);
1402                 p.skip_spaces();
1403         }
1404
1405         else if (name == "comment") {
1406                 eat_whitespace(p, os, parent_context, false);
1407                 parent_context.check_layout(os);
1408                 begin_inset(os, "Note Comment\n");
1409                 os << "status open\n";
1410                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1411                 end_inset(os);
1412                 p.skip_spaces();
1413                 skip_braces(p); // eat {} that might by set by LyX behind comments
1414                 preamble.registerAutomaticallyLoadedPackage("verbatim");
1415         }
1416
1417         else if (name == "verbatim") {
1418                 os << "\n\\end_layout\n\n\\begin_layout Verbatim\n";
1419                 string const s = p.plainEnvironment("verbatim");
1420                 string::const_iterator it2 = s.begin();
1421                 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
1422                         if (*it == '\\')
1423                                 os << "\\backslash ";
1424                         else if (*it == '\n') {
1425                                 it2 = it + 1;
1426                                 // avoid adding an empty paragraph at the end
1427                                 // FIXME: if there are 2 consecutive spaces at the end ignore it
1428                                 // because LyX will re-add a \n
1429                                 // This hack must be removed once bug 8049 is fixed!
1430                                 if ((it + 1 != et) && (it + 2 != et || *it2 != '\n'))
1431                                         os << "\n\\end_layout\n\\begin_layout Verbatim\n";
1432                         } else 
1433                                 os << *it;
1434                 }
1435                 os << "\n\\end_layout\n\n";
1436                 p.skip_spaces();
1437                 // reset to Standard layout
1438                 os << "\n\\begin_layout Standard\n";
1439         }
1440
1441         else if (name == "lyxgreyedout") {
1442                 eat_whitespace(p, os, parent_context, false);
1443                 parent_context.check_layout(os);
1444                 begin_inset(os, "Note Greyedout\n");
1445                 os << "status open\n";
1446                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1447                 end_inset(os);
1448                 p.skip_spaces();
1449                 if (!preamble.notefontcolor().empty())
1450                         preamble.registerAutomaticallyLoadedPackage("color");
1451         }
1452
1453         else if (name == "framed" || name == "shaded") {
1454                 eat_whitespace(p, os, parent_context, false);
1455                 parse_outer_box(p, os, FLAG_END, outer, parent_context, name, "");
1456                 p.skip_spaces();
1457         }
1458
1459         else if (name == "lstlisting") {
1460                 eat_whitespace(p, os, parent_context, false);
1461                 // FIXME handle the automatic color package loading
1462                 // uwestoehr asks: In what case color is loaded?
1463                 parse_listings(p, os, parent_context, false);
1464                 p.skip_spaces();
1465         }
1466
1467         else if (!parent_context.new_layout_allowed)
1468                 parse_unknown_environment(p, name, os, FLAG_END, outer,
1469                                           parent_context);
1470
1471         // Alignment and spacing settings
1472         // FIXME (bug xxxx): These settings can span multiple paragraphs and
1473         //                                       therefore are totally broken!
1474         // Note that \centering, raggedright, and raggedleft cannot be handled, as
1475         // they are commands not environments. They are furthermore switches that
1476         // can be ended by another switches, but also by commands like \footnote or
1477         // \parbox. So the only safe way is to leave them untouched.
1478         else if (name == "center" || name == "centering" ||
1479                  name == "flushleft" || name == "flushright" ||
1480                  name == "singlespace" || name == "onehalfspace" ||
1481                  name == "doublespace" || name == "spacing") {
1482                 eat_whitespace(p, os, parent_context, false);
1483                 // We must begin a new paragraph if not already done
1484                 if (! parent_context.atParagraphStart()) {
1485                         parent_context.check_end_layout(os);
1486                         parent_context.new_paragraph(os);
1487                 }
1488                 if (name == "flushleft")
1489                         parent_context.add_extra_stuff("\\align left\n");
1490                 else if (name == "flushright")
1491                         parent_context.add_extra_stuff("\\align right\n");
1492                 else if (name == "center" || name == "centering")
1493                         parent_context.add_extra_stuff("\\align center\n");
1494                 else if (name == "singlespace")
1495                         parent_context.add_extra_stuff("\\paragraph_spacing single\n");
1496                 else if (name == "onehalfspace") {
1497                         parent_context.add_extra_stuff("\\paragraph_spacing onehalf\n");
1498                         preamble.registerAutomaticallyLoadedPackage("setspace");
1499                 } else if (name == "doublespace") {
1500                         parent_context.add_extra_stuff("\\paragraph_spacing double\n");
1501                         preamble.registerAutomaticallyLoadedPackage("setspace");
1502                 } else if (name == "spacing") {
1503                         parent_context.add_extra_stuff("\\paragraph_spacing other " + p.verbatim_item() + "\n");
1504                         preamble.registerAutomaticallyLoadedPackage("setspace");
1505                 }
1506                 parse_text(p, os, FLAG_END, outer, parent_context);
1507                 // Just in case the environment is empty
1508                 parent_context.extra_stuff.erase();
1509                 // We must begin a new paragraph to reset the alignment
1510                 parent_context.new_paragraph(os);
1511                 p.skip_spaces();
1512         }
1513
1514         // The single '=' is meant here.
1515         else if ((newlayout = findLayout(parent_context.textclass, name, false))) {
1516                 eat_whitespace(p, os, parent_context, false);
1517                 Context context(true, parent_context.textclass, newlayout,
1518                                 parent_context.layout, parent_context.font);
1519                 if (parent_context.deeper_paragraph) {
1520                         // We are beginning a nested environment after a
1521                         // deeper paragraph inside the outer list environment.
1522                         // Therefore we don't need to output a "begin deeper".
1523                         context.need_end_deeper = true;
1524                 }
1525                 parent_context.check_end_layout(os);
1526                 if (last_env == name) {
1527                         // we need to output a separator since LyX would export
1528                         // the two environments as one otherwise (bug 5716)
1529                         docstring const sep = from_ascii("--Separator--");
1530                         TeX2LyXDocClass const & textclass(parent_context.textclass);
1531                         if (textclass.hasLayout(sep)) {
1532                                 Context newcontext(parent_context);
1533                                 newcontext.layout = &(textclass[sep]);
1534                                 newcontext.check_layout(os);
1535                                 newcontext.check_end_layout(os);
1536                         } else {
1537                                 parent_context.check_layout(os);
1538                                 begin_inset(os, "Note Note\n");
1539                                 os << "status closed\n";
1540                                 Context newcontext(true, textclass,
1541                                                 &(textclass.defaultLayout()));
1542                                 newcontext.check_layout(os);
1543                                 newcontext.check_end_layout(os);
1544                                 end_inset(os);
1545                                 parent_context.check_end_layout(os);
1546                         }
1547                 }
1548                 switch (context.layout->latextype) {
1549                 case  LATEX_LIST_ENVIRONMENT:
1550                         context.add_par_extra_stuff("\\labelwidthstring "
1551                                                     + p.verbatim_item() + '\n');
1552                         p.skip_spaces();
1553                         break;
1554                 case  LATEX_BIB_ENVIRONMENT:
1555                         p.verbatim_item(); // swallow next arg
1556                         p.skip_spaces();
1557                         break;
1558                 default:
1559                         break;
1560                 }
1561                 context.check_deeper(os);
1562                 // handle known optional and required arguments
1563                 // layouts require all optional arguments before the required ones
1564                 // Unfortunately LyX can't handle arguments of list arguments (bug 7468):
1565                 // It is impossible to place anything after the environment name,
1566                 // but before the first \\item.
1567                 if (context.layout->latextype == LATEX_ENVIRONMENT) {
1568                         bool need_layout = true;
1569                         unsigned int optargs = 0;
1570                         while (optargs < context.layout->optargs) {
1571                                 eat_whitespace(p, os, context, false);
1572                                 if (p.next_token().cat() == catEscape ||
1573                                     p.next_token().character() != '[')
1574                                         break;
1575                                 p.get_token(); // eat '['
1576                                 if (need_layout) {
1577                                         context.check_layout(os);
1578                                         need_layout = false;
1579                                 }
1580                                 begin_inset(os, "Argument\n");
1581                                 os << "status collapsed\n\n";
1582                                 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
1583                                 end_inset(os);
1584                                 eat_whitespace(p, os, context, false);
1585                                 ++optargs;
1586                         }
1587                         unsigned int reqargs = 0;
1588                         while (reqargs < context.layout->reqargs) {
1589                                 eat_whitespace(p, os, context, false);
1590                                 if (p.next_token().cat() != catBegin)
1591                                         break;
1592                                 p.get_token(); // eat '{'
1593                                 if (need_layout) {
1594                                         context.check_layout(os);
1595                                         need_layout = false;
1596                                 }
1597                                 begin_inset(os, "Argument\n");
1598                                 os << "status collapsed\n\n";
1599                                 parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context);
1600                                 end_inset(os);
1601                                 eat_whitespace(p, os, context, false);
1602                                 ++reqargs;
1603                         }
1604                 }
1605                 parse_text(p, os, FLAG_END, outer, context);
1606                 context.check_end_layout(os);
1607                 if (parent_context.deeper_paragraph) {
1608                         // We must suppress the "end deeper" because we
1609                         // suppressed the "begin deeper" above.
1610                         context.need_end_deeper = false;
1611                 }
1612                 context.check_end_deeper(os);
1613                 parent_context.new_paragraph(os);
1614                 p.skip_spaces();
1615                 if (!preamble.titleLayoutFound())
1616                         preamble.titleLayoutFound(newlayout->intitle);
1617                 set<string> const & req = newlayout->requires();
1618                 set<string>::const_iterator it = req.begin();
1619                 set<string>::const_iterator en = req.end();
1620                 for (; it != en; ++it)
1621                         preamble.registerAutomaticallyLoadedPackage(*it);
1622         }
1623
1624         // The single '=' is meant here.
1625         else if ((newinsetlayout = findInsetLayout(parent_context.textclass, name, false))) {
1626                 eat_whitespace(p, os, parent_context, false);
1627                 parent_context.check_layout(os);
1628                 begin_inset(os, "Flex ");
1629                 os << to_utf8(newinsetlayout->name()) << '\n'
1630                    << "status collapsed\n";
1631                 parse_text_in_inset(p, os, FLAG_END, false, parent_context, newinsetlayout);
1632                 end_inset(os);
1633         }
1634
1635         else if (name == "appendix") {
1636                 // This is no good latex style, but it works and is used in some documents...
1637                 eat_whitespace(p, os, parent_context, false);
1638                 parent_context.check_end_layout(os);
1639                 Context context(true, parent_context.textclass, parent_context.layout,
1640                                 parent_context.layout, parent_context.font);
1641                 context.check_layout(os);
1642                 os << "\\start_of_appendix\n";
1643                 parse_text(p, os, FLAG_END, outer, context);
1644                 context.check_end_layout(os);
1645                 p.skip_spaces();
1646         }
1647
1648         else if (known_environments.find(name) != known_environments.end()) {
1649                 vector<ArgumentType> arguments = known_environments[name];
1650                 // The last "argument" denotes wether we may translate the
1651                 // environment contents to LyX
1652                 // The default required if no argument is given makes us
1653                 // compatible with the reLyXre environment.
1654                 ArgumentType contents = arguments.empty() ?
1655                         required :
1656                         arguments.back();
1657                 if (!arguments.empty())
1658                         arguments.pop_back();
1659                 // See comment in parse_unknown_environment()
1660                 bool const specialfont =
1661                         (parent_context.font != parent_context.normalfont);
1662                 bool const new_layout_allowed =
1663                         parent_context.new_layout_allowed;
1664                 if (specialfont)
1665                         parent_context.new_layout_allowed = false;
1666                 parse_arguments("\\begin{" + name + "}", arguments, p, os,
1667                                 outer, parent_context);
1668                 if (contents == verbatim)
1669                         handle_ert(os, p.verbatimEnvironment(name),
1670                                    parent_context);
1671                 else
1672                         parse_text_snippet(p, os, FLAG_END, outer,
1673                                            parent_context);
1674                 handle_ert(os, "\\end{" + name + "}", parent_context);
1675                 if (specialfont)
1676                         parent_context.new_layout_allowed = new_layout_allowed;
1677         }
1678
1679         else
1680                 parse_unknown_environment(p, name, os, FLAG_END, outer,
1681                                           parent_context);
1682
1683         last_env = name;
1684         active_environments.pop_back();
1685 }
1686
1687
1688 /// parses a comment and outputs it to \p os.
1689 void parse_comment(Parser & p, ostream & os, Token const & t, Context & context)
1690 {
1691         LASSERT(t.cat() == catComment, return);
1692         if (!t.cs().empty()) {
1693                 context.check_layout(os);
1694                 handle_comment(os, '%' + t.cs(), context);
1695                 if (p.next_token().cat() == catNewline) {
1696                         // A newline after a comment line starts a new
1697                         // paragraph
1698                         if (context.new_layout_allowed) {
1699                                 if(!context.atParagraphStart())
1700                                         // Only start a new paragraph if not already
1701                                         // done (we might get called recursively)
1702                                         context.new_paragraph(os);
1703                         } else
1704                                 handle_ert(os, "\n", context);
1705                         eat_whitespace(p, os, context, true);
1706                 }
1707         } else {
1708                 // "%\n" combination
1709                 p.skip_spaces();
1710         }
1711 }
1712
1713
1714 /*!
1715  * Reads spaces and comments until the first non-space, non-comment token.
1716  * New paragraphs (double newlines or \\par) are handled like simple spaces
1717  * if \p eatParagraph is true.
1718  * Spaces are skipped, but comments are written to \p os.
1719  */
1720 void eat_whitespace(Parser & p, ostream & os, Context & context,
1721                     bool eatParagraph)
1722 {
1723         while (p.good()) {
1724                 Token const & t = p.get_token();
1725                 if (t.cat() == catComment)
1726                         parse_comment(p, os, t, context);
1727                 else if ((! eatParagraph && p.isParagraph()) ||
1728                          (t.cat() != catSpace && t.cat() != catNewline)) {
1729                         p.putback();
1730                         return;
1731                 }
1732         }
1733 }
1734
1735
1736 /*!
1737  * Set a font attribute, parse text and reset the font attribute.
1738  * \param attribute Attribute name (e.g. \\family, \\shape etc.)
1739  * \param currentvalue Current value of the attribute. Is set to the new
1740  * value during parsing.
1741  * \param newvalue New value of the attribute
1742  */
1743 void parse_text_attributes(Parser & p, ostream & os, unsigned flags, bool outer,
1744                            Context & context, string const & attribute,
1745                            string & currentvalue, string const & newvalue)
1746 {
1747         context.check_layout(os);
1748         string const oldvalue = currentvalue;
1749         currentvalue = newvalue;
1750         os << '\n' << attribute << ' ' << newvalue << "\n";
1751         parse_text_snippet(p, os, flags, outer, context);
1752         context.check_layout(os);
1753         os << '\n' << attribute << ' ' << oldvalue << "\n";
1754         currentvalue = oldvalue;
1755 }
1756
1757
1758 /// get the arguments of a natbib or jurabib citation command
1759 void get_cite_arguments(Parser & p, bool natbibOrder,
1760         string & before, string & after)
1761 {
1762         // We need to distinguish "" and "[]", so we can't use p.getOpt().
1763
1764         // text before the citation
1765         before.clear();
1766         // text after the citation
1767         after = p.getFullOpt();
1768
1769         if (!after.empty()) {
1770                 before = p.getFullOpt();
1771                 if (natbibOrder && !before.empty())
1772                         swap(before, after);
1773         }
1774 }
1775
1776
1777 /// Convert filenames with TeX macros and/or quotes to something LyX
1778 /// can understand
1779 string const normalize_filename(string const & name)
1780 {
1781         Parser p(trim(name, "\""));
1782         ostringstream os;
1783         while (p.good()) {
1784                 Token const & t = p.get_token();
1785                 if (t.cat() != catEscape)
1786                         os << t.asInput();
1787                 else if (t.cs() == "lyxdot") {
1788                         // This is used by LyX for simple dots in relative
1789                         // names
1790                         os << '.';
1791                         p.skip_spaces();
1792                 } else if (t.cs() == "space") {
1793                         os << ' ';
1794                         p.skip_spaces();
1795                 } else
1796                         os << t.asInput();
1797         }
1798         return os.str();
1799 }
1800
1801
1802 /// Convert \p name from TeX convention (relative to master file) to LyX
1803 /// convention (relative to .lyx file) if it is relative
1804 void fix_relative_filename(string & name)
1805 {
1806         if (FileName::isAbsolute(name))
1807                 return;
1808
1809         name = to_utf8(makeRelPath(from_utf8(makeAbsPath(name, getMasterFilePath()).absFileName()),
1810                                    from_utf8(getParentFilePath())));
1811 }
1812
1813
1814 /// Parse a NoWeb Scrap section. The initial "<<" is already parsed.
1815 void parse_noweb(Parser & p, ostream & os, Context & context)
1816 {
1817         // assemble the rest of the keyword
1818         string name("<<");
1819         bool scrap = false;
1820         while (p.good()) {
1821                 Token const & t = p.get_token();
1822                 if (t.asInput() == ">" && p.next_token().asInput() == ">") {
1823                         name += ">>";
1824                         p.get_token();
1825                         scrap = (p.good() && p.next_token().asInput() == "=");
1826                         if (scrap)
1827                                 name += p.get_token().asInput();
1828                         break;
1829                 }
1830                 name += t.asInput();
1831         }
1832
1833         if (!scrap || !context.new_layout_allowed ||
1834             !context.textclass.hasLayout(from_ascii("Scrap"))) {
1835                 cerr << "Warning: Could not interpret '" << name
1836                      << "'. Ignoring it." << endl;
1837                 return;
1838         }
1839
1840         // We use new_paragraph instead of check_end_layout because the stuff
1841         // following the noweb chunk needs to start with a \begin_layout.
1842         // This may create a new paragraph even if there was none in the
1843         // noweb file, but the alternative is an invalid LyX file. Since
1844         // noweb code chunks are implemented with a layout style in LyX they
1845         // always must be in an own paragraph.
1846         context.new_paragraph(os);
1847         Context newcontext(true, context.textclass,
1848                 &context.textclass[from_ascii("Scrap")]);
1849         newcontext.check_layout(os);
1850         os << name;
1851         while (p.good()) {
1852                 Token const & t = p.get_token();
1853                 // We abuse the parser a bit, because this is no TeX syntax
1854                 // at all.
1855                 if (t.cat() == catEscape)
1856                         os << subst(t.asInput(), "\\", "\n\\backslash\n");
1857                 else {
1858                         ostringstream oss;
1859                         Context tmp(false, context.textclass,
1860                                     &context.textclass[from_ascii("Scrap")]);
1861                         tmp.need_end_layout = true;
1862                         tmp.check_layout(oss);
1863                         os << subst(t.asInput(), "\n", oss.str());
1864                 }
1865                 // The scrap chunk is ended by an @ at the beginning of a line.
1866                 // After the @ the line may contain a comment and/or
1867                 // whitespace, but nothing else.
1868                 if (t.asInput() == "@" && p.prev_token().cat() == catNewline &&
1869                     (p.next_token().cat() == catSpace ||
1870                      p.next_token().cat() == catNewline ||
1871                      p.next_token().cat() == catComment)) {
1872                         while (p.good() && p.next_token().cat() == catSpace)
1873                                 os << p.get_token().asInput();
1874                         if (p.next_token().cat() == catComment)
1875                                 // The comment includes a final '\n'
1876                                 os << p.get_token().asInput();
1877                         else {
1878                                 if (p.next_token().cat() == catNewline)
1879                                         p.get_token();
1880                                 os << '\n';
1881                         }
1882                         break;
1883                 }
1884         }
1885         newcontext.check_end_layout(os);
1886 }
1887
1888
1889 /// detects \\def, \\long\\def and \\global\\long\\def with ws and comments
1890 bool is_macro(Parser & p)
1891 {
1892         Token first = p.curr_token();
1893         if (first.cat() != catEscape || !p.good())
1894                 return false;
1895         if (first.cs() == "def")
1896                 return true;
1897         if (first.cs() != "global" && first.cs() != "long")
1898                 return false;
1899         Token second = p.get_token();
1900         int pos = 1;
1901         while (p.good() && !p.isParagraph() && (second.cat() == catSpace ||
1902                second.cat() == catNewline || second.cat() == catComment)) {
1903                 second = p.get_token();
1904                 pos++;
1905         }
1906         bool secondvalid = second.cat() == catEscape;
1907         Token third;
1908         bool thirdvalid = false;
1909         if (p.good() && first.cs() == "global" && secondvalid &&
1910             second.cs() == "long") {
1911                 third = p.get_token();
1912                 pos++;
1913                 while (p.good() && !p.isParagraph() &&
1914                        (third.cat() == catSpace ||
1915                         third.cat() == catNewline ||
1916                         third.cat() == catComment)) {
1917                         third = p.get_token();
1918                         pos++;
1919                 }
1920                 thirdvalid = third.cat() == catEscape;
1921         }
1922         for (int i = 0; i < pos; ++i)
1923                 p.putback();
1924         if (!secondvalid)
1925                 return false;
1926         if (!thirdvalid)
1927                 return (first.cs() == "global" || first.cs() == "long") &&
1928                        second.cs() == "def";
1929         return first.cs() == "global" && second.cs() == "long" &&
1930                third.cs() == "def";
1931 }
1932
1933
1934 /// Parse a macro definition (assumes that is_macro() returned true)
1935 void parse_macro(Parser & p, ostream & os, Context & context)
1936 {
1937         context.check_layout(os);
1938         Token first = p.curr_token();
1939         Token second;
1940         Token third;
1941         string command = first.asInput();
1942         if (first.cs() != "def") {
1943                 p.get_token();
1944                 eat_whitespace(p, os, context, false);
1945                 second = p.curr_token();
1946                 command += second.asInput();
1947                 if (second.cs() != "def") {
1948                         p.get_token();
1949                         eat_whitespace(p, os, context, false);
1950                         third = p.curr_token();
1951                         command += third.asInput();
1952                 }
1953         }
1954         eat_whitespace(p, os, context, false);
1955         string const name = p.get_token().cs();
1956         eat_whitespace(p, os, context, false);
1957
1958         // parameter text
1959         bool simple = true;
1960         string paramtext;
1961         int arity = 0;
1962         while (p.next_token().cat() != catBegin) {
1963                 if (p.next_token().cat() == catParameter) {
1964                         // # found
1965                         p.get_token();
1966                         paramtext += "#";
1967
1968                         // followed by number?
1969                         if (p.next_token().cat() == catOther) {
1970                                 char c = p.getChar();
1971                                 paramtext += c;
1972                                 // number = current arity + 1?
1973                                 if (c == arity + '0' + 1)
1974                                         ++arity;
1975                                 else
1976                                         simple = false;
1977                         } else
1978                                 paramtext += p.get_token().cs();
1979                 } else {
1980                         paramtext += p.get_token().cs();
1981                         simple = false;
1982                 }
1983         }
1984
1985         // only output simple (i.e. compatible) macro as FormulaMacros
1986         string ert = '\\' + name + ' ' + paramtext + '{' + p.verbatim_item() + '}';
1987         if (simple) {
1988                 context.check_layout(os);
1989                 begin_inset(os, "FormulaMacro");
1990                 os << "\n\\def" << ert;
1991                 end_inset(os);
1992         } else
1993                 handle_ert(os, command + ert, context);
1994 }
1995
1996
1997 void registerExternalTemplatePackages(string const & name)
1998 {
1999         external::TemplateManager const & etm = external::TemplateManager::get();
2000         external::Template const * const et = etm.getTemplateByName(name);
2001         if (!et)
2002                 return;
2003         external::Template::Formats::const_iterator cit = et->formats.end();
2004         if (pdflatex)
2005                 cit = et->formats.find("PDFLaTeX");
2006         if (cit == et->formats.end())
2007                 // If the template has not specified a PDFLaTeX output,
2008                 // we try the LaTeX format.
2009                 cit = et->formats.find("LaTeX");
2010         if (cit == et->formats.end())
2011                 return;
2012         vector<string>::const_iterator qit = cit->second.requirements.begin();
2013         vector<string>::const_iterator qend = cit->second.requirements.end();
2014         for (; qit != qend; ++qit)
2015                 preamble.registerAutomaticallyLoadedPackage(*qit);
2016 }
2017
2018 } // anonymous namespace
2019
2020
2021 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
2022                 Context & context)
2023 {
2024         Layout const * newlayout = 0;
2025         InsetLayout const * newinsetlayout = 0;
2026         char const * const * where = 0;
2027         // Store the latest bibliographystyle and nocite{*} option
2028         // (needed for bibtex inset)
2029         string btprint;
2030         string bibliographystyle = "default";
2031         bool const use_natbib = preamble.isPackageUsed("natbib");
2032         bool const use_jurabib = preamble.isPackageUsed("jurabib");
2033         string last_env;
2034         while (p.good()) {
2035                 Token const & t = p.get_token();
2036
2037 #ifdef FILEDEBUG
2038                 debugToken(cerr, t, flags);
2039 #endif
2040
2041                 if (flags & FLAG_ITEM) {
2042                         if (t.cat() == catSpace)
2043                                 continue;
2044
2045                         flags &= ~FLAG_ITEM;
2046                         if (t.cat() == catBegin) {
2047                                 // skip the brace and collect everything to the next matching
2048                                 // closing brace
2049                                 flags |= FLAG_BRACE_LAST;
2050                                 continue;
2051                         }
2052
2053                         // handle only this single token, leave the loop if done
2054                         flags |= FLAG_LEAVE;
2055                 }
2056
2057                 if (t.cat() != catEscape && t.character() == ']' &&
2058                     (flags & FLAG_BRACK_LAST))
2059                         return;
2060                 if (t.cat() == catEnd && (flags & FLAG_BRACE_LAST))
2061                         return;
2062
2063                 // If there is anything between \end{env} and \begin{env} we
2064                 // don't need to output a separator.
2065                 if (t.cat() != catSpace && t.cat() != catNewline &&
2066                     t.asInput() != "\\begin")
2067                         last_env = "";
2068
2069                 //
2070                 // cat codes
2071                 //
2072                 if (t.cat() == catMath) {
2073                         // we are inside some text mode thingy, so opening new math is allowed
2074                         context.check_layout(os);
2075                         begin_inset(os, "Formula ");
2076                         Token const & n = p.get_token();
2077                         bool const display(n.cat() == catMath && outer);
2078                         if (display) {
2079                                 // TeX's $$...$$ syntax for displayed math
2080                                 os << "\\[";
2081                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
2082                                 os << "\\]";
2083                                 p.get_token(); // skip the second '$' token
2084                         } else {
2085                                 // simple $...$  stuff
2086                                 p.putback();
2087                                 os << '$';
2088                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
2089                                 os << '$';
2090                         }
2091                         end_inset(os);
2092                         if (display) {
2093                                 // Prevent the conversion of a line break to a
2094                                 // space (bug 7668). This does not change the
2095                                 // output, but looks ugly in LyX.
2096                                 eat_whitespace(p, os, context, false);
2097                         }
2098                 }
2099
2100                 else if (t.cat() == catSuper || t.cat() == catSub)
2101                         cerr << "catcode " << t << " illegal in text mode\n";
2102
2103                 // Basic support for english quotes. This should be
2104                 // extended to other quotes, but is not so easy (a
2105                 // left english quote is the same as a right german
2106                 // quote...)
2107                 else if (t.asInput() == "`" && p.next_token().asInput() == "`") {
2108                         context.check_layout(os);
2109                         begin_inset(os, "Quotes ");
2110                         os << "eld";
2111                         end_inset(os);
2112                         p.get_token();
2113                         skip_braces(p);
2114                 }
2115                 else if (t.asInput() == "'" && p.next_token().asInput() == "'") {
2116                         context.check_layout(os);
2117                         begin_inset(os, "Quotes ");
2118                         os << "erd";
2119                         end_inset(os);
2120                         p.get_token();
2121                         skip_braces(p);
2122                 }
2123
2124                 else if (t.asInput() == ">" && p.next_token().asInput() == ">") {
2125                         context.check_layout(os);
2126                         begin_inset(os, "Quotes ");
2127                         os << "ald";
2128                         end_inset(os);
2129                         p.get_token();
2130                         skip_braces(p);
2131                 }
2132
2133                 else if (t.asInput() == "<" && p.next_token().asInput() == "<") {
2134                         context.check_layout(os);
2135                         begin_inset(os, "Quotes ");
2136                         os << "ard";
2137                         end_inset(os);
2138                         p.get_token();
2139                         skip_braces(p);
2140                 }
2141
2142                 else if (t.asInput() == "<"
2143                          && p.next_token().asInput() == "<" && noweb_mode) {
2144                         p.get_token();
2145                         parse_noweb(p, os, context);
2146                 }
2147
2148                 else if (t.cat() == catSpace || (t.cat() == catNewline && ! p.isParagraph()))
2149                         check_space(p, os, context);
2150
2151                 else if (t.character() == '[' && noweb_mode &&
2152                          p.next_token().character() == '[') {
2153                         // These can contain underscores
2154                         p.putback();
2155                         string const s = p.getFullOpt() + ']';
2156                         if (p.next_token().character() == ']')
2157                                 p.get_token();
2158                         else
2159                                 cerr << "Warning: Inserting missing ']' in '"
2160                                      << s << "'." << endl;
2161                         handle_ert(os, s, context);
2162                 }
2163
2164                 else if (t.cat() == catLetter) {
2165                         context.check_layout(os);
2166                         // Workaround for bug 4752.
2167                         // FIXME: This whole code block needs to be removed
2168                         //        when the bug is fixed and tex2lyx produces
2169                         //        the updated file format.
2170                         // The replacement algorithm in LyX is so stupid that
2171                         // it even translates a phrase if it is part of a word.
2172                         bool handled = false;
2173                         for (int const * l = known_phrase_lengths; *l; ++l) {
2174                                 string phrase = t.cs();
2175                                 for (int i = 1; i < *l && p.next_token().isAlnumASCII(); ++i)
2176                                         phrase += p.get_token().cs();
2177                                 if (is_known(phrase, known_coded_phrases)) {
2178                                         handle_ert(os, phrase, context);
2179                                         handled = true;
2180                                         break;
2181                                 } else {
2182                                         for (size_t i = 1; i < phrase.length(); ++i)
2183                                                 p.putback();
2184                                 }
2185                         }
2186                         if (!handled)
2187                                 os << t.cs();
2188                 }
2189
2190                 else if (t.cat() == catOther ||
2191                                t.cat() == catAlign ||
2192                                t.cat() == catParameter) {
2193                         // This translates "&" to "\\&" which may be wrong...
2194                         context.check_layout(os);
2195                         os << t.cs();
2196                 }
2197
2198                 else if (p.isParagraph()) {
2199                         if (context.new_layout_allowed)
2200                                 context.new_paragraph(os);
2201                         else
2202                                 handle_ert(os, "\\par ", context);
2203                         eat_whitespace(p, os, context, true);
2204                 }
2205
2206                 else if (t.cat() == catActive) {
2207                         context.check_layout(os);
2208                         if (t.character() == '~') {
2209                                 if (context.layout->free_spacing)
2210                                         os << ' ';
2211                                 else {
2212                                         begin_inset(os, "space ~\n");
2213                                         end_inset(os);
2214                                 }
2215                         } else
2216                                 os << t.cs();
2217                 }
2218
2219                 else if (t.cat() == catBegin) {
2220                         Token const next = p.next_token();
2221                         Token const end = p.next_next_token();
2222                         if (next.cat() == catEnd) {
2223                         // {}
2224                         Token const prev = p.prev_token();
2225                         p.get_token();
2226                         if (p.next_token().character() == '`' ||
2227                             (prev.character() == '-' &&
2228                              p.next_token().character() == '-'))
2229                                 ; // ignore it in {}`` or -{}-
2230                         else
2231                                 handle_ert(os, "{}", context);
2232                         } else if (next.cat() == catEscape &&
2233                                    is_known(next.cs(), known_quotes) &&
2234                                    end.cat() == catEnd) {
2235                                 // Something like {\textquoteright} (e.g.
2236                                 // from writer2latex). LyX writes
2237                                 // \textquoteright{}, so we may skip the
2238                                 // braces here for better readability.
2239                                 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2240                                                    outer, context);
2241                         } else {
2242                         context.check_layout(os);
2243                         // special handling of font attribute changes
2244                         Token const prev = p.prev_token();
2245                         TeXFont const oldFont = context.font;
2246                         if (next.character() == '[' ||
2247                             next.character() == ']' ||
2248                             next.character() == '*') {
2249                                 p.get_token();
2250                                 if (p.next_token().cat() == catEnd) {
2251                                         os << next.cs();
2252                                         p.get_token();
2253                                 } else {
2254                                         p.putback();
2255                                         handle_ert(os, "{", context);
2256                                         parse_text_snippet(p, os,
2257                                                         FLAG_BRACE_LAST,
2258                                                         outer, context);
2259                                         handle_ert(os, "}", context);
2260                                 }
2261                         } else if (! context.new_layout_allowed) {
2262                                 handle_ert(os, "{", context);
2263                                 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2264                                                    outer, context);
2265                                 handle_ert(os, "}", context);
2266                         } else if (is_known(next.cs(), known_sizes)) {
2267                                 // next will change the size, so we must
2268                                 // reset it here
2269                                 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2270                                                    outer, context);
2271                                 if (!context.atParagraphStart())
2272                                         os << "\n\\size "
2273                                            << context.font.size << "\n";
2274                         } else if (is_known(next.cs(), known_font_families)) {
2275                                 // next will change the font family, so we
2276                                 // must reset it here
2277                                 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2278                                                    outer, context);
2279                                 if (!context.atParagraphStart())
2280                                         os << "\n\\family "
2281                                            << context.font.family << "\n";
2282                         } else if (is_known(next.cs(), known_font_series)) {
2283                                 // next will change the font series, so we
2284                                 // must reset it here
2285                                 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2286                                                    outer, context);
2287                                 if (!context.atParagraphStart())
2288                                         os << "\n\\series "
2289                                            << context.font.series << "\n";
2290                         } else if (is_known(next.cs(), known_font_shapes)) {
2291                                 // next will change the font shape, so we
2292                                 // must reset it here
2293                                 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2294                                                    outer, context);
2295                                 if (!context.atParagraphStart())
2296                                         os << "\n\\shape "
2297                                            << context.font.shape << "\n";
2298                         } else if (is_known(next.cs(), known_old_font_families) ||
2299                                    is_known(next.cs(), known_old_font_series) ||
2300                                    is_known(next.cs(), known_old_font_shapes)) {
2301                                 // next will change the font family, series
2302                                 // and shape, so we must reset it here
2303                                 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2304                                                    outer, context);
2305                                 if (!context.atParagraphStart())
2306                                         os <<  "\n\\family "
2307                                            << context.font.family
2308                                            << "\n\\series "
2309                                            << context.font.series
2310                                            << "\n\\shape "
2311                                            << context.font.shape << "\n";
2312                         } else {
2313                                 handle_ert(os, "{", context);
2314                                 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2315                                                    outer, context);
2316                                 handle_ert(os, "}", context);
2317                                 }
2318                         }
2319                 }
2320
2321                 else if (t.cat() == catEnd) {
2322                         if (flags & FLAG_BRACE_LAST) {
2323                                 return;
2324                         }
2325                         cerr << "stray '}' in text\n";
2326                         handle_ert(os, "}", context);
2327                 }
2328
2329                 else if (t.cat() == catComment)
2330                         parse_comment(p, os, t, context);
2331
2332                 //
2333                 // control sequences
2334                 //
2335
2336                 else if (t.cs() == "(") {
2337                         context.check_layout(os);
2338                         begin_inset(os, "Formula");
2339                         os << " \\(";
2340                         parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
2341                         os << "\\)";
2342                         end_inset(os);
2343                 }
2344
2345                 else if (t.cs() == "[") {
2346                         context.check_layout(os);
2347                         begin_inset(os, "Formula");
2348                         os << " \\[";
2349                         parse_math(p, os, FLAG_EQUATION, MATH_MODE);
2350                         os << "\\]";
2351                         end_inset(os);
2352                         // Prevent the conversion of a line break to a space
2353                         // (bug 7668). This does not change the output, but
2354                         // looks ugly in LyX.
2355                         eat_whitespace(p, os, context, false);
2356                 }
2357
2358                 else if (t.cs() == "begin")
2359                         parse_environment(p, os, outer, last_env,
2360                                           context);
2361
2362                 else if (t.cs() == "end") {
2363                         if (flags & FLAG_END) {
2364                                 // eat environment name
2365                                 string const name = p.getArg('{', '}');
2366                                 if (name != active_environment())
2367                                         cerr << "\\end{" + name + "} does not match \\begin{"
2368                                                 + active_environment() + "}\n";
2369                                 return;
2370                         }
2371                         p.error("found 'end' unexpectedly");
2372                 }
2373
2374                 else if (t.cs() == "item") {
2375                         string s;
2376                         bool const optarg = p.hasOpt();
2377                         if (optarg) {
2378                                 // FIXME: This swallows comments, but we cannot use
2379                                 //        eat_whitespace() since we must not output
2380                                 //        anything before the item.
2381                                 p.skip_spaces(true);
2382                                 s = p.verbatimOption();
2383                         } else
2384                                 p.skip_spaces(false);
2385                         context.set_item();
2386                         context.check_layout(os);
2387                         if (context.has_item) {
2388                                 // An item in an unknown list-like environment
2389                                 // FIXME: Do this in check_layout()!
2390                                 context.has_item = false;
2391                                 if (optarg)
2392                                         handle_ert(os, "\\item", context);
2393                                 else
2394                                         handle_ert(os, "\\item ", context);
2395                         }
2396                         if (optarg) {
2397                                 if (context.layout->labeltype != LABEL_MANUAL) {
2398                                         // LyX does not support \item[\mybullet]
2399                                         // in itemize environments
2400                                         Parser p2(s + ']');
2401                                         os << parse_text_snippet(p2,
2402                                                 FLAG_BRACK_LAST, outer, context);
2403                                 } else if (!s.empty()) {
2404                                         // LyX adds braces around the argument,
2405                                         // so we need to remove them here.
2406                                         if (s.size() > 2 && s[0] == '{' &&
2407                                             s[s.size()-1] == '}')
2408                                                 s = s.substr(1, s.size()-2);
2409                                         // If the argument contains a space we
2410                                         // must put it into ERT: Otherwise LyX
2411                                         // would misinterpret the space as
2412                                         // item delimiter (bug 7663)
2413                                         if (contains(s, ' ')) {
2414                                                 handle_ert(os, s, context);
2415                                         } else {
2416                                                 Parser p2(s + ']');
2417                                                 os << parse_text_snippet(p2,
2418                                                         FLAG_BRACK_LAST,
2419                                                         outer, context);
2420                                         }
2421                                         // The space is needed to separate the
2422                                         // item from the rest of the sentence.
2423                                         os << ' ';
2424                                         eat_whitespace(p, os, context, false);
2425                                 }
2426                         }
2427                 }
2428
2429                 else if (t.cs() == "bibitem") {
2430                         context.set_item();
2431                         context.check_layout(os);
2432                         eat_whitespace(p, os, context, false);
2433                         string label = convert_command_inset_arg(p.verbatimOption());
2434                         string key = convert_command_inset_arg(p.verbatim_item());
2435                         if (contains(label, '\\') || contains(key, '\\')) {
2436                                 // LyX can't handle LaTeX commands in labels or keys
2437                                 handle_ert(os, t.asInput() + '[' + label +
2438                                                "]{" + p.verbatim_item() + '}',
2439                                            context);
2440                         } else {
2441                                 begin_command_inset(os, "bibitem", "bibitem");
2442                                 os << "label \"" << label << "\"\n"
2443                                       "key \"" << key << "\"\n";
2444                                 end_inset(os);
2445                         }
2446                 }
2447
2448                 else if (is_macro(p)) {
2449                         // catch the case of \def\inputGnumericTable
2450                         bool macro = true;
2451                         if (t.cs() == "def") {
2452                                 Token second = p.next_token();
2453                                 if (second.cs() == "inputGnumericTable") {
2454                                         p.pushPosition();
2455                                         p.get_token();
2456                                         skip_braces(p);
2457                                         Token third = p.get_token();
2458                                         p.popPosition();
2459                                         if (third.cs() == "input") {
2460                                                 p.get_token();
2461                                                 skip_braces(p);
2462                                                 p.get_token();
2463                                                 string name = normalize_filename(p.verbatim_item());
2464                                                 string const path = getMasterFilePath();
2465                                                 // We want to preserve relative / absolute filenames,
2466                                                 // therefore path is only used for testing
2467                                                 // The file extension is in every case ".tex".
2468                                                 // So we need to remove this extension and check for
2469                                                 // the original one.
2470                                                 name = removeExtension(name);
2471                                                 if (!makeAbsPath(name, path).exists()) {
2472                                                         char const * const Gnumeric_formats[] = {"gnumeric",
2473                                                                 "ods", "xls", 0};
2474                                                         string const Gnumeric_name =
2475                                                                 find_file(name, path, Gnumeric_formats);
2476                                                         if (!Gnumeric_name.empty())
2477                                                                 name = Gnumeric_name;
2478                                                 }
2479                                                 if (makeAbsPath(name, path).exists())
2480                                                         fix_relative_filename(name);
2481                                                 else
2482                                                         cerr << "Warning: Could not find file '"
2483                                                              << name << "'." << endl;
2484                                                 context.check_layout(os);
2485                                                 begin_inset(os, "External\n\ttemplate ");
2486                                                 os << "GnumericSpreadsheet\n\tfilename "
2487                                                    << name << "\n";
2488                                                 end_inset(os);
2489                                                 context.check_layout(os);
2490                                                 macro = false;
2491                                                 // register the packages that are automatically reloaded
2492                                                 // by the Gnumeric template
2493                                                 registerExternalTemplatePackages("GnumericSpreadsheet");
2494                                         }
2495                                 }
2496                         }
2497                         if (macro)
2498                                 parse_macro(p, os, context);
2499                 }
2500
2501                 else if (t.cs() == "noindent") {
2502                         p.skip_spaces();
2503                         context.add_par_extra_stuff("\\noindent\n");
2504                 }
2505
2506                 else if (t.cs() == "appendix") {
2507                         context.add_par_extra_stuff("\\start_of_appendix\n");
2508                         // We need to start a new paragraph. Otherwise the
2509                         // appendix in 'bla\appendix\chapter{' would start
2510                         // too late.
2511                         context.new_paragraph(os);
2512                         // We need to make sure that the paragraph is
2513                         // generated even if it is empty. Otherwise the
2514                         // appendix in '\par\appendix\par\chapter{' would
2515                         // start too late.
2516                         context.check_layout(os);
2517                         // FIXME: This is a hack to prevent paragraph
2518                         // deletion if it is empty. Handle this better!
2519                         handle_comment(os,
2520                                 "%dummy comment inserted by tex2lyx to "
2521                                 "ensure that this paragraph is not empty",
2522                                 context);
2523                         // Both measures above may generate an additional
2524                         // empty paragraph, but that does not hurt, because
2525                         // whitespace does not matter here.
2526                         eat_whitespace(p, os, context, true);
2527                 }
2528
2529                 // Must catch empty dates before findLayout is called below
2530                 else if (t.cs() == "date") {
2531                         eat_whitespace(p, os, context, false);
2532                         p.pushPosition();
2533                         string const date = p.verbatim_item();
2534                         p.popPosition();
2535                         if (date.empty()) {
2536                                 preamble.suppressDate(true);
2537                                 p.verbatim_item();
2538                         } else {
2539                                 preamble.suppressDate(false);
2540                                 if (context.new_layout_allowed &&
2541                                     (newlayout = findLayout(context.textclass,
2542                                                             t.cs(), true))) {
2543                                         // write the layout
2544                                         output_command_layout(os, p, outer,
2545                                                         context, newlayout);
2546                                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2547                                         if (!preamble.titleLayoutFound())
2548                                                 preamble.titleLayoutFound(newlayout->intitle);
2549                                         set<string> const & req = newlayout->requires();
2550                                         set<string>::const_iterator it = req.begin();
2551                                         set<string>::const_iterator en = req.end();
2552                                         for (; it != en; ++it)
2553                                                 preamble.registerAutomaticallyLoadedPackage(*it);
2554                                 } else
2555                                         handle_ert(os,
2556                                                 "\\date{" + p.verbatim_item() + '}',
2557                                                 context);
2558                         }
2559                 }
2560
2561                 // Starred section headings
2562                 // Must attempt to parse "Section*" before "Section".
2563                 else if ((p.next_token().asInput() == "*") &&
2564                          context.new_layout_allowed &&
2565                          (newlayout = findLayout(context.textclass, t.cs() + '*', true))) {
2566                         // write the layout
2567                         p.get_token();
2568                         output_command_layout(os, p, outer, context, newlayout);
2569                         p.skip_spaces();
2570                         if (!preamble.titleLayoutFound())
2571                                 preamble.titleLayoutFound(newlayout->intitle);
2572                         set<string> const & req = newlayout->requires();
2573                         for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
2574                                 preamble.registerAutomaticallyLoadedPackage(*it);
2575                 }
2576
2577                 // Section headings and the like
2578                 else if (context.new_layout_allowed &&
2579                          (newlayout = findLayout(context.textclass, t.cs(), true))) {
2580                         // write the layout
2581                         output_command_layout(os, p, outer, context, newlayout);
2582                         p.skip_spaces();
2583                         if (!preamble.titleLayoutFound())
2584                                 preamble.titleLayoutFound(newlayout->intitle);
2585                         set<string> const & req = newlayout->requires();
2586                         for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
2587                                 preamble.registerAutomaticallyLoadedPackage(*it);
2588                 }
2589
2590                 else if (t.cs() == "caption") {
2591                         p.skip_spaces();
2592                         context.check_layout(os);
2593                         p.skip_spaces();
2594                         begin_inset(os, "Caption\n");
2595                         Context newcontext(true, context.textclass);
2596                         newcontext.font = context.font;
2597                         newcontext.check_layout(os);
2598                         if (p.next_token().cat() != catEscape &&
2599                             p.next_token().character() == '[') {
2600                                 p.get_token(); // eat '['
2601                                 begin_inset(os, "Argument\n");
2602                                 os << "status collapsed\n";
2603                                 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
2604                                 end_inset(os);
2605                                 eat_whitespace(p, os, context, false);
2606                         }
2607                         parse_text(p, os, FLAG_ITEM, outer, context);
2608                         context.check_end_layout(os);
2609                         // We don't need really a new paragraph, but
2610                         // we must make sure that the next item gets a \begin_layout.
2611                         context.new_paragraph(os);
2612                         end_inset(os);
2613                         p.skip_spaces();
2614                         newcontext.check_end_layout(os);
2615                 }
2616
2617                 else if (t.cs() == "subfloat") {
2618                         // the syntax is \subfloat[caption]{content}
2619                         // if it is a table of figure depends on the surrounding float
2620                         bool has_caption = false;
2621                         p.skip_spaces();
2622                         // do nothing if there is no outer float
2623                         if (!float_type.empty()) {
2624                                 context.check_layout(os);
2625                                 p.skip_spaces();
2626                                 begin_inset(os, "Float " + float_type + "\n");
2627                                 os << "wide false"
2628                                    << "\nsideways false"
2629                                    << "\nstatus collapsed\n\n";
2630                                 // test for caption
2631                                 string caption;
2632                                 if (p.next_token().cat() != catEscape &&
2633                                                 p.next_token().character() == '[') {
2634                                                         p.get_token(); // eat '['
2635                                                         caption = parse_text_snippet(p, FLAG_BRACK_LAST, outer, context);
2636                                                         has_caption = true;
2637                                 }
2638                                 // the content
2639                                 parse_text_in_inset(p, os, FLAG_ITEM, outer, context);
2640                                 // the caption comes always as the last
2641                                 if (has_caption) {
2642                                         // we must make sure that the caption gets a \begin_layout
2643                                         os << "\n\\begin_layout Plain Layout";
2644                                         p.skip_spaces();
2645                                         begin_inset(os, "Caption\n");
2646                                         Context newcontext(true, context.textclass);
2647                                         newcontext.font = context.font;
2648                                         newcontext.check_layout(os);
2649                                         os << caption << "\n";
2650                                         newcontext.check_end_layout(os);
2651                                         // We don't need really a new paragraph, but
2652                                         // we must make sure that the next item gets a \begin_layout.
2653                                         //newcontext.new_paragraph(os);
2654                                         end_inset(os);
2655                                         p.skip_spaces();
2656                                 }
2657                                 // We don't need really a new paragraph, but
2658                                 // we must make sure that the next item gets a \begin_layout.
2659                                 if (has_caption)
2660                                         context.new_paragraph(os);
2661                                 end_inset(os);
2662                                 p.skip_spaces();
2663                                 context.check_end_layout(os);
2664                                 // close the layout we opened
2665                                 if (has_caption)
2666                                         os << "\n\\end_layout\n";
2667                         } else {
2668                                 // if the float type is not supported or there is no surrounding float
2669                                 // output it as ERT
2670                                 if (p.hasOpt()) {
2671                                         string opt_arg = convert_command_inset_arg(p.getArg('[', ']'));
2672                                         handle_ert(os, t.asInput() + '[' + opt_arg +
2673                                                "]{" + p.verbatim_item() + '}', context);
2674                                 } else
2675                                         handle_ert(os, t.asInput() + "{" + p.verbatim_item() + '}', context);
2676                         }
2677                 }
2678
2679                 else if (t.cs() == "includegraphics") {
2680                         bool const clip = p.next_token().asInput() == "*";
2681                         if (clip)
2682                                 p.get_token();
2683                         string const arg = p.getArg('[', ']');
2684                         map<string, string> opts;
2685                         vector<string> keys;
2686                         split_map(arg, opts, keys);
2687                         if (clip)
2688                                 opts["clip"] = string();
2689                         string name = normalize_filename(p.verbatim_item());
2690
2691                         string const path = getMasterFilePath();
2692                         // We want to preserve relative / absolute filenames,
2693                         // therefore path is only used for testing
2694                         if (!makeAbsPath(name, path).exists()) {
2695                                 // The file extension is probably missing.
2696                                 // Now try to find it out.
2697                                 string const dvips_name =
2698                                         find_file(name, path,
2699                                                   known_dvips_graphics_formats);
2700                                 string const pdftex_name =
2701                                         find_file(name, path,
2702                                                   known_pdftex_graphics_formats);
2703                                 if (!dvips_name.empty()) {
2704                                         if (!pdftex_name.empty()) {
2705                                                 cerr << "This file contains the "
2706                                                         "latex snippet\n"
2707                                                         "\"\\includegraphics{"
2708                                                      << name << "}\".\n"
2709                                                         "However, files\n\""
2710                                                      << dvips_name << "\" and\n\""
2711                                                      << pdftex_name << "\"\n"
2712                                                         "both exist, so I had to make a "
2713                                                         "choice and took the first one.\n"
2714                                                         "Please move the unwanted one "
2715                                                         "someplace else and try again\n"
2716                                                         "if my choice was wrong."
2717                                                      << endl;
2718                                         }
2719                                         name = dvips_name;
2720                                 } else if (!pdftex_name.empty()) {
2721                                         name = pdftex_name;
2722                                         pdflatex = true;
2723                                 }
2724                         }
2725
2726                         if (makeAbsPath(name, path).exists())
2727                                 fix_relative_filename(name);
2728                         else
2729                                 cerr << "Warning: Could not find graphics file '"
2730                                      << name << "'." << endl;
2731
2732                         context.check_layout(os);
2733                         begin_inset(os, "Graphics ");
2734                         os << "\n\tfilename " << name << '\n';
2735                         if (opts.find("width") != opts.end())
2736                                 os << "\twidth "
2737                                    << translate_len(opts["width"]) << '\n';
2738                         if (opts.find("height") != opts.end())
2739                                 os << "\theight "
2740                                    << translate_len(opts["height"]) << '\n';
2741                         if (opts.find("scale") != opts.end()) {
2742                                 istringstream iss(opts["scale"]);
2743                                 double val;
2744                                 iss >> val;
2745                                 val = val*100;
2746                                 os << "\tscale " << val << '\n';
2747                         }
2748                         if (opts.find("angle") != opts.end()) {
2749                                 os << "\trotateAngle "
2750                                    << opts["angle"] << '\n';
2751                                 vector<string>::const_iterator a =
2752                                         find(keys.begin(), keys.end(), "angle");
2753                                 vector<string>::const_iterator s =
2754                                         find(keys.begin(), keys.end(), "width");
2755                                 if (s == keys.end())
2756                                         s = find(keys.begin(), keys.end(), "height");
2757                                 if (s == keys.end())
2758                                         s = find(keys.begin(), keys.end(), "scale");
2759                                 if (s != keys.end() && distance(s, a) > 0)
2760                                         os << "\tscaleBeforeRotation\n";
2761                         }
2762                         if (opts.find("origin") != opts.end()) {
2763                                 ostringstream ss;
2764                                 string const opt = opts["origin"];
2765                                 if (opt.find('l') != string::npos) ss << "left";
2766                                 if (opt.find('r') != string::npos) ss << "right";
2767                                 if (opt.find('c') != string::npos) ss << "center";
2768                                 if (opt.find('t') != string::npos) ss << "Top";
2769                                 if (opt.find('b') != string::npos) ss << "Bottom";
2770                                 if (opt.find('B') != string::npos) ss << "Baseline";
2771                                 if (!ss.str().empty())
2772                                         os << "\trotateOrigin " << ss.str() << '\n';
2773                                 else
2774                                         cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
2775                         }
2776                         if (opts.find("keepaspectratio") != opts.end())
2777                                 os << "\tkeepAspectRatio\n";
2778                         if (opts.find("clip") != opts.end())
2779                                 os << "\tclip\n";
2780                         if (opts.find("draft") != opts.end())
2781                                 os << "\tdraft\n";
2782                         if (opts.find("bb") != opts.end())
2783                                 os << "\tBoundingBox "
2784                                    << opts["bb"] << '\n';
2785                         int numberOfbbOptions = 0;
2786                         if (opts.find("bbllx") != opts.end())
2787                                 numberOfbbOptions++;
2788                         if (opts.find("bblly") != opts.end())
2789                                 numberOfbbOptions++;
2790                         if (opts.find("bburx") != opts.end())
2791                                 numberOfbbOptions++;
2792                         if (opts.find("bbury") != opts.end())
2793                                 numberOfbbOptions++;
2794                         if (numberOfbbOptions == 4)
2795                                 os << "\tBoundingBox "
2796                                    << opts["bbllx"] << " " << opts["bblly"] << " "
2797                                    << opts["bburx"] << " " << opts["bbury"] << '\n';
2798                         else if (numberOfbbOptions > 0)
2799                                 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
2800                         numberOfbbOptions = 0;
2801                         if (opts.find("natwidth") != opts.end())
2802                                 numberOfbbOptions++;
2803                         if (opts.find("natheight") != opts.end())
2804                                 numberOfbbOptions++;
2805                         if (numberOfbbOptions == 2)
2806                                 os << "\tBoundingBox 0bp 0bp "
2807                                    << opts["natwidth"] << " " << opts["natheight"] << '\n';
2808                         else if (numberOfbbOptions > 0)
2809                                 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
2810                         ostringstream special;
2811                         if (opts.find("hiresbb") != opts.end())
2812                                 special << "hiresbb,";
2813                         if (opts.find("trim") != opts.end())
2814                                 special << "trim,";
2815                         if (opts.find("viewport") != opts.end())
2816                                 special << "viewport=" << opts["viewport"] << ',';
2817                         if (opts.find("totalheight") != opts.end())
2818                                 special << "totalheight=" << opts["totalheight"] << ',';
2819                         if (opts.find("type") != opts.end())
2820                                 special << "type=" << opts["type"] << ',';
2821                         if (opts.find("ext") != opts.end())
2822                                 special << "ext=" << opts["ext"] << ',';
2823                         if (opts.find("read") != opts.end())
2824                                 special << "read=" << opts["read"] << ',';
2825                         if (opts.find("command") != opts.end())
2826                                 special << "command=" << opts["command"] << ',';
2827                         string s_special = special.str();
2828                         if (!s_special.empty()) {
2829                                 // We had special arguments. Remove the trailing ','.
2830                                 os << "\tspecial " << s_special.substr(0, s_special.size() - 1) << '\n';
2831                         }
2832                         // TODO: Handle the unknown settings better.
2833                         // Warn about invalid options.
2834                         // Check whether some option was given twice.
2835                         end_inset(os);
2836                         preamble.registerAutomaticallyLoadedPackage("graphicx");
2837                 }
2838
2839                 else if (t.cs() == "footnote" ||
2840                          (t.cs() == "thanks" && context.layout->intitle)) {
2841                         p.skip_spaces();
2842                         context.check_layout(os);
2843                         begin_inset(os, "Foot\n");
2844                         os << "status collapsed\n\n";
2845                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
2846                         end_inset(os);
2847                 }
2848
2849                 else if (t.cs() == "marginpar") {
2850                         p.skip_spaces();
2851                         context.check_layout(os);
2852                         begin_inset(os, "Marginal\n");
2853                         os << "status collapsed\n\n";
2854                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
2855                         end_inset(os);
2856                 }
2857
2858                 else if (t.cs() == "lstinline") {
2859                         p.skip_spaces();
2860                         parse_listings(p, os, context, true);
2861                 }
2862
2863                 else if (t.cs() == "ensuremath") {
2864                         p.skip_spaces();
2865                         context.check_layout(os);
2866                         string const s = p.verbatim_item();
2867                         //FIXME: this never triggers in UTF8
2868                         if (s == "\xb1" || s == "\xb3" || s == "\xb2" || s == "\xb5")
2869                                 os << s;
2870                         else
2871                                 handle_ert(os, "\\ensuremath{" + s + "}",
2872                                            context);
2873                 }
2874
2875                 else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
2876                         if (preamble.titleLayoutFound()) {
2877                                 // swallow this
2878                                 skip_spaces_braces(p);
2879                         } else
2880                                 handle_ert(os, t.asInput(), context);
2881                 }
2882
2883                 else if (t.cs() == "tableofcontents" || t.cs() == "lstlistoflistings") {
2884                         context.check_layout(os);
2885                         begin_command_inset(os, "toc", t.cs());
2886                         end_inset(os);
2887                         skip_spaces_braces(p);
2888                         if (t.cs() == "lstlistoflistings")
2889                                 preamble.registerAutomaticallyLoadedPackage("listings");
2890                 }
2891
2892                 else if (t.cs() == "listoffigures") {
2893                         context.check_layout(os);
2894                         begin_inset(os, "FloatList figure\n");
2895                         end_inset(os);
2896                         skip_spaces_braces(p);
2897                 }
2898
2899                 else if (t.cs() == "listoftables") {
2900                         context.check_layout(os);
2901                         begin_inset(os, "FloatList table\n");
2902                         end_inset(os);
2903                         skip_spaces_braces(p);
2904                 }
2905
2906                 else if (t.cs() == "listof") {
2907                         p.skip_spaces(true);
2908                         string const name = p.get_token().cs();
2909                         if (context.textclass.floats().typeExist(name)) {
2910                                 context.check_layout(os);
2911                                 begin_inset(os, "FloatList ");
2912                                 os << name << "\n";
2913                                 end_inset(os);
2914                                 p.get_token(); // swallow second arg
2915                         } else
2916                                 handle_ert(os, "\\listof{" + name + "}", context);
2917                 }
2918
2919                 else if ((where = is_known(t.cs(), known_text_font_families)))
2920                         parse_text_attributes(p, os, FLAG_ITEM, outer,
2921                                 context, "\\family", context.font.family,
2922                                 known_coded_font_families[where - known_text_font_families]);
2923
2924                 else if ((where = is_known(t.cs(), known_text_font_series)))
2925                         parse_text_attributes(p, os, FLAG_ITEM, outer,
2926                                 context, "\\series", context.font.series,
2927                                 known_coded_font_series[where - known_text_font_series]);
2928
2929                 else if ((where = is_known(t.cs(), known_text_font_shapes)))
2930                         parse_text_attributes(p, os, FLAG_ITEM, outer,
2931                                 context, "\\shape", context.font.shape,
2932                                 known_coded_font_shapes[where - known_text_font_shapes]);
2933
2934                 else if (t.cs() == "textnormal" || t.cs() == "normalfont") {
2935                         context.check_layout(os);
2936                         TeXFont oldFont = context.font;
2937                         context.font.init();
2938                         context.font.size = oldFont.size;
2939                         os << "\n\\family " << context.font.family << "\n";
2940                         os << "\n\\series " << context.font.series << "\n";
2941                         os << "\n\\shape " << context.font.shape << "\n";
2942                         if (t.cs() == "textnormal") {
2943                                 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2944                                 output_font_change(os, context.font, oldFont);
2945                                 context.font = oldFont;
2946                         } else
2947                                 eat_whitespace(p, os, context, false);
2948                 }
2949
2950                 else if (t.cs() == "textcolor") {
2951                         // scheme is \textcolor{color name}{text}
2952                         string const color = p.verbatim_item();
2953                         // we only support the predefined colors of the color package
2954                         if (color == "black" || color == "blue" || color == "cyan"
2955                                 || color == "green" || color == "magenta" || color == "red"
2956                                 || color == "white" || color == "yellow") {
2957                                         context.check_layout(os);
2958                                         os << "\n\\color " << color << "\n";
2959                                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2960                                         context.check_layout(os);
2961                                         os << "\n\\color inherit\n";
2962                                         preamble.registerAutomaticallyLoadedPackage("color");
2963                         } else
2964                                 // for custom defined colors
2965                                 handle_ert(os, t.asInput() + "{" + color + "}", context);
2966                 }
2967
2968                 else if (t.cs() == "underbar" || t.cs() == "uline") {
2969                         // \underbar is not 100% correct (LyX outputs \uline
2970                         // of ulem.sty). The difference is that \ulem allows
2971                         // line breaks, and \underbar does not.
2972                         // Do NOT handle \underline.
2973                         // \underbar cuts through y, g, q, p etc.,
2974                         // \underline does not.
2975                         context.check_layout(os);
2976                         os << "\n\\bar under\n";
2977                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2978                         context.check_layout(os);
2979                         os << "\n\\bar default\n";
2980                         preamble.registerAutomaticallyLoadedPackage("ulem");
2981                 }
2982
2983                 else if (t.cs() == "sout") {
2984                         context.check_layout(os);
2985                         os << "\n\\strikeout on\n";
2986                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2987                         context.check_layout(os);
2988                         os << "\n\\strikeout default\n";
2989                         preamble.registerAutomaticallyLoadedPackage("ulem");
2990                 }
2991
2992                 else if (t.cs() == "uuline" || t.cs() == "uwave" ||
2993                          t.cs() == "emph" || t.cs() == "noun") {
2994                         context.check_layout(os);
2995                         os << "\n\\" << t.cs() << " on\n";
2996                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2997                         context.check_layout(os);
2998                         os << "\n\\" << t.cs() << " default\n";
2999                         if (t.cs() == "uuline" || t.cs() == "uwave")
3000                                 preamble.registerAutomaticallyLoadedPackage("ulem");
3001                 }
3002
3003                 else if (t.cs() == "lyxadded" || t.cs() == "lyxdeleted") {
3004                         context.check_layout(os);
3005                         string name = p.getArg('{', '}');
3006                         string localtime = p.getArg('{', '}');
3007                         preamble.registerAuthor(name);
3008                         Author const & author = preamble.getAuthor(name);
3009                         // from_ctime() will fail if LyX decides to output the
3010                         // time in the text language. It might also use a wrong
3011                         // time zone (if the original LyX document was exported
3012                         // with a different time zone).
3013                         time_t ptime = from_ctime(localtime);
3014                         if (ptime == static_cast<time_t>(-1)) {
3015                                 cerr << "Warning: Could not parse time `" << localtime
3016                                      << "´ for change tracking, using current time instead.\n";
3017                                 ptime = current_time();
3018                         }
3019                         if (t.cs() == "lyxadded")
3020                                 os << "\n\\change_inserted ";
3021                         else
3022                                 os << "\n\\change_deleted ";
3023                         os << author.bufferId() << ' ' << ptime << '\n';
3024                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
3025                         bool dvipost    = LaTeXPackages::isAvailable("dvipost");
3026                         bool xcolorulem = LaTeXPackages::isAvailable("ulem") &&
3027                                           LaTeXPackages::isAvailable("xcolor");
3028                         // No need to test for luatex, since luatex comes in
3029                         // two flavours (dvi and pdf), like latex, and those
3030                         // are detected by pdflatex.
3031                         if (pdflatex || xetex) {
3032                                 if (xcolorulem) {
3033                                         preamble.registerAutomaticallyLoadedPackage("ulem");
3034                                         preamble.registerAutomaticallyLoadedPackage("xcolor");
3035                                         preamble.registerAutomaticallyLoadedPackage("pdfcolmk");
3036                                 }
3037                         } else {
3038                                 if (dvipost) {
3039                                         preamble.registerAutomaticallyLoadedPackage("dvipost");
3040                                 } else if (xcolorulem) {
3041                                         preamble.registerAutomaticallyLoadedPackage("ulem");
3042                                         preamble.registerAutomaticallyLoadedPackage("xcolor");
3043                                 }
3044                         }
3045                 }
3046
3047                 else if (t.cs() == "phantom" || t.cs() == "hphantom" ||
3048                              t.cs() == "vphantom") {
3049                         context.check_layout(os);
3050                         if (t.cs() == "phantom")
3051                                 begin_inset(os, "Phantom Phantom\n");
3052                         if (t.cs() == "hphantom")
3053                                 begin_inset(os, "Phantom HPhantom\n");
3054                         if (t.cs() == "vphantom")
3055                                 begin_inset(os, "Phantom VPhantom\n");
3056                         os << "status open\n";
3057                         parse_text_in_inset(p, os, FLAG_ITEM, outer, context,
3058                                             "Phantom");
3059                         end_inset(os);
3060                 }
3061
3062                 else if (t.cs() == "href") {
3063                         context.check_layout(os);
3064                         string target = p.getArg('{', '}');
3065                         string name = p.getArg('{', '}');
3066                         string type;
3067                         size_t i = target.find(':');
3068                         if (i != string::npos) {
3069                                 type = target.substr(0, i + 1);
3070                                 if (type == "mailto:" || type == "file:")
3071                                         target = target.substr(i + 1);
3072                                 // handle the case that name is equal to target, except of "http://"
3073                                 else if (target.substr(i + 3) == name && type == "http:")
3074                                         target = name;
3075                         }
3076                         begin_command_inset(os, "href", "href");
3077                         if (name != target)
3078                                 os << "name \"" << name << "\"\n";
3079                         os << "target \"" << target << "\"\n";
3080                         if (type == "mailto:" || type == "file:")
3081                                 os << "type \"" << type << "\"\n";
3082                         end_inset(os);
3083                         skip_spaces_braces(p);
3084                 }
3085
3086                 else if (t.cs() == "lyxline") {
3087                         // swallow size argument (it is not used anyway)
3088                         p.getArg('{', '}');
3089                         if (!context.atParagraphStart()) {
3090                                 // so our line is in the middle of a paragraph
3091                                 // we need to add a new line, lest this line
3092                                 // follow the other content on that line and
3093                                 // run off the side of the page
3094                                 // FIXME: This may create an empty paragraph,
3095                                 //        but without that it would not be
3096                                 //        possible to set noindent below.
3097                                 //        Fortunately LaTeX does not care
3098                                 //        about the empty paragraph.
3099                                 context.new_paragraph(os);
3100                         }
3101                         if (preamble.indentParagraphs()) {
3102                                 // we need to unindent, lest the line be too long
3103                                 context.add_par_extra_stuff("\\noindent\n");
3104                         }
3105                         context.check_layout(os);
3106                         begin_command_inset(os, "line", "rule");
3107                         os << "offset \"0.5ex\"\n"
3108                               "width \"100line%\"\n"
3109                               "height \"1pt\"\n";
3110                         end_inset(os);
3111                 }
3112
3113                 else if (t.cs() == "rule") {
3114                         string const offset = (p.hasOpt() ? p.getArg('[', ']') : string());
3115                         string const width = p.getArg('{', '}');
3116                         string const thickness = p.getArg('{', '}');
3117                         context.check_layout(os);
3118                         begin_command_inset(os, "line", "rule");
3119                         if (!offset.empty())
3120                                 os << "offset \"" << translate_len(offset) << "\"\n";
3121                         os << "width \"" << translate_len(width) << "\"\n"
3122                                   "height \"" << translate_len(thickness) << "\"\n";
3123                         end_inset(os);
3124                 }
3125
3126                 else if (is_known(t.cs(), known_phrases) ||
3127                          (t.cs() == "protect" &&
3128                           p.next_token().cat() == catEscape &&
3129                           is_known(p.next_token().cs(), known_phrases))) {
3130                         // LyX sometimes puts a \protect in front, so we have to ignore it
3131                         // FIXME: This needs to be changed when bug 4752 is fixed.
3132                         where = is_known(
3133                                 t.cs() == "protect" ? p.get_token().cs() : t.cs(),
3134                                 known_phrases);
3135                         context.check_layout(os);
3136                         os << known_coded_phrases[where - known_phrases];
3137                         skip_spaces_braces(p);
3138                 }
3139
3140                 else if ((where = is_known(t.cs(), known_ref_commands))) {
3141                         string const opt = p.getOpt();
3142                         if (opt.empty()) {
3143                                 context.check_layout(os);
3144                                 begin_command_inset(os, "ref",
3145                                         known_coded_ref_commands[where - known_ref_commands]);
3146                                 os << "reference \""
3147                                    << convert_command_inset_arg(p.verbatim_item())
3148                                    << "\"\n";
3149                                 end_inset(os);
3150                                 if (t.cs() == "vref" || t.cs() == "vpageref")
3151                                         preamble.registerAutomaticallyLoadedPackage("varioref");
3152
3153                         } else {
3154                                 // LyX does not support optional arguments of ref commands
3155                                 handle_ert(os, t.asInput() + '[' + opt + "]{" +
3156                                                p.verbatim_item() + "}", context);
3157                         }
3158                 }
3159
3160                 else if (use_natbib &&
3161                          is_known(t.cs(), known_natbib_commands) &&
3162                          ((t.cs() != "citefullauthor" &&
3163                            t.cs() != "citeyear" &&
3164                            t.cs() != "citeyearpar") ||
3165                           p.next_token().asInput() != "*")) {
3166                         context.check_layout(os);
3167                         string command = t.cs();
3168                         if (p.next_token().asInput() == "*") {
3169                                 command += '*';
3170                                 p.get_token();
3171                         }
3172                         if (command == "citefullauthor")
3173                                 // alternative name for "\\citeauthor*"
3174                                 command = "citeauthor*";
3175
3176                         // text before the citation
3177                         string before;
3178                         // text after the citation
3179                         string after;
3180                         get_cite_arguments(p, true, before, after);
3181
3182                         if (command == "cite") {
3183                                 // \cite without optional argument means
3184                                 // \citet, \cite with at least one optional
3185                                 // argument means \citep.
3186                                 if (before.empty() && after.empty())
3187                                         command = "citet";
3188                                 else
3189                                         command = "citep";
3190                         }
3191                         if (before.empty() && after == "[]")
3192                                 // avoid \citet[]{a}
3193                                 after.erase();
3194                         else if (before == "[]" && after == "[]") {
3195                                 // avoid \citet[][]{a}
3196                                 before.erase();
3197                                 after.erase();
3198                         }
3199                         // remove the brackets around after and before
3200                         if (!after.empty()) {
3201                                 after.erase(0, 1);
3202                                 after.erase(after.length() - 1, 1);
3203                                 after = convert_command_inset_arg(after);
3204                         }
3205                         if (!before.empty()) {
3206                                 before.erase(0, 1);
3207                                 before.erase(before.length() - 1, 1);
3208                                 before = convert_command_inset_arg(before);
3209                         }
3210                         begin_command_inset(os, "citation", command);
3211                         os << "after " << '"' << after << '"' << "\n";
3212                         os << "before " << '"' << before << '"' << "\n";
3213                         os << "key \""
3214                            << convert_command_inset_arg(p.verbatim_item())
3215                            << "\"\n";
3216                         end_inset(os);
3217                 }
3218
3219                 else if (use_jurabib &&
3220                          is_known(t.cs(), known_jurabib_commands) &&
3221                          (t.cs() == "cite" || p.next_token().asInput() != "*")) {
3222                         context.check_layout(os);
3223                         string command = t.cs();
3224                         if (p.next_token().asInput() == "*") {
3225                                 command += '*';
3226                                 p.get_token();
3227                         }
3228                         char argumentOrder = '\0';
3229                         vector<string> const options =
3230                                 preamble.getPackageOptions("jurabib");
3231                         if (find(options.begin(), options.end(),
3232                                       "natbiborder") != options.end())
3233                                 argumentOrder = 'n';
3234                         else if (find(options.begin(), options.end(),
3235                                            "jurabiborder") != options.end())
3236                                 argumentOrder = 'j';
3237
3238                         // text before the citation
3239                         string before;
3240                         // text after the citation
3241                         string after;
3242                         get_cite_arguments(p, argumentOrder != 'j', before, after);
3243
3244                         string const citation = p.verbatim_item();
3245                         if (!before.empty() && argumentOrder == '\0') {
3246                                 cerr << "Warning: Assuming argument order "
3247                                         "of jurabib version 0.6 for\n'"
3248                                      << command << before << after << '{'
3249                                      << citation << "}'.\n"
3250                                         "Add 'jurabiborder' to the jurabib "
3251                                         "package options if you used an\n"
3252                                         "earlier jurabib version." << endl;
3253                         }
3254                         if (!after.empty()) {
3255                                 after.erase(0, 1);
3256                                 after.erase(after.length() - 1, 1);
3257                         }
3258                         if (!before.empty()) {
3259                                 before.erase(0, 1);
3260                                 before.erase(before.length() - 1, 1);
3261                         }
3262                         begin_command_inset(os, "citation", command);
3263                         os << "after " << '"' << after << '"' << "\n";
3264                         os << "before " << '"' << before << '"' << "\n";
3265                         os << "key " << '"' << citation << '"' << "\n";
3266                         end_inset(os);
3267                 }
3268
3269                 else if (t.cs() == "cite"
3270                         || t.cs() == "nocite") {
3271                         context.check_layout(os);
3272                         string after = convert_command_inset_arg(p.getArg('[', ']'));
3273                         string key = convert_command_inset_arg(p.verbatim_item());
3274                         // store the case that it is "\nocite{*}" to use it later for
3275                         // the BibTeX inset
3276                         if (key != "*") {
3277                                 begin_command_inset(os, "citation", t.cs());
3278                                 os << "after " << '"' << after << '"' << "\n";
3279                                 os << "key " << '"' << key << '"' << "\n";
3280                                 end_inset(os);
3281                         } else if (t.cs() == "nocite")
3282                                 btprint = key;
3283                 }
3284
3285                 else if (t.cs() == "index" ||
3286                          (t.cs() == "sindex" && preamble.use_indices() == "true")) {
3287                         context.check_layout(os);
3288                         string const arg = (t.cs() == "sindex" && p.hasOpt()) ?
3289                                 p.getArg('[', ']') : "";
3290                         string const kind = arg.empty() ? "idx" : arg;
3291                         begin_inset(os, "Index ");
3292                         os << kind << "\nstatus collapsed\n";
3293                         parse_text_in_inset(p, os, FLAG_ITEM, false, context, "Index");
3294                         end_inset(os);
3295                         if (kind != "idx")
3296                                 preamble.registerAutomaticallyLoadedPackage("splitidx");
3297                 }
3298
3299                 else if (t.cs() == "nomenclature") {
3300                         context.check_layout(os);
3301                         begin_command_inset(os, "nomenclature", "nomenclature");
3302                         string prefix = convert_command_inset_arg(p.getArg('[', ']'));
3303                         if (!prefix.empty())
3304                                 os << "prefix " << '"' << prefix << '"' << "\n";
3305                         os << "symbol " << '"'
3306                            << convert_command_inset_arg(p.verbatim_item());
3307                         os << "\"\ndescription \""
3308                            << convert_command_inset_arg(p.verbatim_item())
3309                            << "\"\n";
3310                         end_inset(os);
3311                         preamble.registerAutomaticallyLoadedPackage("nomencl");
3312                 }
3313
3314                 else if (t.cs() == "label") {
3315                         context.check_layout(os);
3316                         begin_command_inset(os, "label", "label");
3317                         os << "name \""
3318                            << convert_command_inset_arg(p.verbatim_item())
3319                            << "\"\n";
3320                         end_inset(os);
3321                 }
3322
3323                 else if (t.cs() == "printindex") {
3324                         context.check_layout(os);
3325                         begin_command_inset(os, "index_print", "printindex");
3326                         os << "type \"idx\"\n";
3327                         end_inset(os);
3328                         skip_spaces_braces(p);
3329                         preamble.registerAutomaticallyLoadedPackage("makeidx");
3330                         if (preamble.use_indices() == "true")
3331                                 preamble.registerAutomaticallyLoadedPackage("splitidx");
3332                 }
3333
3334                 else if (t.cs() == "printnomenclature") {
3335                         string width = "";
3336                         string width_type = "";
3337                         context.check_layout(os);
3338                         begin_command_inset(os, "nomencl_print", "printnomenclature");
3339                         // case of a custom width
3340                         if (p.hasOpt()) {
3341                                 width = p.getArg('[', ']');
3342                                 width = translate_len(width);
3343                                 width_type = "custom";
3344                         }
3345                         // case of no custom width
3346                         // the case of no custom width but the width set
3347                         // via \settowidth{\nomlabelwidth}{***} cannot be supported
3348                         // because the user could have set anything, not only the width
3349                         // of the longest label (which would be width_type = "auto")
3350                         string label = convert_command_inset_arg(p.getArg('{', '}'));
3351                         if (label.empty() && width_type.empty())
3352                                 width_type = "none";
3353                         os << "set_width \"" << width_type << "\"\n";
3354                         if (width_type == "custom")
3355                                 os << "width \"" << width << '\"';
3356                         end_inset(os);
3357                         skip_spaces_braces(p);
3358                         preamble.registerAutomaticallyLoadedPackage("nomencl");
3359                 }
3360
3361                 else if ((t.cs() == "textsuperscript" || t.cs() == "textsubscript")) {
3362                         context.check_layout(os);
3363                         begin_inset(os, "script ");
3364                         os << t.cs().substr(4) << '\n';
3365                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
3366                         end_inset(os);
3367                         if (t.cs() == "textsubscript")
3368                                 preamble.registerAutomaticallyLoadedPackage("subscript");
3369                 }
3370
3371                 else if ((where = is_known(t.cs(), known_quotes))) {
3372                         context.check_layout(os);
3373                         begin_inset(os, "Quotes ");
3374                         os << known_coded_quotes[where - known_quotes];
3375                         end_inset(os);
3376                         // LyX adds {} after the quote, so we have to eat
3377                         // spaces here if there are any before a possible
3378                         // {} pair.
3379                         eat_whitespace(p, os, context, false);
3380                         skip_braces(p);
3381                 }
3382
3383                 else if ((where = is_known(t.cs(), known_sizes)) &&
3384                          context.new_layout_allowed) {
3385                         context.check_layout(os);
3386                         TeXFont const oldFont = context.font;
3387                         context.font.size = known_coded_sizes[where - known_sizes];
3388                         output_font_change(os, oldFont, context.font);
3389                         eat_whitespace(p, os, context, false);
3390                 }
3391
3392                 else if ((where = is_known(t.cs(), known_font_families)) &&
3393                          context.new_layout_allowed) {
3394                         context.check_layout(os);
3395                         TeXFont const oldFont = context.font;
3396                         context.font.family =
3397                                 known_coded_font_families[where - known_font_families];
3398                         output_font_change(os, oldFont, context.font);
3399                         eat_whitespace(p, os, context, false);
3400                 }
3401
3402                 else if ((where = is_known(t.cs(), known_font_series)) &&
3403                          context.new_layout_allowed) {
3404                         context.check_layout(os);
3405                         TeXFont const oldFont = context.font;
3406                         context.font.series =
3407                                 known_coded_font_series[where - known_font_series];
3408                         output_font_change(os, oldFont, context.font);
3409                         eat_whitespace(p, os, context, false);
3410                 }
3411
3412                 else if ((where = is_known(t.cs(), known_font_shapes)) &&
3413                          context.new_layout_allowed) {
3414                         context.check_layout(os);
3415                         TeXFont const oldFont = context.font;
3416                         context.font.shape =
3417                                 known_coded_font_shapes[where - known_font_shapes];
3418                         output_font_change(os, oldFont, context.font);
3419                         eat_whitespace(p, os, context, false);
3420                 }
3421                 else if ((where = is_known(t.cs(), known_old_font_families)) &&
3422                          context.new_layout_allowed) {
3423                         context.check_layout(os);
3424                         TeXFont const oldFont = context.font;
3425                         context.font.init();
3426                         context.font.size = oldFont.size;
3427                         context.font.family =
3428                                 known_coded_font_families[where - known_old_font_families];
3429                         output_font_change(os, oldFont, context.font);
3430                         eat_whitespace(p, os, context, false);
3431                 }
3432
3433                 else if ((where = is_known(t.cs(), known_old_font_series)) &&
3434                          context.new_layout_allowed) {
3435                         context.check_layout(os);
3436                         TeXFont const oldFont = context.font;
3437                         context.font.init();
3438                         context.font.size = oldFont.size;
3439                         context.font.series =
3440                                 known_coded_font_series[where - known_old_font_series];
3441                         output_font_change(os, oldFont, context.font);
3442                         eat_whitespace(p, os, context, false);
3443                 }
3444
3445                 else if ((where = is_known(t.cs(), known_old_font_shapes)) &&
3446                          context.new_layout_allowed) {
3447                         context.check_layout(os);
3448                         TeXFont const oldFont = context.font;
3449                         context.font.init();
3450                         context.font.size = oldFont.size;
3451                         context.font.shape =
3452                                 known_coded_font_shapes[where - known_old_font_shapes];
3453                         output_font_change(os, oldFont, context.font);
3454                         eat_whitespace(p, os, context, false);
3455                 }
3456
3457                 else if (t.cs() == "selectlanguage") {
3458                         context.check_layout(os);
3459                         // save the language for the case that a
3460                         // \foreignlanguage is used
3461                         context.font.language = babel2lyx(p.verbatim_item());
3462                         os << "\n\\lang " << context.font.language << "\n";
3463                 }
3464
3465                 else if (t.cs() == "foreignlanguage") {
3466                         string const lang = babel2lyx(p.verbatim_item());
3467                         parse_text_attributes(p, os, FLAG_ITEM, outer,
3468                                               context, "\\lang",
3469                                               context.font.language, lang);
3470                 }
3471                 
3472                 else if (is_known(t.cs().substr(4, string::npos), polyglossia_languages)) {
3473                         // scheme is \textLANGUAGE{text} where LANGUAGE is in polyglossia_languages[]
3474                         string const lang = polyglossia2lyx(t.cs().substr(4, string::npos));
3475                         parse_text_attributes(p, os, FLAG_ITEM, outer,
3476                                               context, "\\lang",
3477                                               context.font.language, lang);
3478                 }
3479
3480                 else if (t.cs() == "inputencoding") {
3481                         // nothing to write here
3482                         string const enc = subst(p.verbatim_item(), "\n", " ");
3483                         p.setEncoding(enc);
3484                 }
3485
3486                 else if ((where = is_known(t.cs(), known_special_chars))) {
3487                         context.check_layout(os);
3488                         os << "\\SpecialChar \\"
3489                            << known_coded_special_chars[where - known_special_chars]
3490                            << '\n';
3491                         skip_spaces_braces(p);
3492                 }
3493
3494                 else if (t.cs() == "nobreakdash" && p.next_token().asInput() == "-") {
3495                         context.check_layout(os);
3496                         os << "\\SpecialChar \\nobreakdash-\n";
3497                         p.get_token();
3498                 }
3499
3500                 else if (t.cs() == "textquotedbl") {
3501                         context.check_layout(os);
3502                         os << "\"";
3503                         skip_braces(p);
3504                 }
3505
3506                 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
3507                         context.check_layout(os);
3508                         os << "\\SpecialChar \\@.\n";
3509                         p.get_token();
3510                 }
3511
3512                 else if (t.cs() == "-") {
3513                         context.check_layout(os);
3514                         os << "\\SpecialChar \\-\n";
3515                 }
3516
3517                 else if (t.cs() == "textasciitilde") {
3518                         context.check_layout(os);
3519                         os << '~';
3520                         skip_spaces_braces(p);
3521                 }
3522
3523                 else if (t.cs() == "textasciicircum") {
3524                         context.check_layout(os);
3525                         os << '^';
3526                         skip_spaces_braces(p);
3527                 }
3528
3529                 else if (t.cs() == "textbackslash") {
3530                         context.check_layout(os);
3531                         os << "\n\\backslash\n";
3532                         skip_spaces_braces(p);
3533                 }
3534
3535                 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
3536                             || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
3537                             || t.cs() == "%") {
3538                         context.check_layout(os);
3539                         os << t.cs();
3540                 }
3541
3542                 else if (t.cs() == "char") {
3543                         context.check_layout(os);
3544                         if (p.next_token().character() == '`') {
3545                                 p.get_token();
3546                                 if (p.next_token().cs() == "\"") {
3547                                         p.get_token();
3548                                         os << '"';
3549                                         skip_braces(p);
3550                                 } else {
3551                                         handle_ert(os, "\\char`", context);
3552                                 }
3553                         } else {
3554                                 handle_ert(os, "\\char", context);
3555                         }
3556                 }
3557
3558                 else if (t.cs() == "verb") {
3559                         context.check_layout(os);
3560                         char const delimiter = p.next_token().character();
3561                         string const arg = p.getArg(delimiter, delimiter);
3562                         ostringstream oss;
3563                         oss << "\\verb" << delimiter << arg << delimiter;
3564                         handle_ert(os, oss.str(), context);
3565                 }
3566
3567                 // Problem: \= creates a tabstop inside the tabbing environment
3568                 // and else an accent. In the latter case we really would want
3569                 // \={o} instead of \= o.
3570                 else if (t.cs() == "=" && (flags & FLAG_TABBING))
3571                         handle_ert(os, t.asInput(), context);
3572
3573                 // accents (see Table 6 in Comprehensive LaTeX Symbol List)
3574                 else if (t.cs().size() == 1
3575                          && contains("\"'.=^`bcdHkrtuv~", t.cs())) {
3576                         context.check_layout(os);
3577                         // try to see whether the string is in unicodesymbols
3578                         bool termination;
3579                         docstring rem;
3580                         string command = t.asInput() + "{"
3581                                 + trimSpaceAndEol(p.verbatim_item())
3582                                 + "}";
3583                         set<string> req;
3584                         docstring s = encodings.fromLaTeXCommand(from_utf8(command),
3585                                 Encodings::TEXT_CMD | Encodings::MATH_CMD,
3586                                 termination, rem, &req);
3587                         if (!s.empty()) {
3588                                 if (!rem.empty())
3589                                         cerr << "When parsing " << command
3590                                              << ", result is " << to_utf8(s)
3591                                              << "+" << to_utf8(rem) << endl;
3592                                 os << to_utf8(s);
3593                                 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
3594                                         preamble.registerAutomaticallyLoadedPackage(*it);
3595                         } else
3596                                 // we did not find a non-ert version
3597                                 handle_ert(os, command, context);
3598                 }
3599
3600                 else if (t.cs() == "\\") {
3601                         context.check_layout(os);
3602                         if (p.hasOpt())
3603                                 handle_ert(os, "\\\\" + p.getOpt(), context);
3604                         else if (p.next_token().asInput() == "*") {
3605                                 p.get_token();
3606                                 // getOpt() eats the following space if there
3607                                 // is no optional argument, but that is OK
3608                                 // here since it has no effect in the output.
3609                                 handle_ert(os, "\\\\*" + p.getOpt(), context);
3610                         }
3611                         else {
3612                                 begin_inset(os, "Newline newline");
3613                                 end_inset(os);
3614                         }
3615                 }
3616
3617                 else if (t.cs() == "newline" ||
3618                          (t.cs() == "linebreak" && !p.hasOpt())) {
3619                         context.check_layout(os);
3620                         begin_inset(os, "Newline ");
3621                         os << t.cs();
3622                         end_inset(os);
3623                         skip_spaces_braces(p);
3624                 }
3625
3626                 else if (t.cs() == "input" || t.cs() == "include"
3627                          || t.cs() == "verbatiminput") {
3628                         string name = t.cs();
3629                         if (t.cs() == "verbatiminput"
3630                             && p.next_token().asInput() == "*")
3631                                 name += p.get_token().asInput();
3632                         context.check_layout(os);
3633                         string filename(normalize_filename(p.getArg('{', '}')));
3634                         string const path = getMasterFilePath();
3635                         // We want to preserve relative / absolute filenames,
3636                         // therefore path is only used for testing
3637                         if ((t.cs() == "include" || t.cs() == "input") &&
3638                             !makeAbsPath(filename, path).exists()) {
3639                                 // The file extension is probably missing.
3640                                 // Now try to find it out.
3641                                 string const tex_name =
3642                                         find_file(filename, path,
3643                                                   known_tex_extensions);
3644                                 if (!tex_name.empty())
3645                                         filename = tex_name;
3646                         }
3647                         bool external = false;
3648                         string outname;
3649                         if (makeAbsPath(filename, path).exists()) {
3650                                 string const abstexname =
3651                                         makeAbsPath(filename, path).absFileName();
3652                                 string const abslyxname =
3653                                         changeExtension(abstexname, ".lyx");
3654                                 string const absfigname =
3655                                         changeExtension(abstexname, ".fig");
3656                                 fix_relative_filename(filename);
3657                                 string const lyxname =
3658                                         changeExtension(filename, ".lyx");
3659                                 bool xfig = false;
3660                                 external = FileName(absfigname).exists();
3661                                 if (t.cs() == "input") {
3662                                         string const ext = getExtension(abstexname);
3663
3664                                         // Combined PS/LaTeX:
3665                                         // x.eps, x.pstex_t (old xfig)
3666                                         // x.pstex, x.pstex_t (new xfig, e.g. 3.2.5)
3667                                         FileName const absepsname(
3668                                                 changeExtension(abstexname, ".eps"));
3669                                         FileName const abspstexname(
3670                                                 changeExtension(abstexname, ".pstex"));
3671                                         bool const xfigeps =
3672                                                 (absepsname.exists() ||
3673                                                  abspstexname.exists()) &&
3674                                                 ext == "pstex_t";
3675
3676                                         // Combined PDF/LaTeX:
3677                                         // x.pdf, x.pdftex_t (old xfig)
3678                                         // x.pdf, x.pdf_t (new xfig, e.g. 3.2.5)
3679                                         FileName const abspdfname(
3680                                                 changeExtension(abstexname, ".pdf"));
3681                                         bool const xfigpdf =
3682                                                 abspdfname.exists() &&
3683                                                 (ext == "pdftex_t" || ext == "pdf_t");
3684                                         if (xfigpdf)
3685                                                 pdflatex = true;
3686
3687                                         // Combined PS/PDF/LaTeX:
3688                                         // x_pspdftex.eps, x_pspdftex.pdf, x.pspdftex
3689                                         string const absbase2(
3690                                                 removeExtension(abstexname) + "_pspdftex");
3691                                         FileName const abseps2name(
3692                                                 addExtension(absbase2, ".eps"));
3693                                         FileName const abspdf2name(
3694                                                 addExtension(absbase2, ".pdf"));
3695                                         bool const xfigboth =
3696                                                 abspdf2name.exists() &&
3697                                                 abseps2name.exists() && ext == "pspdftex";
3698
3699                                         xfig = xfigpdf || xfigeps || xfigboth;
3700                                         external = external && xfig;
3701                                 }
3702                                 if (external) {
3703                                         outname = changeExtension(filename, ".fig");
3704                                 } else if (xfig) {
3705                                         // Don't try to convert, the result
3706                                         // would be full of ERT.
3707                                         outname = filename;
3708                                 } else if (t.cs() != "verbatiminput" &&
3709                                     tex2lyx(abstexname, FileName(abslyxname),
3710                                             p.getEncoding())) {
3711                                         outname = lyxname;
3712                                 } else {
3713                                         outname = filename;
3714                                 }
3715                         } else {
3716                                 cerr << "Warning: Could not find included file '"
3717                                      << filename << "'." << endl;
3718                                 outname = filename;
3719                         }
3720                         if (external) {
3721                                 begin_inset(os, "External\n");
3722                                 os << "\ttemplate XFig\n"
3723                                    << "\tfilename " << outname << '\n';
3724                                 registerExternalTemplatePackages("XFig");
3725                         } else {
3726                                 begin_command_inset(os, "include", name);
3727                                 os << "preview false\n"
3728                                       "filename \"" << outname << "\"\n";
3729                                 if (t.cs() == "verbatiminput")
3730                                         preamble.registerAutomaticallyLoadedPackage("verbatim");
3731                         }
3732                         end_inset(os);
3733                 }
3734
3735                 else if (t.cs() == "bibliographystyle") {
3736                         // store new bibliographystyle
3737                         bibliographystyle = p.verbatim_item();
3738                         // If any other command than \bibliography and
3739                         // \nocite{*} follows, we need to output the style
3740                         // (because it might be used by that command).
3741                         // Otherwise, it will automatically be output by LyX.
3742                         p.pushPosition();
3743                         bool output = true;
3744                         for (Token t2 = p.get_token(); p.good(); t2 = p.get_token()) {
3745                                 if (t2.cat() == catBegin)
3746                                         break;
3747                                 if (t2.cat() != catEscape)
3748                                         continue;
3749                                 if (t2.cs() == "nocite") {
3750                                         if (p.getArg('{', '}') == "*")
3751                                                 continue;
3752                                 } else if (t2.cs() == "bibliography")
3753                                         output = false;
3754                                 break;
3755                         }
3756                         p.popPosition();
3757                         if (output) {
3758                                 handle_ert(os,
3759                                         "\\bibliographystyle{" + bibliographystyle + '}',
3760                                         context);
3761                         }
3762                 }
3763
3764                 else if (t.cs() == "bibliography") {
3765                         context.check_layout(os);
3766                         begin_command_inset(os, "bibtex", "bibtex");
3767                         if (!btprint.empty()) {
3768                                 os << "btprint " << '"' << "btPrintAll" << '"' << "\n";
3769                                 // clear the string because the next BibTeX inset can be without the
3770                                 // \nocite{*} option
3771                                 btprint.clear();
3772                         }
3773                         os << "bibfiles " << '"' << p.verbatim_item() << '"' << "\n";
3774                         // Do we have a bibliographystyle set?
3775                         if (!bibliographystyle.empty())
3776                                 os << "options " << '"' << bibliographystyle << '"' << "\n";
3777                         end_inset(os);
3778                 }
3779
3780                 else if (t.cs() == "parbox") {
3781                         // Test whether this is an outer box of a shaded box
3782                         p.pushPosition();
3783                         // swallow arguments
3784                         while (p.hasOpt()) {
3785                                 p.getArg('[', ']');
3786                                 p.skip_spaces(true);
3787                         }
3788                         p.getArg('{', '}');
3789                         p.skip_spaces(true);
3790                         // eat the '{'
3791                         if (p.next_token().cat() == catBegin)
3792                                 p.get_token();
3793                         p.skip_spaces(true);
3794                         Token to = p.get_token();
3795                         bool shaded = false;
3796                         if (to.asInput() == "\\begin") {
3797                                 p.skip_spaces(true);
3798                                 if (p.getArg('{', '}') == "shaded")
3799                                         shaded = true;
3800                         }
3801                         p.popPosition();
3802                         if (shaded) {
3803                                 parse_outer_box(p, os, FLAG_ITEM, outer,
3804                                                 context, "parbox", "shaded");
3805                         } else
3806                                 parse_box(p, os, 0, FLAG_ITEM, outer, context,
3807                                           "", "", t.cs());
3808                 }
3809
3810                 else if (t.cs() == "ovalbox" || t.cs() == "Ovalbox" ||
3811                          t.cs() == "shadowbox" || t.cs() == "doublebox")
3812                         parse_outer_box(p, os, FLAG_ITEM, outer, context, t.cs(), "");
3813
3814                 else if (t.cs() == "framebox") {
3815                         if (p.next_token().character() == '(') {
3816                                 //the syntax is: \framebox(x,y)[position]{content}
3817                                 string arg = t.asInput();
3818                                 arg += p.getFullParentheseArg();
3819                                 arg += p.getFullOpt();
3820                                 eat_whitespace(p, os, context, false);
3821                                 handle_ert(os, arg + '{', context);
3822                                 eat_whitespace(p, os, context, false);
3823                                 parse_text(p, os, FLAG_ITEM, outer, context);
3824                                 handle_ert(os, "}", context);
3825                         } else {
3826                                 string special = p.getFullOpt();
3827                                 special += p.getOpt();
3828                                 parse_outer_box(p, os, FLAG_ITEM, outer,
3829                                                 context, t.cs(), special);
3830                         }
3831                 }
3832
3833                 //\makebox() is part of the picture environment and different from \makebox{}
3834                 //\makebox{} will be parsed by parse_box
3835                 else if (t.cs() == "makebox") {
3836                         if (p.next_token().character() == '(') {
3837                                 //the syntax is: \makebox(x,y)[position]{content}
3838                                 string arg = t.asInput();
3839                                 arg += p.getFullParentheseArg();
3840                                 arg += p.getFullOpt();
3841                                 eat_whitespace(p, os, context, false);
3842                                 handle_ert(os, arg + '{', context);
3843                                 eat_whitespace(p, os, context, false);
3844                                 parse_text(p, os, FLAG_ITEM, outer, context);
3845                                 handle_ert(os, "}", context);
3846                         } else
3847                                 //the syntax is: \makebox[width][position]{content}
3848                                 parse_box(p, os, 0, FLAG_ITEM, outer, context,
3849                                           "", "", t.cs());
3850                 }
3851
3852                 else if (t.cs() == "smallskip" ||
3853                          t.cs() == "medskip" ||
3854                          t.cs() == "bigskip" ||
3855                          t.cs() == "vfill") {
3856                         context.check_layout(os);
3857                         begin_inset(os, "VSpace ");
3858                         os << t.cs();
3859                         end_inset(os);
3860                         skip_spaces_braces(p);
3861                 }
3862
3863                 else if ((where = is_known(t.cs(), known_spaces))) {
3864                         context.check_layout(os);
3865                         begin_inset(os, "space ");
3866                         os << '\\' << known_coded_spaces[where - known_spaces]
3867                            << '\n';
3868                         end_inset(os);
3869                         // LaTeX swallows whitespace after all spaces except
3870                         // "\\,". We have to do that here, too, because LyX
3871                         // adds "{}" which would make the spaces significant.
3872                         if (t.cs() !=  ",")
3873                                 eat_whitespace(p, os, context, false);
3874                         // LyX adds "{}" after all spaces except "\\ " and
3875                         // "\\,", so we have to remove "{}".
3876                         // "\\,{}" is equivalent to "\\," in LaTeX, so we
3877                         // remove the braces after "\\,", too.
3878                         if (t.cs() != " ")
3879                                 skip_braces(p);
3880                 }
3881
3882                 else if (t.cs() == "newpage" ||
3883                          (t.cs() == "pagebreak" && !p.hasOpt()) ||
3884                          t.cs() == "clearpage" ||
3885                          t.cs() == "cleardoublepage") {
3886                         context.check_layout(os);
3887                         begin_inset(os, "Newpage ");
3888                         os << t.cs();
3889                         end_inset(os);
3890                         skip_spaces_braces(p);
3891                 }
3892
3893                 else if (t.cs() == "DeclareRobustCommand" ||
3894                          t.cs() == "DeclareRobustCommandx" ||
3895                          t.cs() == "newcommand" ||
3896                          t.cs() == "newcommandx" ||
3897                          t.cs() == "providecommand" ||
3898                          t.cs() == "providecommandx" ||
3899                          t.cs() == "renewcommand" ||
3900                          t.cs() == "renewcommandx") {
3901                         // DeclareRobustCommand, DeclareRobustCommandx,
3902                         // providecommand and providecommandx could be handled
3903                         // by parse_command(), but we need to call
3904                         // add_known_command() here.
3905                         string name = t.asInput();
3906                         if (p.next_token().asInput() == "*") {
3907                                 // Starred form. Eat '*'
3908                                 p.get_token();
3909                                 name += '*';
3910                         }
3911                         string const command = p.verbatim_item();
3912                         string const opt1 = p.getFullOpt();
3913                         string const opt2 = p.getFullOpt();
3914                         add_known_command(command, opt1, !opt2.empty());
3915                         string const ert = name + '{' + command + '}' +
3916                                            opt1 + opt2 +
3917                                            '{' + p.verbatim_item() + '}';
3918
3919                         if (t.cs() == "DeclareRobustCommand" ||
3920                             t.cs() == "DeclareRobustCommandx" ||
3921                             t.cs() == "providecommand" ||
3922                             t.cs() == "providecommandx" ||
3923                             name[name.length()-1] == '*')
3924                                 handle_ert(os, ert, context);
3925                         else {
3926                                 context.check_layout(os);
3927                                 begin_inset(os, "FormulaMacro");
3928                                 os << "\n" << ert;
3929                                 end_inset(os);
3930                         }
3931                 }
3932
3933                 else if (t.cs() == "let" && p.next_token().asInput() != "*") {
3934                         // let could be handled by parse_command(),
3935                         // but we need to call add_known_command() here.
3936                         string ert = t.asInput();
3937                         string name;
3938                         p.skip_spaces();
3939                         if (p.next_token().cat() == catBegin) {
3940                                 name = p.verbatim_item();
3941                                 ert += '{' + name + '}';
3942                         } else {
3943                                 name = p.verbatim_item();
3944                                 ert += name;
3945                         }
3946                         string command;
3947                         p.skip_spaces();
3948                         if (p.next_token().cat() == catBegin) {
3949                                 command = p.verbatim_item();
3950                                 ert += '{' + command + '}';
3951                         } else {
3952                                 command = p.verbatim_item();
3953                                 ert += command;
3954                         }
3955                         // If command is known, make name known too, to parse
3956                         // its arguments correctly. For this reason we also
3957                         // have commands in syntax.default that are hardcoded.
3958                         CommandMap::iterator it = known_commands.find(command);
3959                         if (it != known_commands.end())
3960                                 known_commands[t.asInput()] = it->second;
3961                         handle_ert(os, ert, context);
3962                 }
3963
3964                 else if (t.cs() == "hspace" || t.cs() == "vspace") {
3965                         bool starred = false;
3966                         if (p.next_token().asInput() == "*") {
3967                                 p.get_token();
3968                                 starred = true;
3969                         }
3970                         string name = t.asInput();
3971                         string const length = p.verbatim_item();
3972                         string unit;
3973                         string valstring;
3974                         bool valid = splitLatexLength(length, valstring, unit);
3975                         bool known_hspace = false;
3976                         bool known_vspace = false;
3977                         bool known_unit = false;
3978                         double value;
3979                         if (valid) {
3980                                 istringstream iss(valstring);
3981                                 iss >> value;
3982                                 if (value == 1.0) {
3983                                         if (t.cs()[0] == 'h') {
3984                                                 if (unit == "\\fill") {
3985                                                         if (!starred) {
3986                                                                 unit = "";
3987                                                                 name = "\\hfill";
3988                                                         }
3989                                                         known_hspace = true;
3990                                                 }
3991                                         } else {
3992                                                 if (unit == "\\smallskipamount") {
3993                                                         unit = "smallskip";
3994                                                         known_vspace = true;
3995                                                 } else if (unit == "\\medskipamount") {
3996                                                         unit = "medskip";
3997                                                         known_vspace = true;
3998                                                 } else if (unit == "\\bigskipamount") {
3999                                                         unit = "bigskip";
4000                                                         known_vspace = true;
4001                                                 } else if (unit == "\\fill") {
4002                                                         unit = "vfill";
4003                                                         known_vspace = true;
4004                                                 }
4005                                         }
4006                                 }
4007                                 if (!known_hspace && !known_vspace) {
4008                                         switch (unitFromString(unit)) {
4009                                         case Length::SP:
4010                                         case Length::PT:
4011                                         case Length::BP:
4012                                         case Length::DD:
4013                                         case Length::MM:
4014                                         case Length::PC:
4015                                         case Length::CC:
4016                                         case Length::CM:
4017                                         case Length::IN:
4018                                         case Length::EX:
4019                                         case Length::EM:
4020                                         case Length::MU:
4021                                                 known_unit = true;
4022                                                 break;
4023                                         default:
4024                                                 break;
4025                                         }
4026                                 }
4027                         }
4028
4029                         if (t.cs()[0] == 'h' && (known_unit || known_hspace)) {
4030                                 // Literal horizontal length or known variable
4031                                 context.check_layout(os);
4032                                 begin_inset(os, "space ");
4033                                 os << name;
4034                                 if (starred)
4035                                         os << '*';
4036                                 os << '{';
4037                                 if (known_hspace)
4038                                         os << unit;
4039                                 os << "}";
4040                                 if (known_unit && !known_hspace)
4041                                         os << "\n\\length "
4042                                            << translate_len(length);
4043                                 end_inset(os);
4044                         } else if (known_unit || known_vspace) {
4045                                 // Literal vertical length or known variable
4046                                 context.check_layout(os);
4047                                 begin_inset(os, "VSpace ");
4048                                 if (known_unit)
4049                                         os << value;
4050                                 os << unit;
4051                                 if (starred)
4052                                         os << '*';
4053                                 end_inset(os);
4054                         } else {
4055                                 // LyX can't handle other length variables in Inset VSpace/space
4056                                 if (starred)
4057                                         name += '*';
4058                                 if (valid) {
4059                                         if (value == 1.0)
4060                                                 handle_ert(os, name + '{' + unit + '}', context);
4061                                         else if (value == -1.0)
4062                                                 handle_ert(os, name + "{-" + unit + '}', context);
4063                                         else
4064                                                 handle_ert(os, name + '{' + valstring + unit + '}', context);
4065                                 } else
4066                                         handle_ert(os, name + '{' + length + '}', context);
4067                         }
4068                 }
4069
4070                 // The single '=' is meant here.
4071                 else if ((newinsetlayout = findInsetLayout(context.textclass, t.cs(), true))) {
4072                         p.skip_spaces();
4073                         context.check_layout(os);
4074                         begin_inset(os, "Flex ");
4075                         os << to_utf8(newinsetlayout->name()) << '\n'
4076                            << "status collapsed\n";
4077                         parse_text_in_inset(p, os, FLAG_ITEM, false, context, newinsetlayout);
4078                         end_inset(os);
4079                 }
4080
4081                 else if (t.cs() == "includepdf") {
4082                         p.skip_spaces();
4083                         string const arg = p.getArg('[', ']');
4084                         map<string, string> opts;
4085                         vector<string> keys;
4086                         split_map(arg, opts, keys);
4087                         string name = normalize_filename(p.verbatim_item());
4088                         string const path = getMasterFilePath();
4089                         // We want to preserve relative / absolute filenames,
4090                         // therefore path is only used for testing
4091                         if (!makeAbsPath(name, path).exists()) {
4092                                 // The file extension is probably missing.
4093                                 // Now try to find it out.
4094                                 char const * const pdfpages_format[] = {"pdf", 0};
4095                                 string const pdftex_name =
4096                                         find_file(name, path, pdfpages_format);
4097                                 if (!pdftex_name.empty()) {
4098                                         name = pdftex_name;
4099                                         pdflatex = true;
4100                                 }
4101                         }
4102                         if (makeAbsPath(name, path).exists())
4103                                 fix_relative_filename(name);
4104                         else
4105                                 cerr << "Warning: Could not find file '"
4106                                      << name << "'." << endl;
4107                         // write output
4108                         context.check_layout(os);
4109                         begin_inset(os, "External\n\ttemplate ");
4110                         os << "PDFPages\n\tfilename "
4111                            << name << "\n";
4112                         // parse the options
4113                         if (opts.find("pages") != opts.end())
4114                                 os << "\textra LaTeX \"pages="
4115                                    << opts["pages"] << "\"\n";
4116                         if (opts.find("angle") != opts.end())
4117                                 os << "\trotateAngle "
4118                                    << opts["angle"] << '\n';
4119                         if (opts.find("origin") != opts.end()) {
4120                                 ostringstream ss;
4121                                 string const opt = opts["origin"];
4122                                 if (opt == "tl") ss << "topleft";
4123                                 if (opt == "bl") ss << "bottomleft";
4124                                 if (opt == "Bl") ss << "baselineleft";
4125                                 if (opt == "c") ss << "center";
4126                                 if (opt == "tc") ss << "topcenter";
4127                                 if (opt == "bc") ss << "bottomcenter";
4128                                 if (opt == "Bc") ss << "baselinecenter";
4129                                 if (opt == "tr") ss << "topright";
4130                                 if (opt == "br") ss << "bottomright";
4131                                 if (opt == "Br") ss << "baselineright";
4132                                 if (!ss.str().empty())
4133                                         os << "\trotateOrigin " << ss.str() << '\n';
4134                                 else
4135                                         cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
4136                         }
4137                         if (opts.find("width") != opts.end())
4138                                 os << "\twidth "
4139                                    << translate_len(opts["width"]) << '\n';
4140                         if (opts.find("height") != opts.end())
4141                                 os << "\theight "
4142                                    << translate_len(opts["height"]) << '\n';
4143                         if (opts.find("keepaspectratio") != opts.end())
4144                                 os << "\tkeepAspectRatio\n";
4145                         end_inset(os);
4146                         context.check_layout(os);
4147                         registerExternalTemplatePackages("PDFPages");
4148                 }
4149
4150                 else if (t.cs() == "loadgame") {
4151                         p.skip_spaces();
4152                         string name = normalize_filename(p.verbatim_item());
4153                         string const path = getMasterFilePath();
4154                         // We want to preserve relative / absolute filenames,
4155                         // therefore path is only used for testing
4156                         if (!makeAbsPath(name, path).exists()) {
4157                                 // The file extension is probably missing.
4158                                 // Now try to find it out.
4159                                 char const * const lyxskak_format[] = {"fen", 0};
4160                                 string const lyxskak_name =
4161                                         find_file(name, path, lyxskak_format);
4162                                 if (!lyxskak_name.empty())
4163                                         name = lyxskak_name;
4164                         }
4165                         if (makeAbsPath(name, path).exists())
4166                                 fix_relative_filename(name);
4167                         else
4168                                 cerr << "Warning: Could not find file '"
4169                                      << name << "'." << endl;
4170                         context.check_layout(os);
4171                         begin_inset(os, "External\n\ttemplate ");
4172                         os << "ChessDiagram\n\tfilename "
4173                            << name << "\n";
4174                         end_inset(os);
4175                         context.check_layout(os);
4176                         // after a \loadgame follows a \showboard
4177                         if (p.get_token().asInput() == "showboard")
4178                                 p.get_token();
4179                         registerExternalTemplatePackages("ChessDiagram");
4180                 }
4181
4182                 else {
4183                         // try to see whether the string is in unicodesymbols
4184                         // Only use text mode commands, since we are in text mode here,
4185                         // and math commands may be invalid (bug 6797)
4186                         bool termination;
4187                         docstring rem;
4188                         set<string> req;
4189                         docstring s = encodings.fromLaTeXCommand(from_utf8(t.asInput()),
4190                                         Encodings::TEXT_CMD, termination, rem, &req);
4191                         if (!s.empty()) {
4192                                 if (!rem.empty())
4193                                         cerr << "When parsing " << t.cs()
4194                                              << ", result is " << to_utf8(s)
4195                                              << "+" << to_utf8(rem) << endl;
4196                                 context.check_layout(os);
4197                                 os << to_utf8(s);
4198                                 if (termination)
4199                                         skip_spaces_braces(p);
4200                                 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
4201                                         preamble.registerAutomaticallyLoadedPackage(*it);
4202                         }
4203                         //cerr << "#: " << t << " mode: " << mode << endl;
4204                         // heuristic: read up to next non-nested space
4205                         /*
4206                         string s = t.asInput();
4207                         string z = p.verbatim_item();
4208                         while (p.good() && z != " " && z.size()) {
4209                                 //cerr << "read: " << z << endl;
4210                                 s += z;
4211                                 z = p.verbatim_item();
4212                         }
4213                         cerr << "found ERT: " << s << endl;
4214                         handle_ert(os, s + ' ', context);
4215                         */
4216                         else {
4217                                 string name = t.asInput();
4218                                 if (p.next_token().asInput() == "*") {
4219                                         // Starred commands like \vspace*{}
4220                                         p.get_token();  // Eat '*'
4221                                         name += '*';
4222                                 }
4223                                 if (!parse_command(name, p, os, outer, context))
4224                                         handle_ert(os, name, context);
4225                         }
4226                 }
4227
4228                 if (flags & FLAG_LEAVE) {
4229                         flags &= ~FLAG_LEAVE;
4230                         break;
4231                 }
4232         }
4233 }
4234
4235 // }])
4236
4237
4238 } // namespace lyx