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.
7 * \author Jean-Marc Lasgouttes
10 * Full author contact details are available in file CREDITS.
21 #include "FloatList.h"
22 #include "LaTeXPackages.h"
27 #include "insets/ExternalTemplate.h"
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"
43 using namespace lyx::support;
48 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
49 Context const & context, InsetLayout const * layout)
51 bool const forcePlainLayout =
52 layout ? layout->forcePlainLayout() : false;
53 Context newcontext(true, context.textclass);
55 newcontext.layout = &context.textclass.plainLayout();
57 newcontext.font = context.font;
58 parse_text(p, os, flags, outer, newcontext);
59 newcontext.check_end_layout(os);
65 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
66 Context const & context, string const & name)
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);
76 /// parses a paragraph snippet, useful for example for \\emph{...}
77 void parse_text_snippet(Parser & p, ostream & os, unsigned flags, bool outer,
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;
91 * Thin wrapper around parse_text_snippet() using a string.
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.
99 string parse_text_snippet(Parser & p, unsigned flags, const bool outer,
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();
109 parse_text_snippet(p, os, flags, outer, newcontext);
114 char const * const known_ref_commands[] = { "ref", "pageref", "vref",
115 "vpageref", "prettyref", "eqref", 0 };
117 char const * const known_coded_ref_commands[] = { "ref", "pageref", "vref",
118 "vpageref", "formatted", "eqref", 0 };
121 * known polyglossia language names (including variants)
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",
134 "american", "ancient", "australian", "british", "monotonic", "newzealand",
138 * the same as polyglossia_languages with .lyx names
139 * please keep this in sync with polyglossia_languages line by line!
141 const char * const coded_polyglossia_languages[] = {
142 "albanian", "croatian", "hebrew", "norsk", "swedish", "amharic", "czech", "hindi",
143 "nynorsk", "syriac", "arabic_arabi", "danish", "icelandic", "occitan", "tamil",
144 "armenian", "divehi", "interlingua", "polish", "telugu", "asturian", "dutch",
145 "irish", "portuges", "thai", "bahasa", "english", "italian", "romanian", "turkish",
146 "bahasam", "esperanto", "lao", "russian", "turkmen", "basque", "estonian", "latin",
147 "samin", "ukrainian", "bengali", "farsi", "latvian", "sanskrit", "urdu", "brazilian",
148 "brazilian", "finnish", "lithuanian", "scottish", "uppersorbian", "breton", "french",
149 "lowersorbian", "serbian", "vietnamese", "bulgarian", "galician", "magyar", "slovak",
150 "welsh", "catalan", "ngerman", "malayalam", "slovene", "coptic", "greek",
151 "marathi", "spanish",
152 "american", "ancientgreek", "australian", "british", "greek", "newzealand",
153 "polutonikogreek", 0};
156 * supported CJK encodings
158 const char * const supported_CJK_encodings[] = {
159 "EUC-JP", "KS", "GB", "UTF8", 0};
162 * the same as supported_CJK_encodings with their corresponding LyX language name
163 * please keep this in sync with supported_CJK_encodings line by line!
165 const char * const coded_supported_CJK_encodings[] = {
166 "japanese-cjk", "korean", "chinese-simplified", "chinese-traditional", 0};
168 string CJK2lyx(string const & encoding)
170 char const * const * where = is_known(encoding, supported_CJK_encodings);
172 return coded_supported_CJK_encodings[where - supported_CJK_encodings];
178 * The starred forms are also known except for "citefullauthor",
179 * "citeyear" and "citeyearpar".
181 char const * const known_natbib_commands[] = { "cite", "citet", "citep",
182 "citealt", "citealp", "citeauthor", "citeyear", "citeyearpar",
183 "citefullauthor", "Citet", "Citep", "Citealt", "Citealp", "Citeauthor", 0 };
187 * No starred form other than "cite*" known.
189 char const * const known_jurabib_commands[] = { "cite", "citet", "citep",
190 "citealt", "citealp", "citeauthor", "citeyear", "citeyearpar",
191 // jurabib commands not (yet) supported by LyX:
193 // "footcite", "footcitet", "footcitep", "footcitealt", "footcitealp",
194 // "footciteauthor", "footciteyear", "footciteyearpar",
195 "citefield", "citetitle", 0 };
197 /// LaTeX names for quotes
198 char const * const known_quotes[] = { "dq", "guillemotleft", "flqq", "og",
199 "guillemotright", "frqq", "fg", "glq", "glqq", "textquoteleft", "grq", "grqq",
200 "quotedblbase", "textquotedblleft", "quotesinglbase", "textquoteright", "flq",
201 "guilsinglleft", "frq", "guilsinglright", 0};
203 /// the same as known_quotes with .lyx names
204 char const * const known_coded_quotes[] = { "prd", "ard", "ard", "ard",
205 "ald", "ald", "ald", "gls", "gld", "els", "els", "grd",
206 "gld", "grd", "gls", "ers", "fls",
207 "fls", "frs", "frs", 0};
209 /// LaTeX names for font sizes
210 char const * const known_sizes[] = { "tiny", "scriptsize", "footnotesize",
211 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
213 /// the same as known_sizes with .lyx names
214 char const * const known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
215 "small", "normal", "large", "larger", "largest", "huge", "giant", 0};
217 /// LaTeX 2.09 names for font families
218 char const * const known_old_font_families[] = { "rm", "sf", "tt", 0};
220 /// LaTeX names for font families
221 char const * const known_font_families[] = { "rmfamily", "sffamily",
224 /// LaTeX names for font family changing commands
225 char const * const known_text_font_families[] = { "textrm", "textsf",
228 /// The same as known_old_font_families, known_font_families and
229 /// known_text_font_families with .lyx names
230 char const * const known_coded_font_families[] = { "roman", "sans",
233 /// LaTeX 2.09 names for font series
234 char const * const known_old_font_series[] = { "bf", 0};
236 /// LaTeX names for font series
237 char const * const known_font_series[] = { "bfseries", "mdseries", 0};
239 /// LaTeX names for font series changing commands
240 char const * const known_text_font_series[] = { "textbf", "textmd", 0};
242 /// The same as known_old_font_series, known_font_series and
243 /// known_text_font_series with .lyx names
244 char const * const known_coded_font_series[] = { "bold", "medium", 0};
246 /// LaTeX 2.09 names for font shapes
247 char const * const known_old_font_shapes[] = { "it", "sl", "sc", 0};
249 /// LaTeX names for font shapes
250 char const * const known_font_shapes[] = { "itshape", "slshape", "scshape",
253 /// LaTeX names for font shape changing commands
254 char const * const known_text_font_shapes[] = { "textit", "textsl", "textsc",
257 /// The same as known_old_font_shapes, known_font_shapes and
258 /// known_text_font_shapes with .lyx names
259 char const * const known_coded_font_shapes[] = { "italic", "slanted",
260 "smallcaps", "up", 0};
262 /// Known special characters which need skip_spaces_braces() afterwards
263 char const * const known_special_chars[] = {"ldots", "lyxarrow",
264 "textcompwordmark", "slash", 0};
266 /// the same as known_special_chars with .lyx names
267 char const * const known_coded_special_chars[] = {"ldots{}", "menuseparator",
268 "textcompwordmark{}", "slash{}", 0};
271 * Graphics file extensions known by the dvips driver of the graphics package.
272 * These extensions are used to complete the filename of an included
273 * graphics file if it does not contain an extension.
274 * The order must be the same that latex uses to find a file, because we
275 * will use the first extension that matches.
276 * This is only an approximation for the common cases. If we would want to
277 * do it right in all cases, we would need to know which graphics driver is
278 * used and know the extensions of every driver of the graphics package.
280 char const * const known_dvips_graphics_formats[] = {"eps", "ps", "eps.gz",
281 "ps.gz", "eps.Z", "ps.Z", 0};
284 * Graphics file extensions known by the pdftex driver of the graphics package.
285 * \sa known_dvips_graphics_formats
287 char const * const known_pdftex_graphics_formats[] = {"png", "pdf", "jpg",
291 * Known file extensions for TeX files as used by \\include.
293 char const * const known_tex_extensions[] = {"tex", 0};
295 /// spaces known by InsetSpace
296 char const * const known_spaces[] = { " ", "space", ",",
297 "thinspace", "quad", "qquad", "enspace", "enskip",
298 "negthinspace", "negmedspace", "negthickspace", "textvisiblespace",
299 "hfill", "dotfill", "hrulefill", "leftarrowfill", "rightarrowfill",
300 "upbracefill", "downbracefill", 0};
302 /// the same as known_spaces with .lyx names
303 char const * const known_coded_spaces[] = { "space{}", "space{}",
304 "thinspace{}", "thinspace{}", "quad{}", "qquad{}", "enspace{}", "enskip{}",
305 "negthinspace{}", "negmedspace{}", "negthickspace{}", "textvisiblespace{}",
306 "hfill{}", "dotfill{}", "hrulefill{}", "leftarrowfill{}", "rightarrowfill{}",
307 "upbracefill{}", "downbracefill{}", 0};
309 /// These are translated by LyX to commands like "\\LyX{}", so we have to put
310 /// them in ERT. "LaTeXe" must come before "LaTeX"!
311 char const * const known_phrases[] = {"LyX", "TeX", "LaTeXe", "LaTeX", 0};
312 char const * const known_coded_phrases[] = {"LyX", "TeX", "LaTeX2e", "LaTeX", 0};
313 int const known_phrase_lengths[] = {3, 5, 7, 0};
315 // string to store the float type to be able to determine the type of subfloats
316 string float_type = "";
319 /// splits "x=z, y=b" into a map and an ordered keyword vector
320 void split_map(string const & s, map<string, string> & res, vector<string> & keys)
325 keys.resize(v.size());
326 for (size_t i = 0; i < v.size(); ++i) {
327 size_t const pos = v[i].find('=');
328 string const index = trimSpaceAndEol(v[i].substr(0, pos));
329 string const value = trimSpaceAndEol(v[i].substr(pos + 1, string::npos));
337 * Split a LaTeX length into value and unit.
338 * The latter can be a real unit like "pt", or a latex length variable
339 * like "\textwidth". The unit may contain additional stuff like glue
340 * lengths, but we don't care, because such lengths are ERT anyway.
341 * \returns true if \p value and \p unit are valid.
343 bool splitLatexLength(string const & len, string & value, string & unit)
347 const string::size_type i = len.find_first_not_of(" -+0123456789.,");
348 //'4,5' is a valid LaTeX length number. Change it to '4.5'
349 string const length = subst(len, ',', '.');
350 if (i == string::npos)
353 if (len[0] == '\\') {
354 // We had something like \textwidth without a factor
360 value = trimSpaceAndEol(string(length, 0, i));
364 // 'cM' is a valid LaTeX length unit. Change it to 'cm'
365 if (contains(len, '\\'))
366 unit = trimSpaceAndEol(string(len, i));
368 unit = ascii_lowercase(trimSpaceAndEol(string(len, i)));
373 /// A simple function to translate a latex length to something LyX can
374 /// understand. Not perfect, but rather best-effort.
375 bool translate_len(string const & length, string & valstring, string & unit)
377 if (!splitLatexLength(length, valstring, unit))
379 // LyX uses percent values
381 istringstream iss(valstring);
386 string const percentval = oss.str();
388 if (unit.empty() || unit[0] != '\\')
390 string::size_type const i = unit.find(' ');
391 string const endlen = (i == string::npos) ? string() : string(unit, i);
392 if (unit == "\\textwidth") {
393 valstring = percentval;
394 unit = "text%" + endlen;
395 } else if (unit == "\\columnwidth") {
396 valstring = percentval;
397 unit = "col%" + endlen;
398 } else if (unit == "\\paperwidth") {
399 valstring = percentval;
400 unit = "page%" + endlen;
401 } else if (unit == "\\linewidth") {
402 valstring = percentval;
403 unit = "line%" + endlen;
404 } else if (unit == "\\paperheight") {
405 valstring = percentval;
406 unit = "pheight%" + endlen;
407 } else if (unit == "\\textheight") {
408 valstring = percentval;
409 unit = "theight%" + endlen;
417 string translate_len(string const & length)
421 if (translate_len(length, value, unit))
423 // If the input is invalid, return what we have.
431 * Translates a LaTeX length into \p value, \p unit and
432 * \p special parts suitable for a box inset.
433 * The difference from translate_len() is that a box inset knows about
434 * some special "units" that are stored in \p special.
436 void translate_box_len(string const & length, string & value, string & unit, string & special)
438 if (translate_len(length, value, unit)) {
439 if (unit == "\\height" || unit == "\\depth" ||
440 unit == "\\totalheight" || unit == "\\width") {
441 special = unit.substr(1);
442 // The unit is not used, but LyX requires a dummy setting
455 * Find a file with basename \p name in path \p path and an extension
458 string find_file(string const & name, string const & path,
459 char const * const * extensions)
461 for (char const * const * what = extensions; *what; ++what) {
462 string const trial = addExtension(name, *what);
463 if (makeAbsPath(trial, path).exists())
470 void begin_inset(ostream & os, string const & name)
472 os << "\n\\begin_inset " << name;
476 void begin_command_inset(ostream & os, string const & name,
477 string const & latexname)
479 begin_inset(os, "CommandInset ");
480 os << name << "\nLatexCommand " << latexname << '\n';
484 void end_inset(ostream & os)
486 os << "\n\\end_inset\n\n";
490 bool skip_braces(Parser & p)
492 if (p.next_token().cat() != catBegin)
495 if (p.next_token().cat() == catEnd) {
504 /// replace LaTeX commands in \p s from the unicodesymbols file with their
506 docstring convert_unicodesymbols(docstring s)
509 for (size_t i = 0; i < s.size();) {
518 docstring parsed = encodings.fromLaTeXCommand(s,
519 Encodings::TEXT_CMD, termination, rem, &req);
520 set<string>::const_iterator it = req.begin();
521 set<string>::const_iterator en = req.end();
522 for (; it != en; ++it)
523 preamble.registerAutomaticallyLoadedPackage(*it);
526 if (s.empty() || s[0] != '\\')
535 /// try to convert \p s to a valid InsetCommand argument
536 string convert_command_inset_arg(string s)
539 // since we don't know the input encoding we can't use from_utf8
540 s = to_utf8(convert_unicodesymbols(from_ascii(s)));
541 // LyX cannot handle newlines in a latex command
542 return subst(s, "\n", " ");
546 void handle_backslash(ostream & os, string const & s)
548 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
550 os << "\n\\backslash\n";
557 void handle_ert(ostream & os, string const & s, Context & context)
559 // We must have a valid layout before outputting the ERT inset.
560 context.check_layout(os);
561 Context newcontext(true, context.textclass);
562 begin_inset(os, "ERT");
563 os << "\nstatus collapsed\n";
564 newcontext.check_layout(os);
565 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
567 os << "\n\\backslash\n";
568 else if (*it == '\n') {
569 newcontext.new_paragraph(os);
570 newcontext.check_layout(os);
574 newcontext.check_end_layout(os);
579 void handle_comment(ostream & os, string const & s, Context & context)
581 // TODO: Handle this better
582 Context newcontext(true, context.textclass);
583 begin_inset(os, "ERT");
584 os << "\nstatus collapsed\n";
585 newcontext.check_layout(os);
586 handle_backslash(os, s);
587 // make sure that our comment is the last thing on the line
588 newcontext.new_paragraph(os);
589 newcontext.check_layout(os);
590 newcontext.check_end_layout(os);
595 Layout const * findLayout(TextClass const & textclass, string const & name, bool command)
597 Layout const * layout = findLayoutWithoutModule(textclass, name, command);
600 if (checkModule(name, command))
601 return findLayoutWithoutModule(textclass, name, command);
606 InsetLayout const * findInsetLayout(TextClass const & textclass, string const & name, bool command)
608 InsetLayout const * insetlayout = findInsetLayoutWithoutModule(textclass, name, command);
611 if (checkModule(name, command))
612 return findInsetLayoutWithoutModule(textclass, name, command);
617 void eat_whitespace(Parser &, ostream &, Context &, bool);
621 * Skips whitespace and braces.
622 * This should be called after a command has been parsed that is not put into
623 * ERT, and where LyX adds "{}" if needed.
625 void skip_spaces_braces(Parser & p, bool keepws = false)
627 /* The following four examples produce the same typeset output and
628 should be handled by this function:
636 // Unfortunately we need to skip comments, too.
637 // We can't use eat_whitespace since writing them after the {}
638 // results in different output in some cases.
639 bool const skipped_spaces = p.skip_spaces(true);
640 bool const skipped_braces = skip_braces(p);
641 if (keepws && skipped_spaces && !skipped_braces)
642 // put back the space (it is better handled by check_space)
643 p.unskip_spaces(true);
647 void output_command_layout(ostream & os, Parser & p, bool outer,
648 Context & parent_context,
649 Layout const * newlayout)
651 TeXFont const oldFont = parent_context.font;
652 // save the current font size
653 string const size = oldFont.size;
654 // reset the font size to default, because the font size switches
655 // don't affect section headings and the like
656 parent_context.font.size = Context::normalfont.size;
657 // we only need to write the font change if we have an open layout
658 if (!parent_context.atParagraphStart())
659 output_font_change(os, oldFont, parent_context.font);
660 parent_context.check_end_layout(os);
661 Context context(true, parent_context.textclass, newlayout,
662 parent_context.layout, parent_context.font);
663 if (parent_context.deeper_paragraph) {
664 // We are beginning a nested environment after a
665 // deeper paragraph inside the outer list environment.
666 // Therefore we don't need to output a "begin deeper".
667 context.need_end_deeper = true;
669 context.check_deeper(os);
670 context.check_layout(os);
671 unsigned int optargs = 0;
672 while (optargs < context.layout->optargs) {
673 eat_whitespace(p, os, context, false);
674 if (p.next_token().cat() == catEscape ||
675 p.next_token().character() != '[')
677 p.get_token(); // eat '['
678 begin_inset(os, "Argument\n");
679 os << "status collapsed\n\n";
680 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
682 eat_whitespace(p, os, context, false);
685 unsigned int reqargs = 0;
686 while (reqargs < context.layout->reqargs) {
687 eat_whitespace(p, os, context, false);
688 if (p.next_token().cat() != catBegin)
690 p.get_token(); // eat '{'
691 begin_inset(os, "Argument\n");
692 os << "status collapsed\n\n";
693 parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context);
695 eat_whitespace(p, os, context, false);
698 parse_text(p, os, FLAG_ITEM, outer, context);
699 context.check_end_layout(os);
700 if (parent_context.deeper_paragraph) {
701 // We must suppress the "end deeper" because we
702 // suppressed the "begin deeper" above.
703 context.need_end_deeper = false;
705 context.check_end_deeper(os);
706 // We don't need really a new paragraph, but
707 // we must make sure that the next item gets a \begin_layout.
708 parent_context.new_paragraph(os);
709 // Set the font size to the original value. No need to output it here
710 // (Context::begin_layout() will do that if needed)
711 parent_context.font.size = size;
716 * Output a space if necessary.
717 * This function gets called for every whitespace token.
719 * We have three cases here:
720 * 1. A space must be suppressed. Example: The lyxcode case below
721 * 2. A space may be suppressed. Example: Spaces before "\par"
722 * 3. A space must not be suppressed. Example: A space between two words
724 * We currently handle only 1. and 3 and from 2. only the case of
725 * spaces before newlines as a side effect.
727 * 2. could be used to suppress as many spaces as possible. This has two effects:
728 * - Reimporting LyX generated LaTeX files changes almost no whitespace
729 * - Superflous whitespace from non LyX generated LaTeX files is removed.
730 * The drawback is that the logic inside the function becomes
731 * complicated, and that is the reason why it is not implemented.
733 void check_space(Parser & p, ostream & os, Context & context)
735 Token const next = p.next_token();
736 Token const curr = p.curr_token();
737 // A space before a single newline and vice versa must be ignored
738 // LyX emits a newline before \end{lyxcode}.
739 // This newline must be ignored,
740 // otherwise LyX will add an additional protected space.
741 if (next.cat() == catSpace ||
742 next.cat() == catNewline ||
743 (next.cs() == "end" && context.layout->free_spacing && curr.cat() == catNewline)) {
746 context.check_layout(os);
752 * Parse all arguments of \p command
754 void parse_arguments(string const & command,
755 vector<ArgumentType> const & template_arguments,
756 Parser & p, ostream & os, bool outer, Context & context)
758 string ert = command;
759 size_t no_arguments = template_arguments.size();
760 for (size_t i = 0; i < no_arguments; ++i) {
761 switch (template_arguments[i]) {
764 // This argument contains regular LaTeX
765 handle_ert(os, ert + '{', context);
766 eat_whitespace(p, os, context, false);
767 if (template_arguments[i] == required)
768 parse_text(p, os, FLAG_ITEM, outer, context);
770 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
774 // This argument consists only of a single item.
775 // The presence of '{' or not must be preserved.
777 if (p.next_token().cat() == catBegin)
778 ert += '{' + p.verbatim_item() + '}';
780 ert += p.verbatim_item();
784 // This argument may contain special characters
785 ert += '{' + p.verbatim_item() + '}';
789 // true because we must not eat whitespace
790 // if an optional arg follows we must not strip the
791 // brackets from this one
792 if (i < no_arguments - 1 &&
793 template_arguments[i+1] == optional)
794 ert += p.getFullOpt(true);
796 ert += p.getOpt(true);
800 handle_ert(os, ert, context);
805 * Check whether \p command is a known command. If yes,
806 * handle the command with all arguments.
807 * \return true if the command was parsed, false otherwise.
809 bool parse_command(string const & command, Parser & p, ostream & os,
810 bool outer, Context & context)
812 if (known_commands.find(command) != known_commands.end()) {
813 parse_arguments(command, known_commands[command], p, os,
821 /// Parses a minipage or parbox
822 void parse_box(Parser & p, ostream & os, unsigned outer_flags,
823 unsigned inner_flags, bool outer, Context & parent_context,
824 string const & outer_type, string const & special,
825 string const & inner_type)
829 string hor_pos = "c";
830 // We need to set the height to the LaTeX default of 1\\totalheight
831 // for the case when no height argument is given
832 string height_value = "1";
833 string height_unit = "in";
834 string height_special = "totalheight";
839 string width_special = "none";
840 if (!inner_type.empty() && p.hasOpt()) {
841 if (inner_type != "makebox")
842 position = p.getArg('[', ']');
844 latex_width = p.getArg('[', ']');
845 translate_box_len(latex_width, width_value, width_unit, width_special);
848 if (position != "t" && position != "c" && position != "b") {
849 cerr << "invalid position " << position << " for "
850 << inner_type << endl;
854 if (inner_type != "makebox") {
855 latex_height = p.getArg('[', ']');
856 translate_box_len(latex_height, height_value, height_unit, height_special);
858 hor_pos = p.getArg('[', ']');
861 inner_pos = p.getArg('[', ']');
862 if (inner_pos != "c" && inner_pos != "t" &&
863 inner_pos != "b" && inner_pos != "s") {
864 cerr << "invalid inner_pos "
865 << inner_pos << " for "
866 << inner_type << endl;
867 inner_pos = position;
872 if (inner_type.empty()) {
873 if (special.empty() && outer_type != "framebox")
874 latex_width = "1\\columnwidth";
877 latex_width = p2.getArg('[', ']');
878 string const opt = p2.getArg('[', ']');
881 if (hor_pos != "l" && hor_pos != "c" &&
883 cerr << "invalid hor_pos " << hor_pos
884 << " for " << outer_type << endl;
889 } else if (inner_type != "makebox")
890 latex_width = p.verbatim_item();
891 // if e.g. only \ovalbox{content} was used, set the width to 1\columnwidth
892 // as this is LyX's standard for such cases (except for makebox)
893 // \framebox is more special and handled below
894 if (latex_width.empty() && inner_type != "makebox"
895 && outer_type != "framebox")
896 latex_width = "1\\columnwidth";
898 translate_len(latex_width, width_value, width_unit);
900 bool shadedparbox = false;
901 if (inner_type == "shaded") {
902 eat_whitespace(p, os, parent_context, false);
903 if (outer_type == "parbox") {
905 if (p.next_token().cat() == catBegin)
907 eat_whitespace(p, os, parent_context, false);
913 // If we already read the inner box we have to push the inner env
914 if (!outer_type.empty() && !inner_type.empty() &&
915 (inner_flags & FLAG_END))
916 active_environments.push_back(inner_type);
917 // LyX can't handle length variables
918 bool use_ert = contains(width_unit, '\\') || contains(height_unit, '\\');
919 if (!use_ert && !outer_type.empty() && !inner_type.empty()) {
920 // Look whether there is some content after the end of the
921 // inner box, but before the end of the outer box.
922 // If yes, we need to output ERT.
924 if (inner_flags & FLAG_END)
925 p.verbatimEnvironment(inner_type);
929 bool const outer_env(outer_type == "framed" || outer_type == "minipage");
930 if ((outer_env && p.next_token().asInput() != "\\end") ||
931 (!outer_env && p.next_token().cat() != catEnd)) {
932 // something is between the end of the inner box and
933 // the end of the outer box, so we need to use ERT.
938 // if only \makebox{content} was used we can set its width to 1\width
939 // because this identic and also identic to \mbox
940 // this doesn't work for \framebox{content}, thus we have to use ERT for this
941 if (latex_width.empty() && inner_type == "makebox") {
944 width_special = "width";
945 } else if (latex_width.empty() && outer_type == "framebox") {
950 if (!outer_type.empty()) {
951 if (outer_flags & FLAG_END)
952 ss << "\\begin{" << outer_type << '}';
954 ss << '\\' << outer_type << '{';
955 if (!special.empty())
959 if (!inner_type.empty()) {
960 if (inner_type != "shaded") {
961 if (inner_flags & FLAG_END)
962 ss << "\\begin{" << inner_type << '}';
964 ss << '\\' << inner_type;
966 if (!position.empty())
967 ss << '[' << position << ']';
968 if (!latex_height.empty())
969 ss << '[' << latex_height << ']';
970 if (!inner_pos.empty())
971 ss << '[' << inner_pos << ']';
972 ss << '{' << latex_width << '}';
973 if (!(inner_flags & FLAG_END))
976 if (inner_type == "shaded")
977 ss << "\\begin{shaded}";
978 handle_ert(os, ss.str(), parent_context);
979 if (!inner_type.empty()) {
980 parse_text(p, os, inner_flags, outer, parent_context);
981 if (inner_flags & FLAG_END)
982 handle_ert(os, "\\end{" + inner_type + '}',
985 handle_ert(os, "}", parent_context);
987 if (!outer_type.empty()) {
988 // If we already read the inner box we have to pop
990 if (!inner_type.empty() && (inner_flags & FLAG_END))
991 active_environments.pop_back();
993 // Ensure that the end of the outer box is parsed correctly:
994 // The opening brace has been eaten by parse_outer_box()
995 if (!outer_type.empty() && (outer_flags & FLAG_ITEM)) {
996 outer_flags &= ~FLAG_ITEM;
997 outer_flags |= FLAG_BRACE_LAST;
999 parse_text(p, os, outer_flags, outer, parent_context);
1000 if (outer_flags & FLAG_END)
1001 handle_ert(os, "\\end{" + outer_type + '}',
1003 else if (inner_type.empty() && outer_type == "framebox")
1004 // in this case it is already closed later
1007 handle_ert(os, "}", parent_context);
1010 // LyX does not like empty positions, so we have
1011 // to set them to the LaTeX default values here.
1012 if (position.empty())
1014 if (inner_pos.empty())
1015 inner_pos = position;
1016 parent_context.check_layout(os);
1017 begin_inset(os, "Box ");
1018 if (outer_type == "framed")
1020 else if (outer_type == "framebox")
1022 else if (outer_type == "shadowbox")
1023 os << "Shadowbox\n";
1024 else if ((outer_type == "shaded" && inner_type.empty()) ||
1025 (outer_type == "minipage" && inner_type == "shaded") ||
1026 (outer_type == "parbox" && inner_type == "shaded")) {
1028 preamble.registerAutomaticallyLoadedPackage("color");
1029 } else if (outer_type == "doublebox")
1030 os << "Doublebox\n";
1031 else if (outer_type.empty())
1032 os << "Frameless\n";
1034 os << outer_type << '\n';
1035 os << "position \"" << position << "\"\n";
1036 os << "hor_pos \"" << hor_pos << "\"\n";
1037 os << "has_inner_box " << !inner_type.empty() << "\n";
1038 os << "inner_pos \"" << inner_pos << "\"\n";
1039 os << "use_parbox " << (inner_type == "parbox" || shadedparbox)
1041 os << "use_makebox " << (inner_type == "makebox") << '\n';
1042 os << "width \"" << width_value << width_unit << "\"\n";
1043 os << "special \"" << width_special << "\"\n";
1044 os << "height \"" << height_value << height_unit << "\"\n";
1045 os << "height_special \"" << height_special << "\"\n";
1046 os << "status open\n\n";
1048 // Unfortunately we can't use parse_text_in_inset:
1049 // InsetBox::forcePlainLayout() is hard coded and does not
1050 // use the inset layout. Apart from that do we call parse_text
1051 // up to two times, but need only one check_end_layout.
1052 bool const forcePlainLayout =
1053 (!inner_type.empty() || inner_type == "makebox") &&
1054 outer_type != "shaded" && outer_type != "framed";
1055 Context context(true, parent_context.textclass);
1056 if (forcePlainLayout)
1057 context.layout = &context.textclass.plainLayout();
1059 context.font = parent_context.font;
1061 // If we have no inner box the contents will be read with the outer box
1062 if (!inner_type.empty())
1063 parse_text(p, os, inner_flags, outer, context);
1065 // Ensure that the end of the outer box is parsed correctly:
1066 // The opening brace has been eaten by parse_outer_box()
1067 if (!outer_type.empty() && (outer_flags & FLAG_ITEM)) {
1068 outer_flags &= ~FLAG_ITEM;
1069 outer_flags |= FLAG_BRACE_LAST;
1072 // Find end of outer box, output contents if inner_type is
1073 // empty and output possible comments
1074 if (!outer_type.empty()) {
1075 // If we already read the inner box we have to pop
1077 if (!inner_type.empty() && (inner_flags & FLAG_END))
1078 active_environments.pop_back();
1079 // This does not output anything but comments if
1080 // inner_type is not empty (see use_ert)
1081 parse_text(p, os, outer_flags, outer, context);
1084 context.check_end_layout(os);
1086 #ifdef PRESERVE_LAYOUT
1087 // LyX puts a % after the end of the minipage
1088 if (p.next_token().cat() == catNewline && p.next_token().cs().size() > 1) {
1090 //handle_comment(os, "%dummy", parent_context);
1093 parent_context.new_paragraph(os);
1095 else if (p.next_token().cat() == catSpace || p.next_token().cat() == catNewline) {
1096 //handle_comment(os, "%dummy", parent_context);
1099 // We add a protected space if something real follows
1100 if (p.good() && p.next_token().cat() != catComment) {
1101 begin_inset(os, "space ~\n");
1110 void parse_outer_box(Parser & p, ostream & os, unsigned flags, bool outer,
1111 Context & parent_context, string const & outer_type,
1112 string const & special)
1114 eat_whitespace(p, os, parent_context, false);
1115 if (flags & FLAG_ITEM) {
1117 if (p.next_token().cat() == catBegin)
1120 cerr << "Warning: Ignoring missing '{' after \\"
1121 << outer_type << '.' << endl;
1122 eat_whitespace(p, os, parent_context, false);
1125 unsigned int inner_flags = 0;
1127 if (outer_type == "minipage" || outer_type == "parbox") {
1128 p.skip_spaces(true);
1129 while (p.hasOpt()) {
1131 p.skip_spaces(true);
1134 p.skip_spaces(true);
1135 if (outer_type == "parbox") {
1137 if (p.next_token().cat() == catBegin)
1139 p.skip_spaces(true);
1142 if (outer_type == "shaded") {
1143 // These boxes never have an inner box
1145 } else if (p.next_token().asInput() == "\\parbox") {
1146 inner = p.get_token().cs();
1147 inner_flags = FLAG_ITEM;
1148 } else if (p.next_token().asInput() == "\\begin") {
1149 // Is this a minipage or shaded box?
1152 inner = p.getArg('{', '}');
1154 if (inner == "minipage" || inner == "shaded")
1155 inner_flags = FLAG_END;
1160 if (inner_flags == FLAG_END) {
1161 if (inner != "shaded")
1165 eat_whitespace(p, os, parent_context, false);
1167 parse_box(p, os, flags, FLAG_END, outer, parent_context,
1168 outer_type, special, inner);
1170 if (inner_flags == FLAG_ITEM) {
1172 eat_whitespace(p, os, parent_context, false);
1174 parse_box(p, os, flags, inner_flags, outer, parent_context,
1175 outer_type, special, inner);
1180 void parse_listings(Parser & p, ostream & os, Context & parent_context, bool in_line)
1182 parent_context.check_layout(os);
1183 begin_inset(os, "listings\n");
1185 string arg = p.verbatimOption();
1186 os << "lstparams " << '"' << arg << '"' << '\n';
1189 os << "inline true\n";
1191 os << "inline false\n";
1192 os << "status collapsed\n";
1193 Context context(true, parent_context.textclass);
1194 context.layout = &parent_context.textclass.plainLayout();
1197 s = p.plainCommand('!', '!', "lstinline");
1198 context.new_paragraph(os);
1199 context.check_layout(os);
1201 s = p.plainEnvironment("lstlisting");
1202 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
1204 os << "\n\\backslash\n";
1205 else if (*it == '\n') {
1206 // avoid adding an empty paragraph at the end
1208 context.new_paragraph(os);
1209 context.check_layout(os);
1214 context.check_end_layout(os);
1219 /// parse an unknown environment
1220 void parse_unknown_environment(Parser & p, string const & name, ostream & os,
1221 unsigned flags, bool outer,
1222 Context & parent_context)
1224 if (name == "tabbing")
1225 // We need to remember that we have to handle '\=' specially
1226 flags |= FLAG_TABBING;
1228 // We need to translate font changes and paragraphs inside the
1229 // environment to ERT if we have a non standard font.
1230 // Otherwise things like
1231 // \large\begin{foo}\huge bar\end{foo}
1233 bool const specialfont =
1234 (parent_context.font != parent_context.normalfont);
1235 bool const new_layout_allowed = parent_context.new_layout_allowed;
1237 parent_context.new_layout_allowed = false;
1238 handle_ert(os, "\\begin{" + name + "}", parent_context);
1239 parse_text_snippet(p, os, flags, outer, parent_context);
1240 handle_ert(os, "\\end{" + name + "}", parent_context);
1242 parent_context.new_layout_allowed = new_layout_allowed;
1246 void parse_environment(Parser & p, ostream & os, bool outer,
1247 string & last_env, Context & parent_context)
1249 Layout const * newlayout;
1250 InsetLayout const * newinsetlayout = 0;
1251 string const name = p.getArg('{', '}');
1252 const bool is_starred = suffixIs(name, '*');
1253 string const unstarred_name = rtrim(name, "*");
1254 active_environments.push_back(name);
1256 if (is_math_env(name)) {
1257 parent_context.check_layout(os);
1258 begin_inset(os, "Formula ");
1259 os << "\\begin{" << name << "}";
1260 parse_math(p, os, FLAG_END, MATH_MODE);
1261 os << "\\end{" << name << "}";
1263 if (is_display_math_env(name)) {
1264 // Prevent the conversion of a line break to a space
1265 // (bug 7668). This does not change the output, but
1266 // looks ugly in LyX.
1267 eat_whitespace(p, os, parent_context, false);
1271 else if (is_known(name, polyglossia_languages)) {
1272 // We must begin a new paragraph if not already done
1273 if (! parent_context.atParagraphStart()) {
1274 parent_context.check_end_layout(os);
1275 parent_context.new_paragraph(os);
1277 // save the language in the context so that it is
1278 // handled by parse_text
1279 parent_context.font.language = polyglossia2lyx(name);
1280 parse_text(p, os, FLAG_END, outer, parent_context);
1281 // Just in case the environment is empty
1282 parent_context.extra_stuff.erase();
1283 // We must begin a new paragraph to reset the language
1284 parent_context.new_paragraph(os);
1288 else if (unstarred_name == "tabular" || name == "longtable") {
1289 eat_whitespace(p, os, parent_context, false);
1290 string width = "0pt";
1291 if (name == "tabular*") {
1292 width = lyx::translate_len(p.getArg('{', '}'));
1293 eat_whitespace(p, os, parent_context, false);
1295 parent_context.check_layout(os);
1296 begin_inset(os, "Tabular ");
1297 handle_tabular(p, os, name, width, parent_context);
1302 else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
1303 eat_whitespace(p, os, parent_context, false);
1304 string const opt = p.hasOpt() ? p.getArg('[', ']') : string();
1305 eat_whitespace(p, os, parent_context, false);
1306 parent_context.check_layout(os);
1307 begin_inset(os, "Float " + unstarred_name + "\n");
1308 // store the float type for subfloats
1309 // subfloats only work with figures and tables
1310 if (unstarred_name == "figure")
1311 float_type = unstarred_name;
1312 else if (unstarred_name == "table")
1313 float_type = unstarred_name;
1317 os << "placement " << opt << '\n';
1318 if (contains(opt, "H"))
1319 preamble.registerAutomaticallyLoadedPackage("float");
1321 Floating const & fl = parent_context.textclass.floats()
1322 .getType(unstarred_name);
1323 if (!fl.floattype().empty() && fl.usesFloatPkg())
1324 preamble.registerAutomaticallyLoadedPackage("float");
1327 os << "wide " << convert<string>(is_starred)
1328 << "\nsideways false"
1329 << "\nstatus open\n\n";
1330 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1332 // We don't need really a new paragraph, but
1333 // we must make sure that the next item gets a \begin_layout.
1334 parent_context.new_paragraph(os);
1336 // the float is parsed thus delete the type
1340 else if (unstarred_name == "sidewaysfigure"
1341 || unstarred_name == "sidewaystable") {
1342 eat_whitespace(p, os, parent_context, false);
1343 parent_context.check_layout(os);
1344 if (unstarred_name == "sidewaysfigure")
1345 begin_inset(os, "Float figure\n");
1347 begin_inset(os, "Float table\n");
1348 os << "wide " << convert<string>(is_starred)
1349 << "\nsideways true"
1350 << "\nstatus open\n\n";
1351 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1353 // We don't need really a new paragraph, but
1354 // we must make sure that the next item gets a \begin_layout.
1355 parent_context.new_paragraph(os);
1357 preamble.registerAutomaticallyLoadedPackage("rotfloat");
1360 else if (name == "wrapfigure" || name == "wraptable") {
1361 // syntax is \begin{wrapfigure}[lines]{placement}[overhang]{width}
1362 eat_whitespace(p, os, parent_context, false);
1363 parent_context.check_layout(os);
1366 string overhang = "0col%";
1369 lines = p.getArg('[', ']');
1370 string const placement = p.getArg('{', '}');
1372 overhang = p.getArg('[', ']');
1373 string const width = p.getArg('{', '}');
1375 if (name == "wrapfigure")
1376 begin_inset(os, "Wrap figure\n");
1378 begin_inset(os, "Wrap table\n");
1379 os << "lines " << lines
1380 << "\nplacement " << placement
1381 << "\noverhang " << lyx::translate_len(overhang)
1382 << "\nwidth " << lyx::translate_len(width)
1383 << "\nstatus open\n\n";
1384 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1386 // We don't need really a new paragraph, but
1387 // we must make sure that the next item gets a \begin_layout.
1388 parent_context.new_paragraph(os);
1390 preamble.registerAutomaticallyLoadedPackage("wrapfig");
1393 else if (name == "minipage") {
1394 eat_whitespace(p, os, parent_context, false);
1395 // Test whether this is an outer box of a shaded box
1397 // swallow arguments
1398 while (p.hasOpt()) {
1400 p.skip_spaces(true);
1403 p.skip_spaces(true);
1404 Token t = p.get_token();
1405 bool shaded = false;
1406 if (t.asInput() == "\\begin") {
1407 p.skip_spaces(true);
1408 if (p.getArg('{', '}') == "shaded")
1413 parse_outer_box(p, os, FLAG_END, outer,
1414 parent_context, name, "shaded");
1416 parse_box(p, os, 0, FLAG_END, outer, parent_context,
1421 else if (name == "comment") {
1422 eat_whitespace(p, os, parent_context, false);
1423 parent_context.check_layout(os);
1424 begin_inset(os, "Note Comment\n");
1425 os << "status open\n";
1426 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1429 skip_braces(p); // eat {} that might by set by LyX behind comments
1430 preamble.registerAutomaticallyLoadedPackage("verbatim");
1433 else if (name == "verbatim") {
1434 os << "\n\\end_layout\n\n\\begin_layout Verbatim\n";
1435 string const s = p.plainEnvironment("verbatim");
1436 string::const_iterator it2 = s.begin();
1437 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
1439 os << "\\backslash ";
1440 else if (*it == '\n') {
1442 // avoid adding an empty paragraph at the end
1443 // FIXME: if there are 2 consecutive spaces at the end ignore it
1444 // because LyX will re-add a \n
1445 // This hack must be removed once bug 8049 is fixed!
1446 if ((it + 1 != et) && (it + 2 != et || *it2 != '\n'))
1447 os << "\n\\end_layout\n\\begin_layout Verbatim\n";
1451 os << "\n\\end_layout\n\n";
1453 // reset to Standard layout
1454 os << "\n\\begin_layout Standard\n";
1457 else if (name == "CJK") {
1458 // the scheme is \begin{CJK}{encoding}{mapping}{text}
1459 // It is impossible to decide if a CJK environment was in its own paragraph or within
1460 // a line. We therefore always assume a paragraph since the latter is a rare case.
1461 eat_whitespace(p, os, parent_context, false);
1462 parent_context.check_end_layout(os);
1463 // store the encoding to be able to reset it
1464 string const encoding_old = p.encoding_latex_;
1465 string const encoding = p.getArg('{', '}');
1466 // SJIS and BIG5 don't work with LaTeX according to the comment in unicode.cpp
1467 // JIS does not work with LyX's encoding conversion
1468 if (encoding != "SJIS" && encoding != "BIG5" && encoding != "JIS")
1469 p.setEncoding(encoding);
1471 p.setEncoding("utf8");
1472 // LyX doesn't support the second argument so if
1473 // this is used we need to output everything as ERT
1474 string const mapping = p.getArg('{', '}');
1475 if ( (!mapping.empty() && mapping != " ")
1476 || (!is_known(encoding, supported_CJK_encodings))) {
1477 parent_context.check_layout(os);
1478 handle_ert(os, "\\begin{" + name + "}{" + encoding + "}{" + mapping + "}",
1480 // we must parse the content as verbatim because e.g. SJIS can contain
1481 // normally invalid characters
1482 string const s = p.plainEnvironment("CJK");
1483 string::const_iterator it2 = s.begin();
1484 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
1486 handle_ert(os, "\\", parent_context);
1487 else if (*it == '$')
1488 handle_ert(os, "$", parent_context);
1493 handle_ert(os, "\\end{" + name + "}",
1496 string const lang = CJK2lyx(encoding);
1497 // store the language because we must reset it at the end
1498 string const lang_old = parent_context.font.language;
1499 parent_context.font.language = lang;
1500 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1501 parent_context.font.language = lang_old;
1502 parent_context.new_paragraph(os);
1504 p.encoding_latex_ = encoding_old;
1508 else if (name == "lyxgreyedout") {
1509 eat_whitespace(p, os, parent_context, false);
1510 parent_context.check_layout(os);
1511 begin_inset(os, "Note Greyedout\n");
1512 os << "status open\n";
1513 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1516 if (!preamble.notefontcolor().empty())
1517 preamble.registerAutomaticallyLoadedPackage("color");
1520 else if (name == "framed" || name == "shaded") {
1521 eat_whitespace(p, os, parent_context, false);
1522 parse_outer_box(p, os, FLAG_END, outer, parent_context, name, "");
1526 else if (name == "lstlisting") {
1527 eat_whitespace(p, os, parent_context, false);
1528 // FIXME handle the automatic color package loading
1529 // uwestoehr asks: In what case color is loaded?
1530 parse_listings(p, os, parent_context, false);
1534 else if (!parent_context.new_layout_allowed)
1535 parse_unknown_environment(p, name, os, FLAG_END, outer,
1538 // Alignment and spacing settings
1539 // FIXME (bug xxxx): These settings can span multiple paragraphs and
1540 // therefore are totally broken!
1541 // Note that \centering, raggedright, and raggedleft cannot be handled, as
1542 // they are commands not environments. They are furthermore switches that
1543 // can be ended by another switches, but also by commands like \footnote or
1544 // \parbox. So the only safe way is to leave them untouched.
1545 else if (name == "center" || name == "centering" ||
1546 name == "flushleft" || name == "flushright" ||
1547 name == "singlespace" || name == "onehalfspace" ||
1548 name == "doublespace" || name == "spacing") {
1549 eat_whitespace(p, os, parent_context, false);
1550 // We must begin a new paragraph if not already done
1551 if (! parent_context.atParagraphStart()) {
1552 parent_context.check_end_layout(os);
1553 parent_context.new_paragraph(os);
1555 if (name == "flushleft")
1556 parent_context.add_extra_stuff("\\align left\n");
1557 else if (name == "flushright")
1558 parent_context.add_extra_stuff("\\align right\n");
1559 else if (name == "center" || name == "centering")
1560 parent_context.add_extra_stuff("\\align center\n");
1561 else if (name == "singlespace")
1562 parent_context.add_extra_stuff("\\paragraph_spacing single\n");
1563 else if (name == "onehalfspace") {
1564 parent_context.add_extra_stuff("\\paragraph_spacing onehalf\n");
1565 preamble.registerAutomaticallyLoadedPackage("setspace");
1566 } else if (name == "doublespace") {
1567 parent_context.add_extra_stuff("\\paragraph_spacing double\n");
1568 preamble.registerAutomaticallyLoadedPackage("setspace");
1569 } else if (name == "spacing") {
1570 parent_context.add_extra_stuff("\\paragraph_spacing other " + p.verbatim_item() + "\n");
1571 preamble.registerAutomaticallyLoadedPackage("setspace");
1573 parse_text(p, os, FLAG_END, outer, parent_context);
1574 // Just in case the environment is empty
1575 parent_context.extra_stuff.erase();
1576 // We must begin a new paragraph to reset the alignment
1577 parent_context.new_paragraph(os);
1581 // The single '=' is meant here.
1582 else if ((newlayout = findLayout(parent_context.textclass, name, false))) {
1583 eat_whitespace(p, os, parent_context, false);
1584 Context context(true, parent_context.textclass, newlayout,
1585 parent_context.layout, parent_context.font);
1586 if (parent_context.deeper_paragraph) {
1587 // We are beginning a nested environment after a
1588 // deeper paragraph inside the outer list environment.
1589 // Therefore we don't need to output a "begin deeper".
1590 context.need_end_deeper = true;
1592 parent_context.check_end_layout(os);
1593 if (last_env == name) {
1594 // we need to output a separator since LyX would export
1595 // the two environments as one otherwise (bug 5716)
1596 docstring const sep = from_ascii("--Separator--");
1597 TeX2LyXDocClass const & textclass(parent_context.textclass);
1598 if (textclass.hasLayout(sep)) {
1599 Context newcontext(parent_context);
1600 newcontext.layout = &(textclass[sep]);
1601 newcontext.check_layout(os);
1602 newcontext.check_end_layout(os);
1604 parent_context.check_layout(os);
1605 begin_inset(os, "Note Note\n");
1606 os << "status closed\n";
1607 Context newcontext(true, textclass,
1608 &(textclass.defaultLayout()));
1609 newcontext.check_layout(os);
1610 newcontext.check_end_layout(os);
1612 parent_context.check_end_layout(os);
1615 switch (context.layout->latextype) {
1616 case LATEX_LIST_ENVIRONMENT:
1617 context.add_par_extra_stuff("\\labelwidthstring "
1618 + p.verbatim_item() + '\n');
1621 case LATEX_BIB_ENVIRONMENT:
1622 p.verbatim_item(); // swallow next arg
1628 context.check_deeper(os);
1629 // handle known optional and required arguments
1630 // layouts require all optional arguments before the required ones
1631 // Unfortunately LyX can't handle arguments of list arguments (bug 7468):
1632 // It is impossible to place anything after the environment name,
1633 // but before the first \\item.
1634 if (context.layout->latextype == LATEX_ENVIRONMENT) {
1635 bool need_layout = true;
1636 unsigned int optargs = 0;
1637 while (optargs < context.layout->optargs) {
1638 eat_whitespace(p, os, context, false);
1639 if (p.next_token().cat() == catEscape ||
1640 p.next_token().character() != '[')
1642 p.get_token(); // eat '['
1644 context.check_layout(os);
1645 need_layout = false;
1647 begin_inset(os, "Argument\n");
1648 os << "status collapsed\n\n";
1649 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
1651 eat_whitespace(p, os, context, false);
1654 unsigned int reqargs = 0;
1655 while (reqargs < context.layout->reqargs) {
1656 eat_whitespace(p, os, context, false);
1657 if (p.next_token().cat() != catBegin)
1659 p.get_token(); // eat '{'
1661 context.check_layout(os);
1662 need_layout = false;
1664 begin_inset(os, "Argument\n");
1665 os << "status collapsed\n\n";
1666 parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context);
1668 eat_whitespace(p, os, context, false);
1672 parse_text(p, os, FLAG_END, outer, context);
1673 context.check_end_layout(os);
1674 if (parent_context.deeper_paragraph) {
1675 // We must suppress the "end deeper" because we
1676 // suppressed the "begin deeper" above.
1677 context.need_end_deeper = false;
1679 context.check_end_deeper(os);
1680 parent_context.new_paragraph(os);
1682 if (!preamble.titleLayoutFound())
1683 preamble.titleLayoutFound(newlayout->intitle);
1684 set<string> const & req = newlayout->requires();
1685 set<string>::const_iterator it = req.begin();
1686 set<string>::const_iterator en = req.end();
1687 for (; it != en; ++it)
1688 preamble.registerAutomaticallyLoadedPackage(*it);
1691 // The single '=' is meant here.
1692 else if ((newinsetlayout = findInsetLayout(parent_context.textclass, name, false))) {
1693 eat_whitespace(p, os, parent_context, false);
1694 parent_context.check_layout(os);
1695 begin_inset(os, "Flex ");
1696 os << to_utf8(newinsetlayout->name()) << '\n'
1697 << "status collapsed\n";
1698 parse_text_in_inset(p, os, FLAG_END, false, parent_context, newinsetlayout);
1702 else if (name == "appendix") {
1703 // This is no good latex style, but it works and is used in some documents...
1704 eat_whitespace(p, os, parent_context, false);
1705 parent_context.check_end_layout(os);
1706 Context context(true, parent_context.textclass, parent_context.layout,
1707 parent_context.layout, parent_context.font);
1708 context.check_layout(os);
1709 os << "\\start_of_appendix\n";
1710 parse_text(p, os, FLAG_END, outer, context);
1711 context.check_end_layout(os);
1715 else if (known_environments.find(name) != known_environments.end()) {
1716 vector<ArgumentType> arguments = known_environments[name];
1717 // The last "argument" denotes wether we may translate the
1718 // environment contents to LyX
1719 // The default required if no argument is given makes us
1720 // compatible with the reLyXre environment.
1721 ArgumentType contents = arguments.empty() ?
1724 if (!arguments.empty())
1725 arguments.pop_back();
1726 // See comment in parse_unknown_environment()
1727 bool const specialfont =
1728 (parent_context.font != parent_context.normalfont);
1729 bool const new_layout_allowed =
1730 parent_context.new_layout_allowed;
1732 parent_context.new_layout_allowed = false;
1733 parse_arguments("\\begin{" + name + "}", arguments, p, os,
1734 outer, parent_context);
1735 if (contents == verbatim)
1736 handle_ert(os, p.verbatimEnvironment(name),
1739 parse_text_snippet(p, os, FLAG_END, outer,
1741 handle_ert(os, "\\end{" + name + "}", parent_context);
1743 parent_context.new_layout_allowed = new_layout_allowed;
1747 parse_unknown_environment(p, name, os, FLAG_END, outer,
1751 active_environments.pop_back();
1755 /// parses a comment and outputs it to \p os.
1756 void parse_comment(Parser & p, ostream & os, Token const & t, Context & context)
1758 LASSERT(t.cat() == catComment, return);
1759 if (!t.cs().empty()) {
1760 context.check_layout(os);
1761 handle_comment(os, '%' + t.cs(), context);
1762 if (p.next_token().cat() == catNewline) {
1763 // A newline after a comment line starts a new
1765 if (context.new_layout_allowed) {
1766 if(!context.atParagraphStart())
1767 // Only start a new paragraph if not already
1768 // done (we might get called recursively)
1769 context.new_paragraph(os);
1771 handle_ert(os, "\n", context);
1772 eat_whitespace(p, os, context, true);
1775 // "%\n" combination
1782 * Reads spaces and comments until the first non-space, non-comment token.
1783 * New paragraphs (double newlines or \\par) are handled like simple spaces
1784 * if \p eatParagraph is true.
1785 * Spaces are skipped, but comments are written to \p os.
1787 void eat_whitespace(Parser & p, ostream & os, Context & context,
1791 Token const & t = p.get_token();
1792 if (t.cat() == catComment)
1793 parse_comment(p, os, t, context);
1794 else if ((! eatParagraph && p.isParagraph()) ||
1795 (t.cat() != catSpace && t.cat() != catNewline)) {
1804 * Set a font attribute, parse text and reset the font attribute.
1805 * \param attribute Attribute name (e.g. \\family, \\shape etc.)
1806 * \param currentvalue Current value of the attribute. Is set to the new
1807 * value during parsing.
1808 * \param newvalue New value of the attribute
1810 void parse_text_attributes(Parser & p, ostream & os, unsigned flags, bool outer,
1811 Context & context, string const & attribute,
1812 string & currentvalue, string const & newvalue)
1814 context.check_layout(os);
1815 string const oldvalue = currentvalue;
1816 currentvalue = newvalue;
1817 os << '\n' << attribute << ' ' << newvalue << "\n";
1818 parse_text_snippet(p, os, flags, outer, context);
1819 context.check_layout(os);
1820 os << '\n' << attribute << ' ' << oldvalue << "\n";
1821 currentvalue = oldvalue;
1825 /// get the arguments of a natbib or jurabib citation command
1826 void get_cite_arguments(Parser & p, bool natbibOrder,
1827 string & before, string & after)
1829 // We need to distinguish "" and "[]", so we can't use p.getOpt().
1831 // text before the citation
1833 // text after the citation
1834 after = p.getFullOpt();
1836 if (!after.empty()) {
1837 before = p.getFullOpt();
1838 if (natbibOrder && !before.empty())
1839 swap(before, after);
1844 /// Convert filenames with TeX macros and/or quotes to something LyX
1846 string const normalize_filename(string const & name)
1848 Parser p(trim(name, "\""));
1851 Token const & t = p.get_token();
1852 if (t.cat() != catEscape)
1854 else if (t.cs() == "lyxdot") {
1855 // This is used by LyX for simple dots in relative
1859 } else if (t.cs() == "space") {
1869 /// Convert \p name from TeX convention (relative to master file) to LyX
1870 /// convention (relative to .lyx file) if it is relative
1871 void fix_relative_filename(string & name)
1873 if (FileName::isAbsolute(name))
1876 name = to_utf8(makeRelPath(from_utf8(makeAbsPath(name, getMasterFilePath()).absFileName()),
1877 from_utf8(getParentFilePath())));
1881 /// Parse a NoWeb Scrap section. The initial "<<" is already parsed.
1882 void parse_noweb(Parser & p, ostream & os, Context & context)
1884 // assemble the rest of the keyword
1888 Token const & t = p.get_token();
1889 if (t.asInput() == ">" && p.next_token().asInput() == ">") {
1892 scrap = (p.good() && p.next_token().asInput() == "=");
1894 name += p.get_token().asInput();
1897 name += t.asInput();
1900 if (!scrap || !context.new_layout_allowed ||
1901 !context.textclass.hasLayout(from_ascii("Scrap"))) {
1902 cerr << "Warning: Could not interpret '" << name
1903 << "'. Ignoring it." << endl;
1907 // We use new_paragraph instead of check_end_layout because the stuff
1908 // following the noweb chunk needs to start with a \begin_layout.
1909 // This may create a new paragraph even if there was none in the
1910 // noweb file, but the alternative is an invalid LyX file. Since
1911 // noweb code chunks are implemented with a layout style in LyX they
1912 // always must be in an own paragraph.
1913 context.new_paragraph(os);
1914 Context newcontext(true, context.textclass,
1915 &context.textclass[from_ascii("Scrap")]);
1916 newcontext.check_layout(os);
1919 Token const & t = p.get_token();
1920 // We abuse the parser a bit, because this is no TeX syntax
1922 if (t.cat() == catEscape)
1923 os << subst(t.asInput(), "\\", "\n\\backslash\n");
1926 Context tmp(false, context.textclass,
1927 &context.textclass[from_ascii("Scrap")]);
1928 tmp.need_end_layout = true;
1929 tmp.check_layout(oss);
1930 os << subst(t.asInput(), "\n", oss.str());
1932 // The scrap chunk is ended by an @ at the beginning of a line.
1933 // After the @ the line may contain a comment and/or
1934 // whitespace, but nothing else.
1935 if (t.asInput() == "@" && p.prev_token().cat() == catNewline &&
1936 (p.next_token().cat() == catSpace ||
1937 p.next_token().cat() == catNewline ||
1938 p.next_token().cat() == catComment)) {
1939 while (p.good() && p.next_token().cat() == catSpace)
1940 os << p.get_token().asInput();
1941 if (p.next_token().cat() == catComment)
1942 // The comment includes a final '\n'
1943 os << p.get_token().asInput();
1945 if (p.next_token().cat() == catNewline)
1952 newcontext.check_end_layout(os);
1956 /// detects \\def, \\long\\def and \\global\\long\\def with ws and comments
1957 bool is_macro(Parser & p)
1959 Token first = p.curr_token();
1960 if (first.cat() != catEscape || !p.good())
1962 if (first.cs() == "def")
1964 if (first.cs() != "global" && first.cs() != "long")
1966 Token second = p.get_token();
1968 while (p.good() && !p.isParagraph() && (second.cat() == catSpace ||
1969 second.cat() == catNewline || second.cat() == catComment)) {
1970 second = p.get_token();
1973 bool secondvalid = second.cat() == catEscape;
1975 bool thirdvalid = false;
1976 if (p.good() && first.cs() == "global" && secondvalid &&
1977 second.cs() == "long") {
1978 third = p.get_token();
1980 while (p.good() && !p.isParagraph() &&
1981 (third.cat() == catSpace ||
1982 third.cat() == catNewline ||
1983 third.cat() == catComment)) {
1984 third = p.get_token();
1987 thirdvalid = third.cat() == catEscape;
1989 for (int i = 0; i < pos; ++i)
1994 return (first.cs() == "global" || first.cs() == "long") &&
1995 second.cs() == "def";
1996 return first.cs() == "global" && second.cs() == "long" &&
1997 third.cs() == "def";
2001 /// Parse a macro definition (assumes that is_macro() returned true)
2002 void parse_macro(Parser & p, ostream & os, Context & context)
2004 context.check_layout(os);
2005 Token first = p.curr_token();
2008 string command = first.asInput();
2009 if (first.cs() != "def") {
2011 eat_whitespace(p, os, context, false);
2012 second = p.curr_token();
2013 command += second.asInput();
2014 if (second.cs() != "def") {
2016 eat_whitespace(p, os, context, false);
2017 third = p.curr_token();
2018 command += third.asInput();
2021 eat_whitespace(p, os, context, false);
2022 string const name = p.get_token().cs();
2023 eat_whitespace(p, os, context, false);
2029 while (p.next_token().cat() != catBegin) {
2030 if (p.next_token().cat() == catParameter) {
2035 // followed by number?
2036 if (p.next_token().cat() == catOther) {
2037 char c = p.getChar();
2039 // number = current arity + 1?
2040 if (c == arity + '0' + 1)
2045 paramtext += p.get_token().cs();
2047 paramtext += p.get_token().cs();
2052 // only output simple (i.e. compatible) macro as FormulaMacros
2053 string ert = '\\' + name + ' ' + paramtext + '{' + p.verbatim_item() + '}';
2055 context.check_layout(os);
2056 begin_inset(os, "FormulaMacro");
2057 os << "\n\\def" << ert;
2060 handle_ert(os, command + ert, context);
2064 void registerExternalTemplatePackages(string const & name)
2066 external::TemplateManager const & etm = external::TemplateManager::get();
2067 external::Template const * const et = etm.getTemplateByName(name);
2070 external::Template::Formats::const_iterator cit = et->formats.end();
2072 cit = et->formats.find("PDFLaTeX");
2073 if (cit == et->formats.end())
2074 // If the template has not specified a PDFLaTeX output,
2075 // we try the LaTeX format.
2076 cit = et->formats.find("LaTeX");
2077 if (cit == et->formats.end())
2079 vector<string>::const_iterator qit = cit->second.requirements.begin();
2080 vector<string>::const_iterator qend = cit->second.requirements.end();
2081 for (; qit != qend; ++qit)
2082 preamble.registerAutomaticallyLoadedPackage(*qit);
2085 } // anonymous namespace
2088 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
2091 Layout const * newlayout = 0;
2092 InsetLayout const * newinsetlayout = 0;
2093 char const * const * where = 0;
2094 // Store the latest bibliographystyle and nocite{*} option
2095 // (needed for bibtex inset)
2097 string bibliographystyle = "default";
2098 bool const use_natbib = preamble.isPackageUsed("natbib");
2099 bool const use_jurabib = preamble.isPackageUsed("jurabib");
2102 Token const & t = p.get_token();
2104 // it is impossible to determine the correct document language if CJK is used.
2105 // Therefore write a note at the beginning of the document
2107 context.check_layout(os);
2108 begin_inset(os, "Note Note\n");
2109 os << "status open\n\\begin_layout Plain Layout\n"
2110 << "\\series bold\n"
2111 << "Important information:\n"
2112 << "\\end_layout\n\n"
2113 << "\\begin_layout Plain Layout\n"
2114 << "This document contains text in Chinese, Japanese or Korean.\n"
2115 << " It was therefore impossible for tex2lyx to set the correct document langue for your document."
2116 << " Please set in the document settings by yourself!\n"
2117 << "\\end_layout\n";
2122 // it is impossible to determine the correct encoding for non-CJK Japanese.
2123 // Therefore write a note at the beginning of the document
2124 if (is_nonCJKJapanese) {
2125 context.check_layout(os);
2126 begin_inset(os, "Note Note\n");
2127 os << "status open\n\\begin_layout Plain Layout\n"
2128 << "\\series bold\n"
2129 << "Important information:\n"
2130 << "\\end_layout\n\n"
2131 << "\\begin_layout Plain Layout\n"
2132 << "This document is in Japanese (non-CJK).\n"
2133 << " It was therefore impossible for tex2lyx to determine the correct encoding."
2134 << " The encoding EUC-JP was assumed. If this is incorrect, please set the correct"
2135 << " encoding in the document settings.\n"
2136 << "\\end_layout\n";
2138 is_nonCJKJapanese = false;
2142 debugToken(cerr, t, flags);
2145 if (flags & FLAG_ITEM) {
2146 if (t.cat() == catSpace)
2149 flags &= ~FLAG_ITEM;
2150 if (t.cat() == catBegin) {
2151 // skip the brace and collect everything to the next matching
2153 flags |= FLAG_BRACE_LAST;
2157 // handle only this single token, leave the loop if done
2158 flags |= FLAG_LEAVE;
2161 if (t.cat() != catEscape && t.character() == ']' &&
2162 (flags & FLAG_BRACK_LAST))
2164 if (t.cat() == catEnd && (flags & FLAG_BRACE_LAST))
2167 // If there is anything between \end{env} and \begin{env} we
2168 // don't need to output a separator.
2169 if (t.cat() != catSpace && t.cat() != catNewline &&
2170 t.asInput() != "\\begin")
2176 if (t.cat() == catMath) {
2177 // we are inside some text mode thingy, so opening new math is allowed
2178 context.check_layout(os);
2179 begin_inset(os, "Formula ");
2180 Token const & n = p.get_token();
2181 bool const display(n.cat() == catMath && outer);
2183 // TeX's $$...$$ syntax for displayed math
2185 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
2187 p.get_token(); // skip the second '$' token
2189 // simple $...$ stuff
2192 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
2197 // Prevent the conversion of a line break to a
2198 // space (bug 7668). This does not change the
2199 // output, but looks ugly in LyX.
2200 eat_whitespace(p, os, context, false);
2204 else if (t.cat() == catSuper || t.cat() == catSub)
2205 cerr << "catcode " << t << " illegal in text mode\n";
2207 // Basic support for english quotes. This should be
2208 // extended to other quotes, but is not so easy (a
2209 // left english quote is the same as a right german
2211 else if (t.asInput() == "`" && p.next_token().asInput() == "`") {
2212 context.check_layout(os);
2213 begin_inset(os, "Quotes ");
2219 else if (t.asInput() == "'" && p.next_token().asInput() == "'") {
2220 context.check_layout(os);
2221 begin_inset(os, "Quotes ");
2228 else if (t.asInput() == ">" && p.next_token().asInput() == ">") {
2229 context.check_layout(os);
2230 begin_inset(os, "Quotes ");
2237 else if (t.asInput() == "<" && p.next_token().asInput() == "<") {
2238 context.check_layout(os);
2239 begin_inset(os, "Quotes ");
2246 else if (t.asInput() == "<"
2247 && p.next_token().asInput() == "<" && noweb_mode) {
2249 parse_noweb(p, os, context);
2252 else if (t.cat() == catSpace || (t.cat() == catNewline && ! p.isParagraph()))
2253 check_space(p, os, context);
2255 else if (t.character() == '[' && noweb_mode &&
2256 p.next_token().character() == '[') {
2257 // These can contain underscores
2259 string const s = p.getFullOpt() + ']';
2260 if (p.next_token().character() == ']')
2263 cerr << "Warning: Inserting missing ']' in '"
2264 << s << "'." << endl;
2265 handle_ert(os, s, context);
2268 else if (t.cat() == catLetter) {
2269 context.check_layout(os);
2270 // Workaround for bug 4752.
2271 // FIXME: This whole code block needs to be removed
2272 // when the bug is fixed and tex2lyx produces
2273 // the updated file format.
2274 // The replacement algorithm in LyX is so stupid that
2275 // it even translates a phrase if it is part of a word.
2276 bool handled = false;
2277 for (int const * l = known_phrase_lengths; *l; ++l) {
2278 string phrase = t.cs();
2279 for (int i = 1; i < *l && p.next_token().isAlnumASCII(); ++i)
2280 phrase += p.get_token().cs();
2281 if (is_known(phrase, known_coded_phrases)) {
2282 handle_ert(os, phrase, context);
2286 for (size_t i = 1; i < phrase.length(); ++i)
2294 else if (t.cat() == catOther ||
2295 t.cat() == catAlign ||
2296 t.cat() == catParameter) {
2297 // This translates "&" to "\\&" which may be wrong...
2298 context.check_layout(os);
2302 else if (p.isParagraph()) {
2303 if (context.new_layout_allowed)
2304 context.new_paragraph(os);
2306 handle_ert(os, "\\par ", context);
2307 eat_whitespace(p, os, context, true);
2310 else if (t.cat() == catActive) {
2311 context.check_layout(os);
2312 if (t.character() == '~') {
2313 if (context.layout->free_spacing)
2316 begin_inset(os, "space ~\n");
2323 else if (t.cat() == catBegin) {
2324 Token const next = p.next_token();
2325 Token const end = p.next_next_token();
2326 if (next.cat() == catEnd) {
2328 Token const prev = p.prev_token();
2330 if (p.next_token().character() == '`' ||
2331 (prev.character() == '-' &&
2332 p.next_token().character() == '-'))
2333 ; // ignore it in {}`` or -{}-
2335 handle_ert(os, "{}", context);
2336 } else if (next.cat() == catEscape &&
2337 is_known(next.cs(), known_quotes) &&
2338 end.cat() == catEnd) {
2339 // Something like {\textquoteright} (e.g.
2340 // from writer2latex). LyX writes
2341 // \textquoteright{}, so we may skip the
2342 // braces here for better readability.
2343 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2346 context.check_layout(os);
2347 // special handling of font attribute changes
2348 Token const prev = p.prev_token();
2349 TeXFont const oldFont = context.font;
2350 if (next.character() == '[' ||
2351 next.character() == ']' ||
2352 next.character() == '*') {
2354 if (p.next_token().cat() == catEnd) {
2359 handle_ert(os, "{", context);
2360 parse_text_snippet(p, os,
2363 handle_ert(os, "}", context);
2365 } else if (! context.new_layout_allowed) {
2366 handle_ert(os, "{", context);
2367 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2369 handle_ert(os, "}", context);
2370 } else if (is_known(next.cs(), known_sizes)) {
2371 // next will change the size, so we must
2373 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2375 if (!context.atParagraphStart())
2377 << context.font.size << "\n";
2378 } else if (is_known(next.cs(), known_font_families)) {
2379 // next will change the font family, so we
2380 // must reset it here
2381 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2383 if (!context.atParagraphStart())
2385 << context.font.family << "\n";
2386 } else if (is_known(next.cs(), known_font_series)) {
2387 // next will change the font series, so we
2388 // must reset it here
2389 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2391 if (!context.atParagraphStart())
2393 << context.font.series << "\n";
2394 } else if (is_known(next.cs(), known_font_shapes)) {
2395 // next will change the font shape, so we
2396 // must reset it here
2397 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2399 if (!context.atParagraphStart())
2401 << context.font.shape << "\n";
2402 } else if (is_known(next.cs(), known_old_font_families) ||
2403 is_known(next.cs(), known_old_font_series) ||
2404 is_known(next.cs(), known_old_font_shapes)) {
2405 // next will change the font family, series
2406 // and shape, so we must reset it here
2407 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2409 if (!context.atParagraphStart())
2411 << context.font.family
2413 << context.font.series
2415 << context.font.shape << "\n";
2417 handle_ert(os, "{", context);
2418 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2420 handle_ert(os, "}", context);
2425 else if (t.cat() == catEnd) {
2426 if (flags & FLAG_BRACE_LAST) {
2429 cerr << "stray '}' in text\n";
2430 handle_ert(os, "}", context);
2433 else if (t.cat() == catComment)
2434 parse_comment(p, os, t, context);
2437 // control sequences
2440 else if (t.cs() == "(") {
2441 context.check_layout(os);
2442 begin_inset(os, "Formula");
2444 parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
2449 else if (t.cs() == "[") {
2450 context.check_layout(os);
2451 begin_inset(os, "Formula");
2453 parse_math(p, os, FLAG_EQUATION, MATH_MODE);
2456 // Prevent the conversion of a line break to a space
2457 // (bug 7668). This does not change the output, but
2458 // looks ugly in LyX.
2459 eat_whitespace(p, os, context, false);
2462 else if (t.cs() == "begin")
2463 parse_environment(p, os, outer, last_env,
2466 else if (t.cs() == "end") {
2467 if (flags & FLAG_END) {
2468 // eat environment name
2469 string const name = p.getArg('{', '}');
2470 if (name != active_environment())
2471 cerr << "\\end{" + name + "} does not match \\begin{"
2472 + active_environment() + "}\n";
2475 p.error("found 'end' unexpectedly");
2478 else if (t.cs() == "item") {
2480 bool const optarg = p.hasOpt();
2482 // FIXME: This swallows comments, but we cannot use
2483 // eat_whitespace() since we must not output
2484 // anything before the item.
2485 p.skip_spaces(true);
2486 s = p.verbatimOption();
2488 p.skip_spaces(false);
2490 context.check_layout(os);
2491 if (context.has_item) {
2492 // An item in an unknown list-like environment
2493 // FIXME: Do this in check_layout()!
2494 context.has_item = false;
2496 handle_ert(os, "\\item", context);
2498 handle_ert(os, "\\item ", context);
2501 if (context.layout->labeltype != LABEL_MANUAL) {
2502 // LyX does not support \item[\mybullet]
2503 // in itemize environments
2505 os << parse_text_snippet(p2,
2506 FLAG_BRACK_LAST, outer, context);
2507 } else if (!s.empty()) {
2508 // LyX adds braces around the argument,
2509 // so we need to remove them here.
2510 if (s.size() > 2 && s[0] == '{' &&
2511 s[s.size()-1] == '}')
2512 s = s.substr(1, s.size()-2);
2513 // If the argument contains a space we
2514 // must put it into ERT: Otherwise LyX
2515 // would misinterpret the space as
2516 // item delimiter (bug 7663)
2517 if (contains(s, ' ')) {
2518 handle_ert(os, s, context);
2521 os << parse_text_snippet(p2,
2525 // The space is needed to separate the
2526 // item from the rest of the sentence.
2528 eat_whitespace(p, os, context, false);
2533 else if (t.cs() == "bibitem") {
2535 context.check_layout(os);
2536 eat_whitespace(p, os, context, false);
2537 string label = convert_command_inset_arg(p.verbatimOption());
2538 string key = convert_command_inset_arg(p.verbatim_item());
2539 if (contains(label, '\\') || contains(key, '\\')) {
2540 // LyX can't handle LaTeX commands in labels or keys
2541 handle_ert(os, t.asInput() + '[' + label +
2542 "]{" + p.verbatim_item() + '}',
2545 begin_command_inset(os, "bibitem", "bibitem");
2546 os << "label \"" << label << "\"\n"
2547 "key \"" << key << "\"\n";
2552 else if (is_macro(p)) {
2553 // catch the case of \def\inputGnumericTable
2555 if (t.cs() == "def") {
2556 Token second = p.next_token();
2557 if (second.cs() == "inputGnumericTable") {
2561 Token third = p.get_token();
2563 if (third.cs() == "input") {
2567 string name = normalize_filename(p.verbatim_item());
2568 string const path = getMasterFilePath();
2569 // We want to preserve relative / absolute filenames,
2570 // therefore path is only used for testing
2571 // The file extension is in every case ".tex".
2572 // So we need to remove this extension and check for
2573 // the original one.
2574 name = removeExtension(name);
2575 if (!makeAbsPath(name, path).exists()) {
2576 char const * const Gnumeric_formats[] = {"gnumeric",
2578 string const Gnumeric_name =
2579 find_file(name, path, Gnumeric_formats);
2580 if (!Gnumeric_name.empty())
2581 name = Gnumeric_name;
2583 if (makeAbsPath(name, path).exists())
2584 fix_relative_filename(name);
2586 cerr << "Warning: Could not find file '"
2587 << name << "'." << endl;
2588 context.check_layout(os);
2589 begin_inset(os, "External\n\ttemplate ");
2590 os << "GnumericSpreadsheet\n\tfilename "
2593 context.check_layout(os);
2595 // register the packages that are automatically reloaded
2596 // by the Gnumeric template
2597 registerExternalTemplatePackages("GnumericSpreadsheet");
2602 parse_macro(p, os, context);
2605 else if (t.cs() == "noindent") {
2607 context.add_par_extra_stuff("\\noindent\n");
2610 else if (t.cs() == "appendix") {
2611 context.add_par_extra_stuff("\\start_of_appendix\n");
2612 // We need to start a new paragraph. Otherwise the
2613 // appendix in 'bla\appendix\chapter{' would start
2615 context.new_paragraph(os);
2616 // We need to make sure that the paragraph is
2617 // generated even if it is empty. Otherwise the
2618 // appendix in '\par\appendix\par\chapter{' would
2620 context.check_layout(os);
2621 // FIXME: This is a hack to prevent paragraph
2622 // deletion if it is empty. Handle this better!
2624 "%dummy comment inserted by tex2lyx to "
2625 "ensure that this paragraph is not empty",
2627 // Both measures above may generate an additional
2628 // empty paragraph, but that does not hurt, because
2629 // whitespace does not matter here.
2630 eat_whitespace(p, os, context, true);
2633 // Must catch empty dates before findLayout is called below
2634 else if (t.cs() == "date") {
2635 eat_whitespace(p, os, context, false);
2637 string const date = p.verbatim_item();
2640 preamble.suppressDate(true);
2643 preamble.suppressDate(false);
2644 if (context.new_layout_allowed &&
2645 (newlayout = findLayout(context.textclass,
2648 output_command_layout(os, p, outer,
2649 context, newlayout);
2650 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2651 if (!preamble.titleLayoutFound())
2652 preamble.titleLayoutFound(newlayout->intitle);
2653 set<string> const & req = newlayout->requires();
2654 set<string>::const_iterator it = req.begin();
2655 set<string>::const_iterator en = req.end();
2656 for (; it != en; ++it)
2657 preamble.registerAutomaticallyLoadedPackage(*it);
2660 "\\date{" + p.verbatim_item() + '}',
2665 // Starred section headings
2666 // Must attempt to parse "Section*" before "Section".
2667 else if ((p.next_token().asInput() == "*") &&
2668 context.new_layout_allowed &&
2669 (newlayout = findLayout(context.textclass, t.cs() + '*', true))) {
2672 output_command_layout(os, p, outer, context, newlayout);
2674 if (!preamble.titleLayoutFound())
2675 preamble.titleLayoutFound(newlayout->intitle);
2676 set<string> const & req = newlayout->requires();
2677 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
2678 preamble.registerAutomaticallyLoadedPackage(*it);
2681 // Section headings and the like
2682 else if (context.new_layout_allowed &&
2683 (newlayout = findLayout(context.textclass, t.cs(), true))) {
2685 output_command_layout(os, p, outer, context, newlayout);
2687 if (!preamble.titleLayoutFound())
2688 preamble.titleLayoutFound(newlayout->intitle);
2689 set<string> const & req = newlayout->requires();
2690 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
2691 preamble.registerAutomaticallyLoadedPackage(*it);
2694 else if (t.cs() == "caption") {
2696 context.check_layout(os);
2698 begin_inset(os, "Caption\n");
2699 Context newcontext(true, context.textclass);
2700 newcontext.font = context.font;
2701 newcontext.check_layout(os);
2702 if (p.next_token().cat() != catEscape &&
2703 p.next_token().character() == '[') {
2704 p.get_token(); // eat '['
2705 begin_inset(os, "Argument\n");
2706 os << "status collapsed\n";
2707 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
2709 eat_whitespace(p, os, context, false);
2711 parse_text(p, os, FLAG_ITEM, outer, context);
2712 context.check_end_layout(os);
2713 // We don't need really a new paragraph, but
2714 // we must make sure that the next item gets a \begin_layout.
2715 context.new_paragraph(os);
2718 newcontext.check_end_layout(os);
2721 else if (t.cs() == "subfloat") {
2722 // the syntax is \subfloat[caption]{content}
2723 // if it is a table of figure depends on the surrounding float
2724 bool has_caption = false;
2726 // do nothing if there is no outer float
2727 if (!float_type.empty()) {
2728 context.check_layout(os);
2730 begin_inset(os, "Float " + float_type + "\n");
2732 << "\nsideways false"
2733 << "\nstatus collapsed\n\n";
2736 if (p.next_token().cat() != catEscape &&
2737 p.next_token().character() == '[') {
2738 p.get_token(); // eat '['
2739 caption = parse_text_snippet(p, FLAG_BRACK_LAST, outer, context);
2743 parse_text_in_inset(p, os, FLAG_ITEM, outer, context);
2744 // the caption comes always as the last
2746 // we must make sure that the caption gets a \begin_layout
2747 os << "\n\\begin_layout Plain Layout";
2749 begin_inset(os, "Caption\n");
2750 Context newcontext(true, context.textclass);
2751 newcontext.font = context.font;
2752 newcontext.check_layout(os);
2753 os << caption << "\n";
2754 newcontext.check_end_layout(os);
2755 // We don't need really a new paragraph, but
2756 // we must make sure that the next item gets a \begin_layout.
2757 //newcontext.new_paragraph(os);
2761 // We don't need really a new paragraph, but
2762 // we must make sure that the next item gets a \begin_layout.
2764 context.new_paragraph(os);
2767 context.check_end_layout(os);
2768 // close the layout we opened
2770 os << "\n\\end_layout\n";
2772 // if the float type is not supported or there is no surrounding float
2775 string opt_arg = convert_command_inset_arg(p.getArg('[', ']'));
2776 handle_ert(os, t.asInput() + '[' + opt_arg +
2777 "]{" + p.verbatim_item() + '}', context);
2779 handle_ert(os, t.asInput() + "{" + p.verbatim_item() + '}', context);
2783 else if (t.cs() == "includegraphics") {
2784 bool const clip = p.next_token().asInput() == "*";
2787 string const arg = p.getArg('[', ']');
2788 map<string, string> opts;
2789 vector<string> keys;
2790 split_map(arg, opts, keys);
2792 opts["clip"] = string();
2793 string name = normalize_filename(p.verbatim_item());
2795 string const path = getMasterFilePath();
2796 // We want to preserve relative / absolute filenames,
2797 // therefore path is only used for testing
2798 if (!makeAbsPath(name, path).exists()) {
2799 // The file extension is probably missing.
2800 // Now try to find it out.
2801 string const dvips_name =
2802 find_file(name, path,
2803 known_dvips_graphics_formats);
2804 string const pdftex_name =
2805 find_file(name, path,
2806 known_pdftex_graphics_formats);
2807 if (!dvips_name.empty()) {
2808 if (!pdftex_name.empty()) {
2809 cerr << "This file contains the "
2811 "\"\\includegraphics{"
2813 "However, files\n\""
2814 << dvips_name << "\" and\n\""
2815 << pdftex_name << "\"\n"
2816 "both exist, so I had to make a "
2817 "choice and took the first one.\n"
2818 "Please move the unwanted one "
2819 "someplace else and try again\n"
2820 "if my choice was wrong."
2824 } else if (!pdftex_name.empty()) {
2830 if (makeAbsPath(name, path).exists())
2831 fix_relative_filename(name);
2833 cerr << "Warning: Could not find graphics file '"
2834 << name << "'." << endl;
2836 context.check_layout(os);
2837 begin_inset(os, "Graphics ");
2838 os << "\n\tfilename " << name << '\n';
2839 if (opts.find("width") != opts.end())
2841 << translate_len(opts["width"]) << '\n';
2842 if (opts.find("height") != opts.end())
2844 << translate_len(opts["height"]) << '\n';
2845 if (opts.find("scale") != opts.end()) {
2846 istringstream iss(opts["scale"]);
2850 os << "\tscale " << val << '\n';
2852 if (opts.find("angle") != opts.end()) {
2853 os << "\trotateAngle "
2854 << opts["angle"] << '\n';
2855 vector<string>::const_iterator a =
2856 find(keys.begin(), keys.end(), "angle");
2857 vector<string>::const_iterator s =
2858 find(keys.begin(), keys.end(), "width");
2859 if (s == keys.end())
2860 s = find(keys.begin(), keys.end(), "height");
2861 if (s == keys.end())
2862 s = find(keys.begin(), keys.end(), "scale");
2863 if (s != keys.end() && distance(s, a) > 0)
2864 os << "\tscaleBeforeRotation\n";
2866 if (opts.find("origin") != opts.end()) {
2868 string const opt = opts["origin"];
2869 if (opt.find('l') != string::npos) ss << "left";
2870 if (opt.find('r') != string::npos) ss << "right";
2871 if (opt.find('c') != string::npos) ss << "center";
2872 if (opt.find('t') != string::npos) ss << "Top";
2873 if (opt.find('b') != string::npos) ss << "Bottom";
2874 if (opt.find('B') != string::npos) ss << "Baseline";
2875 if (!ss.str().empty())
2876 os << "\trotateOrigin " << ss.str() << '\n';
2878 cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
2880 if (opts.find("keepaspectratio") != opts.end())
2881 os << "\tkeepAspectRatio\n";
2882 if (opts.find("clip") != opts.end())
2884 if (opts.find("draft") != opts.end())
2886 if (opts.find("bb") != opts.end())
2887 os << "\tBoundingBox "
2888 << opts["bb"] << '\n';
2889 int numberOfbbOptions = 0;
2890 if (opts.find("bbllx") != opts.end())
2891 numberOfbbOptions++;
2892 if (opts.find("bblly") != opts.end())
2893 numberOfbbOptions++;
2894 if (opts.find("bburx") != opts.end())
2895 numberOfbbOptions++;
2896 if (opts.find("bbury") != opts.end())
2897 numberOfbbOptions++;
2898 if (numberOfbbOptions == 4)
2899 os << "\tBoundingBox "
2900 << opts["bbllx"] << " " << opts["bblly"] << " "
2901 << opts["bburx"] << " " << opts["bbury"] << '\n';
2902 else if (numberOfbbOptions > 0)
2903 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
2904 numberOfbbOptions = 0;
2905 if (opts.find("natwidth") != opts.end())
2906 numberOfbbOptions++;
2907 if (opts.find("natheight") != opts.end())
2908 numberOfbbOptions++;
2909 if (numberOfbbOptions == 2)
2910 os << "\tBoundingBox 0bp 0bp "
2911 << opts["natwidth"] << " " << opts["natheight"] << '\n';
2912 else if (numberOfbbOptions > 0)
2913 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
2914 ostringstream special;
2915 if (opts.find("hiresbb") != opts.end())
2916 special << "hiresbb,";
2917 if (opts.find("trim") != opts.end())
2919 if (opts.find("viewport") != opts.end())
2920 special << "viewport=" << opts["viewport"] << ',';
2921 if (opts.find("totalheight") != opts.end())
2922 special << "totalheight=" << opts["totalheight"] << ',';
2923 if (opts.find("type") != opts.end())
2924 special << "type=" << opts["type"] << ',';
2925 if (opts.find("ext") != opts.end())
2926 special << "ext=" << opts["ext"] << ',';
2927 if (opts.find("read") != opts.end())
2928 special << "read=" << opts["read"] << ',';
2929 if (opts.find("command") != opts.end())
2930 special << "command=" << opts["command"] << ',';
2931 string s_special = special.str();
2932 if (!s_special.empty()) {
2933 // We had special arguments. Remove the trailing ','.
2934 os << "\tspecial " << s_special.substr(0, s_special.size() - 1) << '\n';
2936 // TODO: Handle the unknown settings better.
2937 // Warn about invalid options.
2938 // Check whether some option was given twice.
2940 preamble.registerAutomaticallyLoadedPackage("graphicx");
2943 else if (t.cs() == "footnote" ||
2944 (t.cs() == "thanks" && context.layout->intitle)) {
2946 context.check_layout(os);
2947 begin_inset(os, "Foot\n");
2948 os << "status collapsed\n\n";
2949 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
2953 else if (t.cs() == "marginpar") {
2955 context.check_layout(os);
2956 begin_inset(os, "Marginal\n");
2957 os << "status collapsed\n\n";
2958 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
2962 else if (t.cs() == "lstinline") {
2964 parse_listings(p, os, context, true);
2967 else if (t.cs() == "ensuremath") {
2969 context.check_layout(os);
2970 string const s = p.verbatim_item();
2971 //FIXME: this never triggers in UTF8
2972 if (s == "\xb1" || s == "\xb3" || s == "\xb2" || s == "\xb5")
2975 handle_ert(os, "\\ensuremath{" + s + "}",
2979 else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
2980 if (preamble.titleLayoutFound()) {
2982 skip_spaces_braces(p);
2984 handle_ert(os, t.asInput(), context);
2987 else if (t.cs() == "tableofcontents" || t.cs() == "lstlistoflistings") {
2988 context.check_layout(os);
2989 begin_command_inset(os, "toc", t.cs());
2991 skip_spaces_braces(p);
2992 if (t.cs() == "lstlistoflistings")
2993 preamble.registerAutomaticallyLoadedPackage("listings");
2996 else if (t.cs() == "listoffigures") {
2997 context.check_layout(os);
2998 begin_inset(os, "FloatList figure\n");
3000 skip_spaces_braces(p);
3003 else if (t.cs() == "listoftables") {
3004 context.check_layout(os);
3005 begin_inset(os, "FloatList table\n");
3007 skip_spaces_braces(p);
3010 else if (t.cs() == "listof") {
3011 p.skip_spaces(true);
3012 string const name = p.get_token().cs();
3013 if (context.textclass.floats().typeExist(name)) {
3014 context.check_layout(os);
3015 begin_inset(os, "FloatList ");
3018 p.get_token(); // swallow second arg
3020 handle_ert(os, "\\listof{" + name + "}", context);
3023 else if ((where = is_known(t.cs(), known_text_font_families)))
3024 parse_text_attributes(p, os, FLAG_ITEM, outer,
3025 context, "\\family", context.font.family,
3026 known_coded_font_families[where - known_text_font_families]);
3028 else if ((where = is_known(t.cs(), known_text_font_series)))
3029 parse_text_attributes(p, os, FLAG_ITEM, outer,
3030 context, "\\series", context.font.series,
3031 known_coded_font_series[where - known_text_font_series]);
3033 else if ((where = is_known(t.cs(), known_text_font_shapes)))
3034 parse_text_attributes(p, os, FLAG_ITEM, outer,
3035 context, "\\shape", context.font.shape,
3036 known_coded_font_shapes[where - known_text_font_shapes]);
3038 else if (t.cs() == "textnormal" || t.cs() == "normalfont") {
3039 context.check_layout(os);
3040 TeXFont oldFont = context.font;
3041 context.font.init();
3042 context.font.size = oldFont.size;
3043 os << "\n\\family " << context.font.family << "\n";
3044 os << "\n\\series " << context.font.series << "\n";
3045 os << "\n\\shape " << context.font.shape << "\n";
3046 if (t.cs() == "textnormal") {
3047 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
3048 output_font_change(os, context.font, oldFont);
3049 context.font = oldFont;
3051 eat_whitespace(p, os, context, false);
3054 else if (t.cs() == "textcolor") {
3055 // scheme is \textcolor{color name}{text}
3056 string const color = p.verbatim_item();
3057 // we only support the predefined colors of the color package
3058 if (color == "black" || color == "blue" || color == "cyan"
3059 || color == "green" || color == "magenta" || color == "red"
3060 || color == "white" || color == "yellow") {
3061 context.check_layout(os);
3062 os << "\n\\color " << color << "\n";
3063 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
3064 context.check_layout(os);
3065 os << "\n\\color inherit\n";
3066 preamble.registerAutomaticallyLoadedPackage("color");
3068 // for custom defined colors
3069 handle_ert(os, t.asInput() + "{" + color + "}", context);
3072 else if (t.cs() == "underbar" || t.cs() == "uline") {
3073 // \underbar is not 100% correct (LyX outputs \uline
3074 // of ulem.sty). The difference is that \ulem allows
3075 // line breaks, and \underbar does not.
3076 // Do NOT handle \underline.
3077 // \underbar cuts through y, g, q, p etc.,
3078 // \underline does not.
3079 context.check_layout(os);
3080 os << "\n\\bar under\n";
3081 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
3082 context.check_layout(os);
3083 os << "\n\\bar default\n";
3084 preamble.registerAutomaticallyLoadedPackage("ulem");
3087 else if (t.cs() == "sout") {
3088 context.check_layout(os);
3089 os << "\n\\strikeout on\n";
3090 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
3091 context.check_layout(os);
3092 os << "\n\\strikeout default\n";
3093 preamble.registerAutomaticallyLoadedPackage("ulem");
3096 else if (t.cs() == "uuline" || t.cs() == "uwave" ||
3097 t.cs() == "emph" || t.cs() == "noun") {
3098 context.check_layout(os);
3099 os << "\n\\" << t.cs() << " on\n";
3100 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
3101 context.check_layout(os);
3102 os << "\n\\" << t.cs() << " default\n";
3103 if (t.cs() == "uuline" || t.cs() == "uwave")
3104 preamble.registerAutomaticallyLoadedPackage("ulem");
3107 else if (t.cs() == "lyxadded" || t.cs() == "lyxdeleted") {
3108 context.check_layout(os);
3109 string name = p.getArg('{', '}');
3110 string localtime = p.getArg('{', '}');
3111 preamble.registerAuthor(name);
3112 Author const & author = preamble.getAuthor(name);
3113 // from_ctime() will fail if LyX decides to output the
3114 // time in the text language. It might also use a wrong
3115 // time zone (if the original LyX document was exported
3116 // with a different time zone).
3117 time_t ptime = from_ctime(localtime);
3118 if (ptime == static_cast<time_t>(-1)) {
3119 cerr << "Warning: Could not parse time `" << localtime
3120 << "´ for change tracking, using current time instead.\n";
3121 ptime = current_time();
3123 if (t.cs() == "lyxadded")
3124 os << "\n\\change_inserted ";
3126 os << "\n\\change_deleted ";
3127 os << author.bufferId() << ' ' << ptime << '\n';
3128 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
3129 bool dvipost = LaTeXPackages::isAvailable("dvipost");
3130 bool xcolorulem = LaTeXPackages::isAvailable("ulem") &&
3131 LaTeXPackages::isAvailable("xcolor");
3132 // No need to test for luatex, since luatex comes in
3133 // two flavours (dvi and pdf), like latex, and those
3134 // are detected by pdflatex.
3135 if (pdflatex || xetex) {
3137 preamble.registerAutomaticallyLoadedPackage("ulem");
3138 preamble.registerAutomaticallyLoadedPackage("xcolor");
3139 preamble.registerAutomaticallyLoadedPackage("pdfcolmk");
3143 preamble.registerAutomaticallyLoadedPackage("dvipost");
3144 } else if (xcolorulem) {
3145 preamble.registerAutomaticallyLoadedPackage("ulem");
3146 preamble.registerAutomaticallyLoadedPackage("xcolor");
3151 else if (t.cs() == "phantom" || t.cs() == "hphantom" ||
3152 t.cs() == "vphantom") {
3153 context.check_layout(os);
3154 if (t.cs() == "phantom")
3155 begin_inset(os, "Phantom Phantom\n");
3156 if (t.cs() == "hphantom")
3157 begin_inset(os, "Phantom HPhantom\n");
3158 if (t.cs() == "vphantom")
3159 begin_inset(os, "Phantom VPhantom\n");
3160 os << "status open\n";
3161 parse_text_in_inset(p, os, FLAG_ITEM, outer, context,
3166 else if (t.cs() == "href") {
3167 context.check_layout(os);
3168 string target = p.getArg('{', '}');
3169 string name = p.getArg('{', '}');
3171 size_t i = target.find(':');
3172 if (i != string::npos) {
3173 type = target.substr(0, i + 1);
3174 if (type == "mailto:" || type == "file:")
3175 target = target.substr(i + 1);
3176 // handle the case that name is equal to target, except of "http://"
3177 else if (target.substr(i + 3) == name && type == "http:")
3180 begin_command_inset(os, "href", "href");
3182 os << "name \"" << name << "\"\n";
3183 os << "target \"" << target << "\"\n";
3184 if (type == "mailto:" || type == "file:")
3185 os << "type \"" << type << "\"\n";
3187 skip_spaces_braces(p);
3190 else if (t.cs() == "lyxline") {
3191 // swallow size argument (it is not used anyway)
3193 if (!context.atParagraphStart()) {
3194 // so our line is in the middle of a paragraph
3195 // we need to add a new line, lest this line
3196 // follow the other content on that line and
3197 // run off the side of the page
3198 // FIXME: This may create an empty paragraph,
3199 // but without that it would not be
3200 // possible to set noindent below.
3201 // Fortunately LaTeX does not care
3202 // about the empty paragraph.
3203 context.new_paragraph(os);
3205 if (preamble.indentParagraphs()) {
3206 // we need to unindent, lest the line be too long
3207 context.add_par_extra_stuff("\\noindent\n");
3209 context.check_layout(os);
3210 begin_command_inset(os, "line", "rule");
3211 os << "offset \"0.5ex\"\n"
3212 "width \"100line%\"\n"
3217 else if (t.cs() == "rule") {
3218 string const offset = (p.hasOpt() ? p.getArg('[', ']') : string());
3219 string const width = p.getArg('{', '}');
3220 string const thickness = p.getArg('{', '}');
3221 context.check_layout(os);
3222 begin_command_inset(os, "line", "rule");
3223 if (!offset.empty())
3224 os << "offset \"" << translate_len(offset) << "\"\n";
3225 os << "width \"" << translate_len(width) << "\"\n"
3226 "height \"" << translate_len(thickness) << "\"\n";
3230 else if (is_known(t.cs(), known_phrases) ||
3231 (t.cs() == "protect" &&
3232 p.next_token().cat() == catEscape &&
3233 is_known(p.next_token().cs(), known_phrases))) {
3234 // LyX sometimes puts a \protect in front, so we have to ignore it
3235 // FIXME: This needs to be changed when bug 4752 is fixed.
3237 t.cs() == "protect" ? p.get_token().cs() : t.cs(),
3239 context.check_layout(os);
3240 os << known_coded_phrases[where - known_phrases];
3241 skip_spaces_braces(p);
3244 else if ((where = is_known(t.cs(), known_ref_commands))) {
3245 string const opt = p.getOpt();
3247 context.check_layout(os);
3248 begin_command_inset(os, "ref",
3249 known_coded_ref_commands[where - known_ref_commands]);
3250 os << "reference \""
3251 << convert_command_inset_arg(p.verbatim_item())
3254 if (t.cs() == "vref" || t.cs() == "vpageref")
3255 preamble.registerAutomaticallyLoadedPackage("varioref");
3258 // LyX does not support optional arguments of ref commands
3259 handle_ert(os, t.asInput() + '[' + opt + "]{" +
3260 p.verbatim_item() + "}", context);
3264 else if (use_natbib &&
3265 is_known(t.cs(), known_natbib_commands) &&
3266 ((t.cs() != "citefullauthor" &&
3267 t.cs() != "citeyear" &&
3268 t.cs() != "citeyearpar") ||
3269 p.next_token().asInput() != "*")) {
3270 context.check_layout(os);
3271 string command = t.cs();
3272 if (p.next_token().asInput() == "*") {
3276 if (command == "citefullauthor")
3277 // alternative name for "\\citeauthor*"
3278 command = "citeauthor*";
3280 // text before the citation
3282 // text after the citation
3284 get_cite_arguments(p, true, before, after);
3286 if (command == "cite") {
3287 // \cite without optional argument means
3288 // \citet, \cite with at least one optional
3289 // argument means \citep.
3290 if (before.empty() && after.empty())
3295 if (before.empty() && after == "[]")
3296 // avoid \citet[]{a}
3298 else if (before == "[]" && after == "[]") {
3299 // avoid \citet[][]{a}
3303 // remove the brackets around after and before
3304 if (!after.empty()) {
3306 after.erase(after.length() - 1, 1);
3307 after = convert_command_inset_arg(after);
3309 if (!before.empty()) {
3311 before.erase(before.length() - 1, 1);
3312 before = convert_command_inset_arg(before);
3314 begin_command_inset(os, "citation", command);
3315 os << "after " << '"' << after << '"' << "\n";
3316 os << "before " << '"' << before << '"' << "\n";
3318 << convert_command_inset_arg(p.verbatim_item())
3323 else if (use_jurabib &&
3324 is_known(t.cs(), known_jurabib_commands) &&
3325 (t.cs() == "cite" || p.next_token().asInput() != "*")) {
3326 context.check_layout(os);
3327 string command = t.cs();
3328 if (p.next_token().asInput() == "*") {
3332 char argumentOrder = '\0';
3333 vector<string> const options =
3334 preamble.getPackageOptions("jurabib");
3335 if (find(options.begin(), options.end(),
3336 "natbiborder") != options.end())
3337 argumentOrder = 'n';
3338 else if (find(options.begin(), options.end(),
3339 "jurabiborder") != options.end())
3340 argumentOrder = 'j';
3342 // text before the citation
3344 // text after the citation
3346 get_cite_arguments(p, argumentOrder != 'j', before, after);
3348 string const citation = p.verbatim_item();
3349 if (!before.empty() && argumentOrder == '\0') {
3350 cerr << "Warning: Assuming argument order "
3351 "of jurabib version 0.6 for\n'"
3352 << command << before << after << '{'
3353 << citation << "}'.\n"
3354 "Add 'jurabiborder' to the jurabib "
3355 "package options if you used an\n"
3356 "earlier jurabib version." << endl;
3358 if (!after.empty()) {
3360 after.erase(after.length() - 1, 1);
3362 if (!before.empty()) {
3364 before.erase(before.length() - 1, 1);
3366 begin_command_inset(os, "citation", command);
3367 os << "after " << '"' << after << '"' << "\n";
3368 os << "before " << '"' << before << '"' << "\n";
3369 os << "key " << '"' << citation << '"' << "\n";
3373 else if (t.cs() == "cite"
3374 || t.cs() == "nocite") {
3375 context.check_layout(os);
3376 string after = convert_command_inset_arg(p.getArg('[', ']'));
3377 string key = convert_command_inset_arg(p.verbatim_item());
3378 // store the case that it is "\nocite{*}" to use it later for
3381 begin_command_inset(os, "citation", t.cs());
3382 os << "after " << '"' << after << '"' << "\n";
3383 os << "key " << '"' << key << '"' << "\n";
3385 } else if (t.cs() == "nocite")
3389 else if (t.cs() == "index" ||
3390 (t.cs() == "sindex" && preamble.use_indices() == "true")) {
3391 context.check_layout(os);
3392 string const arg = (t.cs() == "sindex" && p.hasOpt()) ?
3393 p.getArg('[', ']') : "";
3394 string const kind = arg.empty() ? "idx" : arg;
3395 begin_inset(os, "Index ");
3396 os << kind << "\nstatus collapsed\n";
3397 parse_text_in_inset(p, os, FLAG_ITEM, false, context, "Index");
3400 preamble.registerAutomaticallyLoadedPackage("splitidx");
3403 else if (t.cs() == "nomenclature") {
3404 context.check_layout(os);
3405 begin_command_inset(os, "nomenclature", "nomenclature");
3406 string prefix = convert_command_inset_arg(p.getArg('[', ']'));
3407 if (!prefix.empty())
3408 os << "prefix " << '"' << prefix << '"' << "\n";
3409 os << "symbol " << '"'
3410 << convert_command_inset_arg(p.verbatim_item());
3411 os << "\"\ndescription \""
3412 << convert_command_inset_arg(p.verbatim_item())
3415 preamble.registerAutomaticallyLoadedPackage("nomencl");
3418 else if (t.cs() == "label") {
3419 context.check_layout(os);
3420 begin_command_inset(os, "label", "label");
3422 << convert_command_inset_arg(p.verbatim_item())
3427 else if (t.cs() == "printindex") {
3428 context.check_layout(os);
3429 begin_command_inset(os, "index_print", "printindex");
3430 os << "type \"idx\"\n";
3432 skip_spaces_braces(p);
3433 preamble.registerAutomaticallyLoadedPackage("makeidx");
3434 if (preamble.use_indices() == "true")
3435 preamble.registerAutomaticallyLoadedPackage("splitidx");
3438 else if (t.cs() == "printnomenclature") {
3440 string width_type = "";
3441 context.check_layout(os);
3442 begin_command_inset(os, "nomencl_print", "printnomenclature");
3443 // case of a custom width
3445 width = p.getArg('[', ']');
3446 width = translate_len(width);
3447 width_type = "custom";
3449 // case of no custom width
3450 // the case of no custom width but the width set
3451 // via \settowidth{\nomlabelwidth}{***} cannot be supported
3452 // because the user could have set anything, not only the width
3453 // of the longest label (which would be width_type = "auto")
3454 string label = convert_command_inset_arg(p.getArg('{', '}'));
3455 if (label.empty() && width_type.empty())
3456 width_type = "none";
3457 os << "set_width \"" << width_type << "\"\n";
3458 if (width_type == "custom")
3459 os << "width \"" << width << '\"';
3461 skip_spaces_braces(p);
3462 preamble.registerAutomaticallyLoadedPackage("nomencl");
3465 else if ((t.cs() == "textsuperscript" || t.cs() == "textsubscript")) {
3466 context.check_layout(os);
3467 begin_inset(os, "script ");
3468 os << t.cs().substr(4) << '\n';
3469 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
3471 if (t.cs() == "textsubscript")
3472 preamble.registerAutomaticallyLoadedPackage("subscript");
3475 else if ((where = is_known(t.cs(), known_quotes))) {
3476 context.check_layout(os);
3477 begin_inset(os, "Quotes ");
3478 os << known_coded_quotes[where - known_quotes];
3480 // LyX adds {} after the quote, so we have to eat
3481 // spaces here if there are any before a possible
3483 eat_whitespace(p, os, context, false);
3487 else if ((where = is_known(t.cs(), known_sizes)) &&
3488 context.new_layout_allowed) {
3489 context.check_layout(os);
3490 TeXFont const oldFont = context.font;
3491 context.font.size = known_coded_sizes[where - known_sizes];
3492 output_font_change(os, oldFont, context.font);
3493 eat_whitespace(p, os, context, false);
3496 else if ((where = is_known(t.cs(), known_font_families)) &&
3497 context.new_layout_allowed) {
3498 context.check_layout(os);
3499 TeXFont const oldFont = context.font;
3500 context.font.family =
3501 known_coded_font_families[where - known_font_families];
3502 output_font_change(os, oldFont, context.font);
3503 eat_whitespace(p, os, context, false);
3506 else if ((where = is_known(t.cs(), known_font_series)) &&
3507 context.new_layout_allowed) {
3508 context.check_layout(os);
3509 TeXFont const oldFont = context.font;
3510 context.font.series =
3511 known_coded_font_series[where - known_font_series];
3512 output_font_change(os, oldFont, context.font);
3513 eat_whitespace(p, os, context, false);
3516 else if ((where = is_known(t.cs(), known_font_shapes)) &&
3517 context.new_layout_allowed) {
3518 context.check_layout(os);
3519 TeXFont const oldFont = context.font;
3520 context.font.shape =
3521 known_coded_font_shapes[where - known_font_shapes];
3522 output_font_change(os, oldFont, context.font);
3523 eat_whitespace(p, os, context, false);
3525 else if ((where = is_known(t.cs(), known_old_font_families)) &&
3526 context.new_layout_allowed) {
3527 context.check_layout(os);
3528 TeXFont const oldFont = context.font;
3529 context.font.init();
3530 context.font.size = oldFont.size;
3531 context.font.family =
3532 known_coded_font_families[where - known_old_font_families];
3533 output_font_change(os, oldFont, context.font);
3534 eat_whitespace(p, os, context, false);
3537 else if ((where = is_known(t.cs(), known_old_font_series)) &&
3538 context.new_layout_allowed) {
3539 context.check_layout(os);
3540 TeXFont const oldFont = context.font;
3541 context.font.init();
3542 context.font.size = oldFont.size;
3543 context.font.series =
3544 known_coded_font_series[where - known_old_font_series];
3545 output_font_change(os, oldFont, context.font);
3546 eat_whitespace(p, os, context, false);
3549 else if ((where = is_known(t.cs(), known_old_font_shapes)) &&
3550 context.new_layout_allowed) {
3551 context.check_layout(os);
3552 TeXFont const oldFont = context.font;
3553 context.font.init();
3554 context.font.size = oldFont.size;
3555 context.font.shape =
3556 known_coded_font_shapes[where - known_old_font_shapes];
3557 output_font_change(os, oldFont, context.font);
3558 eat_whitespace(p, os, context, false);
3561 else if (t.cs() == "selectlanguage") {
3562 context.check_layout(os);
3563 // save the language for the case that a
3564 // \foreignlanguage is used
3565 context.font.language = babel2lyx(p.verbatim_item());
3566 os << "\n\\lang " << context.font.language << "\n";
3569 else if (t.cs() == "foreignlanguage") {
3570 string const lang = babel2lyx(p.verbatim_item());
3571 parse_text_attributes(p, os, FLAG_ITEM, outer,
3573 context.font.language, lang);
3576 else if (prefixIs(t.cs(), "text")
3577 && is_known(t.cs().substr(4), polyglossia_languages)) {
3578 // scheme is \textLANGUAGE{text} where LANGUAGE is in polyglossia_languages[]
3580 // We have to output the whole command if it has an option
3581 // because LyX doesn't support this yet, see bug #8214,
3582 // only if there is a single option specifying a variant, we can handle it.
3584 string langopts = p.getOpt();
3585 // check if the option contains a variant, if yes, extract it
3586 string::size_type pos_var = langopts.find("variant");
3587 string::size_type i = langopts.find(',');
3588 if (pos_var != string::npos){
3590 if (i == string::npos) {
3591 variant = langopts.substr(pos_var + 8, langopts.length() - pos_var - 9);
3592 lang = polyglossia2lyx(variant);
3593 parse_text_attributes(p, os, FLAG_ITEM, outer,
3595 context.font.language, lang);
3598 handle_ert(os, t.asInput() + langopts, context);
3600 handle_ert(os, t.asInput() + langopts, context);
3602 lang = polyglossia2lyx(t.cs().substr(4, string::npos));
3603 parse_text_attributes(p, os, FLAG_ITEM, outer,
3605 context.font.language, lang);
3609 else if (t.cs() == "inputencoding") {
3610 // nothing to write here
3611 string const enc = subst(p.verbatim_item(), "\n", " ");
3615 else if ((where = is_known(t.cs(), known_special_chars))) {
3616 context.check_layout(os);
3617 os << "\\SpecialChar \\"
3618 << known_coded_special_chars[where - known_special_chars]
3620 skip_spaces_braces(p);
3623 else if (t.cs() == "nobreakdash" && p.next_token().asInput() == "-") {
3624 context.check_layout(os);
3625 os << "\\SpecialChar \\nobreakdash-\n";
3629 else if (t.cs() == "textquotedbl") {
3630 context.check_layout(os);
3635 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
3636 context.check_layout(os);
3637 os << "\\SpecialChar \\@.\n";
3641 else if (t.cs() == "-") {
3642 context.check_layout(os);
3643 os << "\\SpecialChar \\-\n";
3646 else if (t.cs() == "textasciitilde") {
3647 context.check_layout(os);
3649 skip_spaces_braces(p);
3652 else if (t.cs() == "textasciicircum") {
3653 context.check_layout(os);
3655 skip_spaces_braces(p);
3658 else if (t.cs() == "textbackslash") {
3659 context.check_layout(os);
3660 os << "\n\\backslash\n";
3661 skip_spaces_braces(p);
3664 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
3665 || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
3667 context.check_layout(os);
3671 else if (t.cs() == "char") {
3672 context.check_layout(os);
3673 if (p.next_token().character() == '`') {
3675 if (p.next_token().cs() == "\"") {
3680 handle_ert(os, "\\char`", context);
3683 handle_ert(os, "\\char", context);
3687 else if (t.cs() == "verb") {
3688 context.check_layout(os);
3689 char const delimiter = p.next_token().character();
3690 string const arg = p.getArg(delimiter, delimiter);
3692 oss << "\\verb" << delimiter << arg << delimiter;
3693 handle_ert(os, oss.str(), context);
3696 // Problem: \= creates a tabstop inside the tabbing environment
3697 // and else an accent. In the latter case we really would want
3698 // \={o} instead of \= o.
3699 else if (t.cs() == "=" && (flags & FLAG_TABBING))
3700 handle_ert(os, t.asInput(), context);
3702 // accents (see Table 6 in Comprehensive LaTeX Symbol List)
3703 else if (t.cs().size() == 1
3704 && contains("\"'.=^`bcdHkrtuv~", t.cs())) {
3705 context.check_layout(os);
3706 // try to see whether the string is in unicodesymbols
3709 string command = t.asInput() + "{"
3710 + trimSpaceAndEol(p.verbatim_item())
3713 docstring s = encodings.fromLaTeXCommand(from_utf8(command),
3714 Encodings::TEXT_CMD | Encodings::MATH_CMD,
3715 termination, rem, &req);
3718 cerr << "When parsing " << command
3719 << ", result is " << to_utf8(s)
3720 << "+" << to_utf8(rem) << endl;
3722 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
3723 preamble.registerAutomaticallyLoadedPackage(*it);
3725 // we did not find a non-ert version
3726 handle_ert(os, command, context);
3729 else if (t.cs() == "\\") {
3730 context.check_layout(os);
3732 handle_ert(os, "\\\\" + p.getOpt(), context);
3733 else if (p.next_token().asInput() == "*") {
3735 // getOpt() eats the following space if there
3736 // is no optional argument, but that is OK
3737 // here since it has no effect in the output.
3738 handle_ert(os, "\\\\*" + p.getOpt(), context);
3741 begin_inset(os, "Newline newline");
3746 else if (t.cs() == "newline" ||
3747 (t.cs() == "linebreak" && !p.hasOpt())) {
3748 context.check_layout(os);
3749 begin_inset(os, "Newline ");
3752 skip_spaces_braces(p);
3755 else if (t.cs() == "input" || t.cs() == "include"
3756 || t.cs() == "verbatiminput") {
3757 string name = t.cs();
3758 if (t.cs() == "verbatiminput"
3759 && p.next_token().asInput() == "*")
3760 name += p.get_token().asInput();
3761 context.check_layout(os);
3762 string filename(normalize_filename(p.getArg('{', '}')));
3763 string const path = getMasterFilePath();
3764 // We want to preserve relative / absolute filenames,
3765 // therefore path is only used for testing
3766 if ((t.cs() == "include" || t.cs() == "input") &&
3767 !makeAbsPath(filename, path).exists()) {
3768 // The file extension is probably missing.
3769 // Now try to find it out.
3770 string const tex_name =
3771 find_file(filename, path,
3772 known_tex_extensions);
3773 if (!tex_name.empty())
3774 filename = tex_name;
3776 bool external = false;
3778 if (makeAbsPath(filename, path).exists()) {
3779 string const abstexname =
3780 makeAbsPath(filename, path).absFileName();
3781 string const abslyxname =
3782 changeExtension(abstexname, ".lyx");
3783 string const absfigname =
3784 changeExtension(abstexname, ".fig");
3785 fix_relative_filename(filename);
3786 string const lyxname =
3787 changeExtension(filename, ".lyx");
3789 external = FileName(absfigname).exists();
3790 if (t.cs() == "input") {
3791 string const ext = getExtension(abstexname);
3793 // Combined PS/LaTeX:
3794 // x.eps, x.pstex_t (old xfig)
3795 // x.pstex, x.pstex_t (new xfig, e.g. 3.2.5)
3796 FileName const absepsname(
3797 changeExtension(abstexname, ".eps"));
3798 FileName const abspstexname(
3799 changeExtension(abstexname, ".pstex"));
3800 bool const xfigeps =
3801 (absepsname.exists() ||
3802 abspstexname.exists()) &&
3805 // Combined PDF/LaTeX:
3806 // x.pdf, x.pdftex_t (old xfig)
3807 // x.pdf, x.pdf_t (new xfig, e.g. 3.2.5)
3808 FileName const abspdfname(
3809 changeExtension(abstexname, ".pdf"));
3810 bool const xfigpdf =
3811 abspdfname.exists() &&
3812 (ext == "pdftex_t" || ext == "pdf_t");
3816 // Combined PS/PDF/LaTeX:
3817 // x_pspdftex.eps, x_pspdftex.pdf, x.pspdftex
3818 string const absbase2(
3819 removeExtension(abstexname) + "_pspdftex");
3820 FileName const abseps2name(
3821 addExtension(absbase2, ".eps"));
3822 FileName const abspdf2name(
3823 addExtension(absbase2, ".pdf"));
3824 bool const xfigboth =
3825 abspdf2name.exists() &&
3826 abseps2name.exists() && ext == "pspdftex";
3828 xfig = xfigpdf || xfigeps || xfigboth;
3829 external = external && xfig;
3832 outname = changeExtension(filename, ".fig");
3834 // Don't try to convert, the result
3835 // would be full of ERT.
3837 } else if (t.cs() != "verbatiminput" &&
3838 tex2lyx(abstexname, FileName(abslyxname),
3845 cerr << "Warning: Could not find included file '"
3846 << filename << "'." << endl;
3850 begin_inset(os, "External\n");
3851 os << "\ttemplate XFig\n"
3852 << "\tfilename " << outname << '\n';
3853 registerExternalTemplatePackages("XFig");
3855 begin_command_inset(os, "include", name);
3856 os << "preview false\n"
3857 "filename \"" << outname << "\"\n";
3858 if (t.cs() == "verbatiminput")
3859 preamble.registerAutomaticallyLoadedPackage("verbatim");
3864 else if (t.cs() == "bibliographystyle") {
3865 // store new bibliographystyle
3866 bibliographystyle = p.verbatim_item();
3867 // If any other command than \bibliography and
3868 // \nocite{*} follows, we need to output the style
3869 // (because it might be used by that command).
3870 // Otherwise, it will automatically be output by LyX.
3873 for (Token t2 = p.get_token(); p.good(); t2 = p.get_token()) {
3874 if (t2.cat() == catBegin)
3876 if (t2.cat() != catEscape)
3878 if (t2.cs() == "nocite") {
3879 if (p.getArg('{', '}') == "*")
3881 } else if (t2.cs() == "bibliography")
3888 "\\bibliographystyle{" + bibliographystyle + '}',
3893 else if (t.cs() == "bibliography") {
3894 context.check_layout(os);
3895 begin_command_inset(os, "bibtex", "bibtex");
3896 if (!btprint.empty()) {
3897 os << "btprint " << '"' << "btPrintAll" << '"' << "\n";
3898 // clear the string because the next BibTeX inset can be without the
3899 // \nocite{*} option
3902 os << "bibfiles " << '"' << p.verbatim_item() << '"' << "\n";
3903 // Do we have a bibliographystyle set?
3904 if (!bibliographystyle.empty())
3905 os << "options " << '"' << bibliographystyle << '"' << "\n";
3909 else if (t.cs() == "parbox") {
3910 // Test whether this is an outer box of a shaded box
3912 // swallow arguments
3913 while (p.hasOpt()) {
3915 p.skip_spaces(true);
3918 p.skip_spaces(true);
3920 if (p.next_token().cat() == catBegin)
3922 p.skip_spaces(true);
3923 Token to = p.get_token();
3924 bool shaded = false;
3925 if (to.asInput() == "\\begin") {
3926 p.skip_spaces(true);
3927 if (p.getArg('{', '}') == "shaded")
3932 parse_outer_box(p, os, FLAG_ITEM, outer,
3933 context, "parbox", "shaded");
3935 parse_box(p, os, 0, FLAG_ITEM, outer, context,
3939 else if (t.cs() == "ovalbox" || t.cs() == "Ovalbox" ||
3940 t.cs() == "shadowbox" || t.cs() == "doublebox")
3941 parse_outer_box(p, os, FLAG_ITEM, outer, context, t.cs(), "");
3943 else if (t.cs() == "framebox") {
3944 if (p.next_token().character() == '(') {
3945 //the syntax is: \framebox(x,y)[position]{content}
3946 string arg = t.asInput();
3947 arg += p.getFullParentheseArg();
3948 arg += p.getFullOpt();
3949 eat_whitespace(p, os, context, false);
3950 handle_ert(os, arg + '{', context);
3951 eat_whitespace(p, os, context, false);
3952 parse_text(p, os, FLAG_ITEM, outer, context);
3953 handle_ert(os, "}", context);
3955 string special = p.getFullOpt();
3956 special += p.getOpt();
3957 parse_outer_box(p, os, FLAG_ITEM, outer,
3958 context, t.cs(), special);
3962 //\makebox() is part of the picture environment and different from \makebox{}
3963 //\makebox{} will be parsed by parse_box
3964 else if (t.cs() == "makebox") {
3965 if (p.next_token().character() == '(') {
3966 //the syntax is: \makebox(x,y)[position]{content}
3967 string arg = t.asInput();
3968 arg += p.getFullParentheseArg();
3969 arg += p.getFullOpt();
3970 eat_whitespace(p, os, context, false);
3971 handle_ert(os, arg + '{', context);
3972 eat_whitespace(p, os, context, false);
3973 parse_text(p, os, FLAG_ITEM, outer, context);
3974 handle_ert(os, "}", context);
3976 //the syntax is: \makebox[width][position]{content}
3977 parse_box(p, os, 0, FLAG_ITEM, outer, context,
3981 else if (t.cs() == "smallskip" ||
3982 t.cs() == "medskip" ||
3983 t.cs() == "bigskip" ||
3984 t.cs() == "vfill") {
3985 context.check_layout(os);
3986 begin_inset(os, "VSpace ");
3989 skip_spaces_braces(p);
3992 else if ((where = is_known(t.cs(), known_spaces))) {
3993 context.check_layout(os);
3994 begin_inset(os, "space ");
3995 os << '\\' << known_coded_spaces[where - known_spaces]
3998 // LaTeX swallows whitespace after all spaces except
3999 // "\\,". We have to do that here, too, because LyX
4000 // adds "{}" which would make the spaces significant.
4002 eat_whitespace(p, os, context, false);
4003 // LyX adds "{}" after all spaces except "\\ " and
4004 // "\\,", so we have to remove "{}".
4005 // "\\,{}" is equivalent to "\\," in LaTeX, so we
4006 // remove the braces after "\\,", too.
4011 else if (t.cs() == "newpage" ||
4012 (t.cs() == "pagebreak" && !p.hasOpt()) ||
4013 t.cs() == "clearpage" ||
4014 t.cs() == "cleardoublepage") {
4015 context.check_layout(os);
4016 begin_inset(os, "Newpage ");
4019 skip_spaces_braces(p);
4022 else if (t.cs() == "DeclareRobustCommand" ||
4023 t.cs() == "DeclareRobustCommandx" ||
4024 t.cs() == "newcommand" ||
4025 t.cs() == "newcommandx" ||
4026 t.cs() == "providecommand" ||
4027 t.cs() == "providecommandx" ||
4028 t.cs() == "renewcommand" ||
4029 t.cs() == "renewcommandx") {
4030 // DeclareRobustCommand, DeclareRobustCommandx,
4031 // providecommand and providecommandx could be handled
4032 // by parse_command(), but we need to call
4033 // add_known_command() here.
4034 string name = t.asInput();
4035 if (p.next_token().asInput() == "*") {
4036 // Starred form. Eat '*'
4040 string const command = p.verbatim_item();
4041 string const opt1 = p.getFullOpt();
4042 string const opt2 = p.getFullOpt();
4043 add_known_command(command, opt1, !opt2.empty());
4044 string const ert = name + '{' + command + '}' +
4046 '{' + p.verbatim_item() + '}';
4048 if (t.cs() == "DeclareRobustCommand" ||
4049 t.cs() == "DeclareRobustCommandx" ||
4050 t.cs() == "providecommand" ||
4051 t.cs() == "providecommandx" ||
4052 name[name.length()-1] == '*')
4053 handle_ert(os, ert, context);
4055 context.check_layout(os);
4056 begin_inset(os, "FormulaMacro");
4062 else if (t.cs() == "let" && p.next_token().asInput() != "*") {
4063 // let could be handled by parse_command(),
4064 // but we need to call add_known_command() here.
4065 string ert = t.asInput();
4068 if (p.next_token().cat() == catBegin) {
4069 name = p.verbatim_item();
4070 ert += '{' + name + '}';
4072 name = p.verbatim_item();
4077 if (p.next_token().cat() == catBegin) {
4078 command = p.verbatim_item();
4079 ert += '{' + command + '}';
4081 command = p.verbatim_item();
4084 // If command is known, make name known too, to parse
4085 // its arguments correctly. For this reason we also
4086 // have commands in syntax.default that are hardcoded.
4087 CommandMap::iterator it = known_commands.find(command);
4088 if (it != known_commands.end())
4089 known_commands[t.asInput()] = it->second;
4090 handle_ert(os, ert, context);
4093 else if (t.cs() == "hspace" || t.cs() == "vspace") {
4094 bool starred = false;
4095 if (p.next_token().asInput() == "*") {
4099 string name = t.asInput();
4100 string const length = p.verbatim_item();
4103 bool valid = splitLatexLength(length, valstring, unit);
4104 bool known_hspace = false;
4105 bool known_vspace = false;
4106 bool known_unit = false;
4109 istringstream iss(valstring);
4112 if (t.cs()[0] == 'h') {
4113 if (unit == "\\fill") {
4118 known_hspace = true;
4121 if (unit == "\\smallskipamount") {
4123 known_vspace = true;
4124 } else if (unit == "\\medskipamount") {
4126 known_vspace = true;
4127 } else if (unit == "\\bigskipamount") {
4129 known_vspace = true;
4130 } else if (unit == "\\fill") {
4132 known_vspace = true;
4136 if (!known_hspace && !known_vspace) {
4137 switch (unitFromString(unit)) {
4158 if (t.cs()[0] == 'h' && (known_unit || known_hspace)) {
4159 // Literal horizontal length or known variable
4160 context.check_layout(os);
4161 begin_inset(os, "space ");
4169 if (known_unit && !known_hspace)
4171 << translate_len(length);
4173 } else if (known_unit || known_vspace) {
4174 // Literal vertical length or known variable
4175 context.check_layout(os);
4176 begin_inset(os, "VSpace ");
4184 // LyX can't handle other length variables in Inset VSpace/space
4189 handle_ert(os, name + '{' + unit + '}', context);
4190 else if (value == -1.0)
4191 handle_ert(os, name + "{-" + unit + '}', context);
4193 handle_ert(os, name + '{' + valstring + unit + '}', context);
4195 handle_ert(os, name + '{' + length + '}', context);
4199 // The single '=' is meant here.
4200 else if ((newinsetlayout = findInsetLayout(context.textclass, t.cs(), true))) {
4202 context.check_layout(os);
4203 begin_inset(os, "Flex ");
4204 os << to_utf8(newinsetlayout->name()) << '\n'
4205 << "status collapsed\n";
4206 parse_text_in_inset(p, os, FLAG_ITEM, false, context, newinsetlayout);
4210 else if (t.cs() == "includepdf") {
4212 string const arg = p.getArg('[', ']');
4213 map<string, string> opts;
4214 vector<string> keys;
4215 split_map(arg, opts, keys);
4216 string name = normalize_filename(p.verbatim_item());
4217 string const path = getMasterFilePath();
4218 // We want to preserve relative / absolute filenames,
4219 // therefore path is only used for testing
4220 if (!makeAbsPath(name, path).exists()) {
4221 // The file extension is probably missing.
4222 // Now try to find it out.
4223 char const * const pdfpages_format[] = {"pdf", 0};
4224 string const pdftex_name =
4225 find_file(name, path, pdfpages_format);
4226 if (!pdftex_name.empty()) {
4231 if (makeAbsPath(name, path).exists())
4232 fix_relative_filename(name);
4234 cerr << "Warning: Could not find file '"
4235 << name << "'." << endl;
4237 context.check_layout(os);
4238 begin_inset(os, "External\n\ttemplate ");
4239 os << "PDFPages\n\tfilename "
4241 // parse the options
4242 if (opts.find("pages") != opts.end())
4243 os << "\textra LaTeX \"pages="
4244 << opts["pages"] << "\"\n";
4245 if (opts.find("angle") != opts.end())
4246 os << "\trotateAngle "
4247 << opts["angle"] << '\n';
4248 if (opts.find("origin") != opts.end()) {
4250 string const opt = opts["origin"];
4251 if (opt == "tl") ss << "topleft";
4252 if (opt == "bl") ss << "bottomleft";
4253 if (opt == "Bl") ss << "baselineleft";
4254 if (opt == "c") ss << "center";
4255 if (opt == "tc") ss << "topcenter";
4256 if (opt == "bc") ss << "bottomcenter";
4257 if (opt == "Bc") ss << "baselinecenter";
4258 if (opt == "tr") ss << "topright";
4259 if (opt == "br") ss << "bottomright";
4260 if (opt == "Br") ss << "baselineright";
4261 if (!ss.str().empty())
4262 os << "\trotateOrigin " << ss.str() << '\n';
4264 cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
4266 if (opts.find("width") != opts.end())
4268 << translate_len(opts["width"]) << '\n';
4269 if (opts.find("height") != opts.end())
4271 << translate_len(opts["height"]) << '\n';
4272 if (opts.find("keepaspectratio") != opts.end())
4273 os << "\tkeepAspectRatio\n";
4275 context.check_layout(os);
4276 registerExternalTemplatePackages("PDFPages");
4279 else if (t.cs() == "loadgame") {
4281 string name = normalize_filename(p.verbatim_item());
4282 string const path = getMasterFilePath();
4283 // We want to preserve relative / absolute filenames,
4284 // therefore path is only used for testing
4285 if (!makeAbsPath(name, path).exists()) {
4286 // The file extension is probably missing.
4287 // Now try to find it out.
4288 char const * const lyxskak_format[] = {"fen", 0};
4289 string const lyxskak_name =
4290 find_file(name, path, lyxskak_format);
4291 if (!lyxskak_name.empty())
4292 name = lyxskak_name;
4294 if (makeAbsPath(name, path).exists())
4295 fix_relative_filename(name);
4297 cerr << "Warning: Could not find file '"
4298 << name << "'." << endl;
4299 context.check_layout(os);
4300 begin_inset(os, "External\n\ttemplate ");
4301 os << "ChessDiagram\n\tfilename "
4304 context.check_layout(os);
4305 // after a \loadgame follows a \showboard
4306 if (p.get_token().asInput() == "showboard")
4308 registerExternalTemplatePackages("ChessDiagram");
4312 // try to see whether the string is in unicodesymbols
4313 // Only use text mode commands, since we are in text mode here,
4314 // and math commands may be invalid (bug 6797)
4318 docstring s = encodings.fromLaTeXCommand(from_utf8(t.asInput()),
4319 Encodings::TEXT_CMD, termination, rem, &req);
4322 cerr << "When parsing " << t.cs()
4323 << ", result is " << to_utf8(s)
4324 << "+" << to_utf8(rem) << endl;
4325 context.check_layout(os);
4328 skip_spaces_braces(p);
4329 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
4330 preamble.registerAutomaticallyLoadedPackage(*it);
4332 //cerr << "#: " << t << " mode: " << mode << endl;
4333 // heuristic: read up to next non-nested space
4335 string s = t.asInput();
4336 string z = p.verbatim_item();
4337 while (p.good() && z != " " && z.size()) {
4338 //cerr << "read: " << z << endl;
4340 z = p.verbatim_item();
4342 cerr << "found ERT: " << s << endl;
4343 handle_ert(os, s + ' ', context);
4346 string name = t.asInput();
4347 if (p.next_token().asInput() == "*") {
4348 // Starred commands like \vspace*{}
4349 p.get_token(); // Eat '*'
4352 if (!parse_command(name, p, os, outer, context))
4353 handle_ert(os, name, context);
4357 if (flags & FLAG_LEAVE) {
4358 flags &= ~FLAG_LEAVE;