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",
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",
152 "american", "ancientgreek", "australian", "british", "greek", "newzealand",
153 "polutonikogreek", 0};
157 * The starred forms are also known except for "citefullauthor",
158 * "citeyear" and "citeyearpar".
160 char const * const known_natbib_commands[] = { "cite", "citet", "citep",
161 "citealt", "citealp", "citeauthor", "citeyear", "citeyearpar",
162 "citefullauthor", "Citet", "Citep", "Citealt", "Citealp", "Citeauthor", 0 };
166 * No starred form other than "cite*" known.
168 char const * const known_jurabib_commands[] = { "cite", "citet", "citep",
169 "citealt", "citealp", "citeauthor", "citeyear", "citeyearpar",
170 // jurabib commands not (yet) supported by LyX:
172 // "footcite", "footcitet", "footcitep", "footcitealt", "footcitealp",
173 // "footciteauthor", "footciteyear", "footciteyearpar",
174 "citefield", "citetitle", 0 };
176 /// LaTeX names for quotes
177 char const * const known_quotes[] = { "dq", "guillemotleft", "flqq", "og",
178 "guillemotright", "frqq", "fg", "glq", "glqq", "textquoteleft", "grq", "grqq",
179 "quotedblbase", "textquotedblleft", "quotesinglbase", "textquoteright", "flq",
180 "guilsinglleft", "frq", "guilsinglright", 0};
182 /// the same as known_quotes with .lyx names
183 char const * const known_coded_quotes[] = { "prd", "ard", "ard", "ard",
184 "ald", "ald", "ald", "gls", "gld", "els", "els", "grd",
185 "gld", "grd", "gls", "ers", "fls",
186 "fls", "frs", "frs", 0};
188 /// LaTeX names for font sizes
189 char const * const known_sizes[] = { "tiny", "scriptsize", "footnotesize",
190 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
192 /// the same as known_sizes with .lyx names
193 char const * const known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
194 "small", "normal", "large", "larger", "largest", "huge", "giant", 0};
196 /// LaTeX 2.09 names for font families
197 char const * const known_old_font_families[] = { "rm", "sf", "tt", 0};
199 /// LaTeX names for font families
200 char const * const known_font_families[] = { "rmfamily", "sffamily",
203 /// LaTeX names for font family changing commands
204 char const * const known_text_font_families[] = { "textrm", "textsf",
207 /// The same as known_old_font_families, known_font_families and
208 /// known_text_font_families with .lyx names
209 char const * const known_coded_font_families[] = { "roman", "sans",
212 /// LaTeX 2.09 names for font series
213 char const * const known_old_font_series[] = { "bf", 0};
215 /// LaTeX names for font series
216 char const * const known_font_series[] = { "bfseries", "mdseries", 0};
218 /// LaTeX names for font series changing commands
219 char const * const known_text_font_series[] = { "textbf", "textmd", 0};
221 /// The same as known_old_font_series, known_font_series and
222 /// known_text_font_series with .lyx names
223 char const * const known_coded_font_series[] = { "bold", "medium", 0};
225 /// LaTeX 2.09 names for font shapes
226 char const * const known_old_font_shapes[] = { "it", "sl", "sc", 0};
228 /// LaTeX names for font shapes
229 char const * const known_font_shapes[] = { "itshape", "slshape", "scshape",
232 /// LaTeX names for font shape changing commands
233 char const * const known_text_font_shapes[] = { "textit", "textsl", "textsc",
236 /// The same as known_old_font_shapes, known_font_shapes and
237 /// known_text_font_shapes with .lyx names
238 char const * const known_coded_font_shapes[] = { "italic", "slanted",
239 "smallcaps", "up", 0};
241 /// Known special characters which need skip_spaces_braces() afterwards
242 char const * const known_special_chars[] = {"ldots", "lyxarrow",
243 "textcompwordmark", "slash", 0};
245 /// the same as known_special_chars with .lyx names
246 char const * const known_coded_special_chars[] = {"ldots{}", "menuseparator",
247 "textcompwordmark{}", "slash{}", 0};
250 * Graphics file extensions known by the dvips driver of the graphics package.
251 * These extensions are used to complete the filename of an included
252 * graphics file if it does not contain an extension.
253 * The order must be the same that latex uses to find a file, because we
254 * will use the first extension that matches.
255 * This is only an approximation for the common cases. If we would want to
256 * do it right in all cases, we would need to know which graphics driver is
257 * used and know the extensions of every driver of the graphics package.
259 char const * const known_dvips_graphics_formats[] = {"eps", "ps", "eps.gz",
260 "ps.gz", "eps.Z", "ps.Z", 0};
263 * Graphics file extensions known by the pdftex driver of the graphics package.
264 * \sa known_dvips_graphics_formats
266 char const * const known_pdftex_graphics_formats[] = {"png", "pdf", "jpg",
270 * Known file extensions for TeX files as used by \\include.
272 char const * const known_tex_extensions[] = {"tex", 0};
274 /// spaces known by InsetSpace
275 char const * const known_spaces[] = { " ", "space", ",",
276 "thinspace", "quad", "qquad", "enspace", "enskip",
277 "negthinspace", "negmedspace", "negthickspace", "textvisiblespace",
278 "hfill", "dotfill", "hrulefill", "leftarrowfill", "rightarrowfill",
279 "upbracefill", "downbracefill", 0};
281 /// the same as known_spaces with .lyx names
282 char const * const known_coded_spaces[] = { "space{}", "space{}",
283 "thinspace{}", "thinspace{}", "quad{}", "qquad{}", "enspace{}", "enskip{}",
284 "negthinspace{}", "negmedspace{}", "negthickspace{}", "textvisiblespace{}",
285 "hfill{}", "dotfill{}", "hrulefill{}", "leftarrowfill{}", "rightarrowfill{}",
286 "upbracefill{}", "downbracefill{}", 0};
288 /// These are translated by LyX to commands like "\\LyX{}", so we have to put
289 /// them in ERT. "LaTeXe" must come before "LaTeX"!
290 char const * const known_phrases[] = {"LyX", "TeX", "LaTeXe", "LaTeX", 0};
291 char const * const known_coded_phrases[] = {"LyX", "TeX", "LaTeX2e", "LaTeX", 0};
292 int const known_phrase_lengths[] = {3, 5, 7, 0};
294 // string to store the float type to be able to determine the type of subfloats
295 string float_type = "";
298 /// splits "x=z, y=b" into a map and an ordered keyword vector
299 void split_map(string const & s, map<string, string> & res, vector<string> & keys)
304 keys.resize(v.size());
305 for (size_t i = 0; i < v.size(); ++i) {
306 size_t const pos = v[i].find('=');
307 string const index = trimSpaceAndEol(v[i].substr(0, pos));
308 string const value = trimSpaceAndEol(v[i].substr(pos + 1, string::npos));
316 * Split a LaTeX length into value and unit.
317 * The latter can be a real unit like "pt", or a latex length variable
318 * like "\textwidth". The unit may contain additional stuff like glue
319 * lengths, but we don't care, because such lengths are ERT anyway.
320 * \returns true if \p value and \p unit are valid.
322 bool splitLatexLength(string const & len, string & value, string & unit)
326 const string::size_type i = len.find_first_not_of(" -+0123456789.,");
327 //'4,5' is a valid LaTeX length number. Change it to '4.5'
328 string const length = subst(len, ',', '.');
329 if (i == string::npos)
332 if (len[0] == '\\') {
333 // We had something like \textwidth without a factor
339 value = trimSpaceAndEol(string(length, 0, i));
343 // 'cM' is a valid LaTeX length unit. Change it to 'cm'
344 if (contains(len, '\\'))
345 unit = trimSpaceAndEol(string(len, i));
347 unit = ascii_lowercase(trimSpaceAndEol(string(len, i)));
352 /// A simple function to translate a latex length to something LyX can
353 /// understand. Not perfect, but rather best-effort.
354 bool translate_len(string const & length, string & valstring, string & unit)
356 if (!splitLatexLength(length, valstring, unit))
358 // LyX uses percent values
360 istringstream iss(valstring);
365 string const percentval = oss.str();
367 if (unit.empty() || unit[0] != '\\')
369 string::size_type const i = unit.find(' ');
370 string const endlen = (i == string::npos) ? string() : string(unit, i);
371 if (unit == "\\textwidth") {
372 valstring = percentval;
373 unit = "text%" + endlen;
374 } else if (unit == "\\columnwidth") {
375 valstring = percentval;
376 unit = "col%" + endlen;
377 } else if (unit == "\\paperwidth") {
378 valstring = percentval;
379 unit = "page%" + endlen;
380 } else if (unit == "\\linewidth") {
381 valstring = percentval;
382 unit = "line%" + endlen;
383 } else if (unit == "\\paperheight") {
384 valstring = percentval;
385 unit = "pheight%" + endlen;
386 } else if (unit == "\\textheight") {
387 valstring = percentval;
388 unit = "theight%" + endlen;
396 string translate_len(string const & length)
400 if (translate_len(length, value, unit))
402 // If the input is invalid, return what we have.
410 * Translates a LaTeX length into \p value, \p unit and
411 * \p special parts suitable for a box inset.
412 * The difference from translate_len() is that a box inset knows about
413 * some special "units" that are stored in \p special.
415 void translate_box_len(string const & length, string & value, string & unit, string & special)
417 if (translate_len(length, value, unit)) {
418 if (unit == "\\height" || unit == "\\depth" ||
419 unit == "\\totalheight" || unit == "\\width") {
420 special = unit.substr(1);
421 // The unit is not used, but LyX requires a dummy setting
434 * Find a file with basename \p name in path \p path and an extension
437 string find_file(string const & name, string const & path,
438 char const * const * extensions)
440 for (char const * const * what = extensions; *what; ++what) {
441 string const trial = addExtension(name, *what);
442 if (makeAbsPath(trial, path).exists())
449 void begin_inset(ostream & os, string const & name)
451 os << "\n\\begin_inset " << name;
455 void begin_command_inset(ostream & os, string const & name,
456 string const & latexname)
458 begin_inset(os, "CommandInset ");
459 os << name << "\nLatexCommand " << latexname << '\n';
463 void end_inset(ostream & os)
465 os << "\n\\end_inset\n\n";
469 bool skip_braces(Parser & p)
471 if (p.next_token().cat() != catBegin)
474 if (p.next_token().cat() == catEnd) {
483 /// replace LaTeX commands in \p s from the unicodesymbols file with their
485 docstring convert_unicodesymbols(docstring s)
488 for (size_t i = 0; i < s.size();) {
497 docstring parsed = encodings.fromLaTeXCommand(s,
498 Encodings::TEXT_CMD, termination, rem, &req);
499 set<string>::const_iterator it = req.begin();
500 set<string>::const_iterator en = req.end();
501 for (; it != en; ++it)
502 preamble.registerAutomaticallyLoadedPackage(*it);
505 if (s.empty() || s[0] != '\\')
514 /// try to convert \p s to a valid InsetCommand argument
515 string convert_command_inset_arg(string s)
518 // since we don't know the input encoding we can't use from_utf8
519 s = to_utf8(convert_unicodesymbols(from_ascii(s)));
520 // LyX cannot handle newlines in a latex command
521 return subst(s, "\n", " ");
525 void handle_backslash(ostream & os, string const & s)
527 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
529 os << "\n\\backslash\n";
536 void handle_ert(ostream & os, string const & s, Context & context)
538 // We must have a valid layout before outputting the ERT inset.
539 context.check_layout(os);
540 Context newcontext(true, context.textclass);
541 begin_inset(os, "ERT");
542 os << "\nstatus collapsed\n";
543 newcontext.check_layout(os);
544 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
546 os << "\n\\backslash\n";
547 else if (*it == '\n') {
548 newcontext.new_paragraph(os);
549 newcontext.check_layout(os);
553 newcontext.check_end_layout(os);
558 void handle_comment(ostream & os, string const & s, Context & context)
560 // TODO: Handle this better
561 Context newcontext(true, context.textclass);
562 begin_inset(os, "ERT");
563 os << "\nstatus collapsed\n";
564 newcontext.check_layout(os);
565 handle_backslash(os, s);
566 // make sure that our comment is the last thing on the line
567 newcontext.new_paragraph(os);
568 newcontext.check_layout(os);
569 newcontext.check_end_layout(os);
574 Layout const * findLayout(TextClass const & textclass, string const & name, bool command)
576 Layout const * layout = findLayoutWithoutModule(textclass, name, command);
579 if (checkModule(name, command))
580 return findLayoutWithoutModule(textclass, name, command);
585 InsetLayout const * findInsetLayout(TextClass const & textclass, string const & name, bool command)
587 InsetLayout const * insetlayout = findInsetLayoutWithoutModule(textclass, name, command);
590 if (checkModule(name, command))
591 return findInsetLayoutWithoutModule(textclass, name, command);
596 void eat_whitespace(Parser &, ostream &, Context &, bool);
600 * Skips whitespace and braces.
601 * This should be called after a command has been parsed that is not put into
602 * ERT, and where LyX adds "{}" if needed.
604 void skip_spaces_braces(Parser & p, bool keepws = false)
606 /* The following four examples produce the same typeset output and
607 should be handled by this function:
615 // Unfortunately we need to skip comments, too.
616 // We can't use eat_whitespace since writing them after the {}
617 // results in different output in some cases.
618 bool const skipped_spaces = p.skip_spaces(true);
619 bool const skipped_braces = skip_braces(p);
620 if (keepws && skipped_spaces && !skipped_braces)
621 // put back the space (it is better handled by check_space)
622 p.unskip_spaces(true);
626 void output_command_layout(ostream & os, Parser & p, bool outer,
627 Context & parent_context,
628 Layout const * newlayout)
630 TeXFont const oldFont = parent_context.font;
631 // save the current font size
632 string const size = oldFont.size;
633 // reset the font size to default, because the font size switches
634 // don't affect section headings and the like
635 parent_context.font.size = Context::normalfont.size;
636 // we only need to write the font change if we have an open layout
637 if (!parent_context.atParagraphStart())
638 output_font_change(os, oldFont, parent_context.font);
639 parent_context.check_end_layout(os);
640 Context context(true, parent_context.textclass, newlayout,
641 parent_context.layout, parent_context.font);
642 if (parent_context.deeper_paragraph) {
643 // We are beginning a nested environment after a
644 // deeper paragraph inside the outer list environment.
645 // Therefore we don't need to output a "begin deeper".
646 context.need_end_deeper = true;
648 context.check_deeper(os);
649 context.check_layout(os);
650 unsigned int optargs = 0;
651 while (optargs < context.layout->optargs) {
652 eat_whitespace(p, os, context, false);
653 if (p.next_token().cat() == catEscape ||
654 p.next_token().character() != '[')
656 p.get_token(); // eat '['
657 begin_inset(os, "Argument\n");
658 os << "status collapsed\n\n";
659 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
661 eat_whitespace(p, os, context, false);
664 unsigned int reqargs = 0;
665 while (reqargs < context.layout->reqargs) {
666 eat_whitespace(p, os, context, false);
667 if (p.next_token().cat() != catBegin)
669 p.get_token(); // eat '{'
670 begin_inset(os, "Argument\n");
671 os << "status collapsed\n\n";
672 parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context);
674 eat_whitespace(p, os, context, false);
677 parse_text(p, os, FLAG_ITEM, outer, context);
678 context.check_end_layout(os);
679 if (parent_context.deeper_paragraph) {
680 // We must suppress the "end deeper" because we
681 // suppressed the "begin deeper" above.
682 context.need_end_deeper = false;
684 context.check_end_deeper(os);
685 // We don't need really a new paragraph, but
686 // we must make sure that the next item gets a \begin_layout.
687 parent_context.new_paragraph(os);
688 // Set the font size to the original value. No need to output it here
689 // (Context::begin_layout() will do that if needed)
690 parent_context.font.size = size;
695 * Output a space if necessary.
696 * This function gets called for every whitespace token.
698 * We have three cases here:
699 * 1. A space must be suppressed. Example: The lyxcode case below
700 * 2. A space may be suppressed. Example: Spaces before "\par"
701 * 3. A space must not be suppressed. Example: A space between two words
703 * We currently handle only 1. and 3 and from 2. only the case of
704 * spaces before newlines as a side effect.
706 * 2. could be used to suppress as many spaces as possible. This has two effects:
707 * - Reimporting LyX generated LaTeX files changes almost no whitespace
708 * - Superflous whitespace from non LyX generated LaTeX files is removed.
709 * The drawback is that the logic inside the function becomes
710 * complicated, and that is the reason why it is not implemented.
712 void check_space(Parser & p, ostream & os, Context & context)
714 Token const next = p.next_token();
715 Token const curr = p.curr_token();
716 // A space before a single newline and vice versa must be ignored
717 // LyX emits a newline before \end{lyxcode}.
718 // This newline must be ignored,
719 // otherwise LyX will add an additional protected space.
720 if (next.cat() == catSpace ||
721 next.cat() == catNewline ||
722 (next.cs() == "end" && context.layout->free_spacing && curr.cat() == catNewline)) {
725 context.check_layout(os);
731 * Parse all arguments of \p command
733 void parse_arguments(string const & command,
734 vector<ArgumentType> const & template_arguments,
735 Parser & p, ostream & os, bool outer, Context & context)
737 string ert = command;
738 size_t no_arguments = template_arguments.size();
739 for (size_t i = 0; i < no_arguments; ++i) {
740 switch (template_arguments[i]) {
743 // This argument contains regular LaTeX
744 handle_ert(os, ert + '{', context);
745 eat_whitespace(p, os, context, false);
746 if (template_arguments[i] == required)
747 parse_text(p, os, FLAG_ITEM, outer, context);
749 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
753 // This argument consists only of a single item.
754 // The presence of '{' or not must be preserved.
756 if (p.next_token().cat() == catBegin)
757 ert += '{' + p.verbatim_item() + '}';
759 ert += p.verbatim_item();
763 // This argument may contain special characters
764 ert += '{' + p.verbatim_item() + '}';
768 // true because we must not eat whitespace
769 // if an optional arg follows we must not strip the
770 // brackets from this one
771 if (i < no_arguments - 1 &&
772 template_arguments[i+1] == optional)
773 ert += p.getFullOpt(true);
775 ert += p.getOpt(true);
779 handle_ert(os, ert, context);
784 * Check whether \p command is a known command. If yes,
785 * handle the command with all arguments.
786 * \return true if the command was parsed, false otherwise.
788 bool parse_command(string const & command, Parser & p, ostream & os,
789 bool outer, Context & context)
791 if (known_commands.find(command) != known_commands.end()) {
792 parse_arguments(command, known_commands[command], p, os,
800 /// Parses a minipage or parbox
801 void parse_box(Parser & p, ostream & os, unsigned outer_flags,
802 unsigned inner_flags, bool outer, Context & parent_context,
803 string const & outer_type, string const & special,
804 string const & inner_type)
808 string hor_pos = "c";
809 // We need to set the height to the LaTeX default of 1\\totalheight
810 // for the case when no height argument is given
811 string height_value = "1";
812 string height_unit = "in";
813 string height_special = "totalheight";
818 string width_special = "none";
819 if (!inner_type.empty() && p.hasOpt()) {
820 if (inner_type != "makebox")
821 position = p.getArg('[', ']');
823 latex_width = p.getArg('[', ']');
824 translate_box_len(latex_width, width_value, width_unit, width_special);
827 if (position != "t" && position != "c" && position != "b") {
828 cerr << "invalid position " << position << " for "
829 << inner_type << endl;
833 if (inner_type != "makebox") {
834 latex_height = p.getArg('[', ']');
835 translate_box_len(latex_height, height_value, height_unit, height_special);
837 hor_pos = p.getArg('[', ']');
840 inner_pos = p.getArg('[', ']');
841 if (inner_pos != "c" && inner_pos != "t" &&
842 inner_pos != "b" && inner_pos != "s") {
843 cerr << "invalid inner_pos "
844 << inner_pos << " for "
845 << inner_type << endl;
846 inner_pos = position;
851 if (inner_type.empty()) {
852 if (special.empty() && outer_type != "framebox")
853 latex_width = "1\\columnwidth";
856 latex_width = p2.getArg('[', ']');
857 string const opt = p2.getArg('[', ']');
860 if (hor_pos != "l" && hor_pos != "c" &&
862 cerr << "invalid hor_pos " << hor_pos
863 << " for " << outer_type << endl;
868 } else if (inner_type != "makebox")
869 latex_width = p.verbatim_item();
870 // if e.g. only \ovalbox{content} was used, set the width to 1\columnwidth
871 // as this is LyX's standard for such cases (except for makebox)
872 // \framebox is more special and handled below
873 if (latex_width.empty() && inner_type != "makebox"
874 && outer_type != "framebox")
875 latex_width = "1\\columnwidth";
877 translate_len(latex_width, width_value, width_unit);
879 bool shadedparbox = false;
880 if (inner_type == "shaded") {
881 eat_whitespace(p, os, parent_context, false);
882 if (outer_type == "parbox") {
884 if (p.next_token().cat() == catBegin)
886 eat_whitespace(p, os, parent_context, false);
892 // If we already read the inner box we have to push the inner env
893 if (!outer_type.empty() && !inner_type.empty() &&
894 (inner_flags & FLAG_END))
895 active_environments.push_back(inner_type);
896 // LyX can't handle length variables
897 bool use_ert = contains(width_unit, '\\') || contains(height_unit, '\\');
898 if (!use_ert && !outer_type.empty() && !inner_type.empty()) {
899 // Look whether there is some content after the end of the
900 // inner box, but before the end of the outer box.
901 // If yes, we need to output ERT.
903 if (inner_flags & FLAG_END)
904 p.verbatimEnvironment(inner_type);
908 bool const outer_env(outer_type == "framed" || outer_type == "minipage");
909 if ((outer_env && p.next_token().asInput() != "\\end") ||
910 (!outer_env && p.next_token().cat() != catEnd)) {
911 // something is between the end of the inner box and
912 // the end of the outer box, so we need to use ERT.
917 // if only \makebox{content} was used we can set its width to 1\width
918 // because this identic and also identic to \mbox
919 // this doesn't work for \framebox{content}, thus we have to use ERT for this
920 if (latex_width.empty() && inner_type == "makebox") {
923 width_special = "width";
924 } else if (latex_width.empty() && outer_type == "framebox") {
929 if (!outer_type.empty()) {
930 if (outer_flags & FLAG_END)
931 ss << "\\begin{" << outer_type << '}';
933 ss << '\\' << outer_type << '{';
934 if (!special.empty())
938 if (!inner_type.empty()) {
939 if (inner_type != "shaded") {
940 if (inner_flags & FLAG_END)
941 ss << "\\begin{" << inner_type << '}';
943 ss << '\\' << inner_type;
945 if (!position.empty())
946 ss << '[' << position << ']';
947 if (!latex_height.empty())
948 ss << '[' << latex_height << ']';
949 if (!inner_pos.empty())
950 ss << '[' << inner_pos << ']';
951 ss << '{' << latex_width << '}';
952 if (!(inner_flags & FLAG_END))
955 if (inner_type == "shaded")
956 ss << "\\begin{shaded}";
957 handle_ert(os, ss.str(), parent_context);
958 if (!inner_type.empty()) {
959 parse_text(p, os, inner_flags, outer, parent_context);
960 if (inner_flags & FLAG_END)
961 handle_ert(os, "\\end{" + inner_type + '}',
964 handle_ert(os, "}", parent_context);
966 if (!outer_type.empty()) {
967 // If we already read the inner box we have to pop
969 if (!inner_type.empty() && (inner_flags & FLAG_END))
970 active_environments.pop_back();
972 // Ensure that the end of the outer box is parsed correctly:
973 // The opening brace has been eaten by parse_outer_box()
974 if (!outer_type.empty() && (outer_flags & FLAG_ITEM)) {
975 outer_flags &= ~FLAG_ITEM;
976 outer_flags |= FLAG_BRACE_LAST;
978 parse_text(p, os, outer_flags, outer, parent_context);
979 if (outer_flags & FLAG_END)
980 handle_ert(os, "\\end{" + outer_type + '}',
982 else if (inner_type.empty() && outer_type == "framebox")
983 // in this case it is already closed later
986 handle_ert(os, "}", parent_context);
989 // LyX does not like empty positions, so we have
990 // to set them to the LaTeX default values here.
991 if (position.empty())
993 if (inner_pos.empty())
994 inner_pos = position;
995 parent_context.check_layout(os);
996 begin_inset(os, "Box ");
997 if (outer_type == "framed")
999 else if (outer_type == "framebox")
1001 else if (outer_type == "shadowbox")
1002 os << "Shadowbox\n";
1003 else if ((outer_type == "shaded" && inner_type.empty()) ||
1004 (outer_type == "minipage" && inner_type == "shaded") ||
1005 (outer_type == "parbox" && inner_type == "shaded")) {
1007 preamble.registerAutomaticallyLoadedPackage("color");
1008 } else if (outer_type == "doublebox")
1009 os << "Doublebox\n";
1010 else if (outer_type.empty())
1011 os << "Frameless\n";
1013 os << outer_type << '\n';
1014 os << "position \"" << position << "\"\n";
1015 os << "hor_pos \"" << hor_pos << "\"\n";
1016 os << "has_inner_box " << !inner_type.empty() << "\n";
1017 os << "inner_pos \"" << inner_pos << "\"\n";
1018 os << "use_parbox " << (inner_type == "parbox" || shadedparbox)
1020 os << "use_makebox " << (inner_type == "makebox") << '\n';
1021 os << "width \"" << width_value << width_unit << "\"\n";
1022 os << "special \"" << width_special << "\"\n";
1023 os << "height \"" << height_value << height_unit << "\"\n";
1024 os << "height_special \"" << height_special << "\"\n";
1025 os << "status open\n\n";
1027 // Unfortunately we can't use parse_text_in_inset:
1028 // InsetBox::forcePlainLayout() is hard coded and does not
1029 // use the inset layout. Apart from that do we call parse_text
1030 // up to two times, but need only one check_end_layout.
1031 bool const forcePlainLayout =
1032 (!inner_type.empty() || inner_type == "makebox") &&
1033 outer_type != "shaded" && outer_type != "framed";
1034 Context context(true, parent_context.textclass);
1035 if (forcePlainLayout)
1036 context.layout = &context.textclass.plainLayout();
1038 context.font = parent_context.font;
1040 // If we have no inner box the contents will be read with the outer box
1041 if (!inner_type.empty())
1042 parse_text(p, os, inner_flags, outer, context);
1044 // Ensure that the end of the outer box is parsed correctly:
1045 // The opening brace has been eaten by parse_outer_box()
1046 if (!outer_type.empty() && (outer_flags & FLAG_ITEM)) {
1047 outer_flags &= ~FLAG_ITEM;
1048 outer_flags |= FLAG_BRACE_LAST;
1051 // Find end of outer box, output contents if inner_type is
1052 // empty and output possible comments
1053 if (!outer_type.empty()) {
1054 // If we already read the inner box we have to pop
1056 if (!inner_type.empty() && (inner_flags & FLAG_END))
1057 active_environments.pop_back();
1058 // This does not output anything but comments if
1059 // inner_type is not empty (see use_ert)
1060 parse_text(p, os, outer_flags, outer, context);
1063 context.check_end_layout(os);
1065 #ifdef PRESERVE_LAYOUT
1066 // LyX puts a % after the end of the minipage
1067 if (p.next_token().cat() == catNewline && p.next_token().cs().size() > 1) {
1069 //handle_comment(os, "%dummy", parent_context);
1072 parent_context.new_paragraph(os);
1074 else if (p.next_token().cat() == catSpace || p.next_token().cat() == catNewline) {
1075 //handle_comment(os, "%dummy", parent_context);
1078 // We add a protected space if something real follows
1079 if (p.good() && p.next_token().cat() != catComment) {
1080 begin_inset(os, "space ~\n");
1089 void parse_outer_box(Parser & p, ostream & os, unsigned flags, bool outer,
1090 Context & parent_context, string const & outer_type,
1091 string const & special)
1093 eat_whitespace(p, os, parent_context, false);
1094 if (flags & FLAG_ITEM) {
1096 if (p.next_token().cat() == catBegin)
1099 cerr << "Warning: Ignoring missing '{' after \\"
1100 << outer_type << '.' << endl;
1101 eat_whitespace(p, os, parent_context, false);
1104 unsigned int inner_flags = 0;
1106 if (outer_type == "minipage" || outer_type == "parbox") {
1107 p.skip_spaces(true);
1108 while (p.hasOpt()) {
1110 p.skip_spaces(true);
1113 p.skip_spaces(true);
1114 if (outer_type == "parbox") {
1116 if (p.next_token().cat() == catBegin)
1118 p.skip_spaces(true);
1121 if (outer_type == "shaded") {
1122 // These boxes never have an inner box
1124 } else if (p.next_token().asInput() == "\\parbox") {
1125 inner = p.get_token().cs();
1126 inner_flags = FLAG_ITEM;
1127 } else if (p.next_token().asInput() == "\\begin") {
1128 // Is this a minipage or shaded box?
1131 inner = p.getArg('{', '}');
1133 if (inner == "minipage" || inner == "shaded")
1134 inner_flags = FLAG_END;
1139 if (inner_flags == FLAG_END) {
1140 if (inner != "shaded")
1144 eat_whitespace(p, os, parent_context, false);
1146 parse_box(p, os, flags, FLAG_END, outer, parent_context,
1147 outer_type, special, inner);
1149 if (inner_flags == FLAG_ITEM) {
1151 eat_whitespace(p, os, parent_context, false);
1153 parse_box(p, os, flags, inner_flags, outer, parent_context,
1154 outer_type, special, inner);
1159 void parse_listings(Parser & p, ostream & os, Context & parent_context, bool in_line)
1161 parent_context.check_layout(os);
1162 begin_inset(os, "listings\n");
1164 string arg = p.verbatimOption();
1165 os << "lstparams " << '"' << arg << '"' << '\n';
1168 os << "inline true\n";
1170 os << "inline false\n";
1171 os << "status collapsed\n";
1172 Context context(true, parent_context.textclass);
1173 context.layout = &parent_context.textclass.plainLayout();
1176 s = p.plainCommand('!', '!', "lstinline");
1177 context.new_paragraph(os);
1178 context.check_layout(os);
1180 s = p.plainEnvironment("lstlisting");
1181 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
1183 os << "\n\\backslash\n";
1184 else if (*it == '\n') {
1185 // avoid adding an empty paragraph at the end
1187 context.new_paragraph(os);
1188 context.check_layout(os);
1193 context.check_end_layout(os);
1198 /// parse an unknown environment
1199 void parse_unknown_environment(Parser & p, string const & name, ostream & os,
1200 unsigned flags, bool outer,
1201 Context & parent_context)
1203 if (name == "tabbing")
1204 // We need to remember that we have to handle '\=' specially
1205 flags |= FLAG_TABBING;
1207 // We need to translate font changes and paragraphs inside the
1208 // environment to ERT if we have a non standard font.
1209 // Otherwise things like
1210 // \large\begin{foo}\huge bar\end{foo}
1212 bool const specialfont =
1213 (parent_context.font != parent_context.normalfont);
1214 bool const new_layout_allowed = parent_context.new_layout_allowed;
1216 parent_context.new_layout_allowed = false;
1217 handle_ert(os, "\\begin{" + name + "}", parent_context);
1218 parse_text_snippet(p, os, flags, outer, parent_context);
1219 handle_ert(os, "\\end{" + name + "}", parent_context);
1221 parent_context.new_layout_allowed = new_layout_allowed;
1225 void parse_environment(Parser & p, ostream & os, bool outer,
1226 string & last_env, Context & parent_context)
1228 Layout const * newlayout;
1229 InsetLayout const * newinsetlayout = 0;
1230 string const name = p.getArg('{', '}');
1231 const bool is_starred = suffixIs(name, '*');
1232 string const unstarred_name = rtrim(name, "*");
1233 active_environments.push_back(name);
1235 if (is_math_env(name)) {
1236 parent_context.check_layout(os);
1237 begin_inset(os, "Formula ");
1238 os << "\\begin{" << name << "}";
1239 parse_math(p, os, FLAG_END, MATH_MODE);
1240 os << "\\end{" << name << "}";
1242 if (is_display_math_env(name)) {
1243 // Prevent the conversion of a line break to a space
1244 // (bug 7668). This does not change the output, but
1245 // looks ugly in LyX.
1246 eat_whitespace(p, os, parent_context, false);
1250 else if (is_known(name, polyglossia_languages)) {
1251 // We must begin a new paragraph if not already done
1252 if (! parent_context.atParagraphStart()) {
1253 parent_context.check_end_layout(os);
1254 parent_context.new_paragraph(os);
1256 // save the language in the context so that it is
1257 // handled by parse_text
1258 parent_context.font.language = polyglossia2lyx(name);
1259 parse_text(p, os, FLAG_END, outer, parent_context);
1260 // Just in case the environment is empty
1261 parent_context.extra_stuff.erase();
1262 // We must begin a new paragraph to reset the language
1263 parent_context.new_paragraph(os);
1267 else if (unstarred_name == "tabular" || name == "longtable") {
1268 eat_whitespace(p, os, parent_context, false);
1269 string width = "0pt";
1270 if (name == "tabular*") {
1271 width = lyx::translate_len(p.getArg('{', '}'));
1272 eat_whitespace(p, os, parent_context, false);
1274 parent_context.check_layout(os);
1275 begin_inset(os, "Tabular ");
1276 handle_tabular(p, os, name, width, parent_context);
1281 else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
1282 eat_whitespace(p, os, parent_context, false);
1283 string const opt = p.hasOpt() ? p.getArg('[', ']') : string();
1284 eat_whitespace(p, os, parent_context, false);
1285 parent_context.check_layout(os);
1286 begin_inset(os, "Float " + unstarred_name + "\n");
1287 // store the float type for subfloats
1288 // subfloats only work with figures and tables
1289 if (unstarred_name == "figure")
1290 float_type = unstarred_name;
1291 else if (unstarred_name == "table")
1292 float_type = unstarred_name;
1296 os << "placement " << opt << '\n';
1297 if (contains(opt, "H"))
1298 preamble.registerAutomaticallyLoadedPackage("float");
1300 Floating const & fl = parent_context.textclass.floats()
1301 .getType(unstarred_name);
1302 if (!fl.floattype().empty() && fl.usesFloatPkg())
1303 preamble.registerAutomaticallyLoadedPackage("float");
1306 os << "wide " << convert<string>(is_starred)
1307 << "\nsideways false"
1308 << "\nstatus open\n\n";
1309 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1311 // We don't need really a new paragraph, but
1312 // we must make sure that the next item gets a \begin_layout.
1313 parent_context.new_paragraph(os);
1315 // the float is parsed thus delete the type
1319 else if (unstarred_name == "sidewaysfigure"
1320 || unstarred_name == "sidewaystable") {
1321 eat_whitespace(p, os, parent_context, false);
1322 parent_context.check_layout(os);
1323 if (unstarred_name == "sidewaysfigure")
1324 begin_inset(os, "Float figure\n");
1326 begin_inset(os, "Float table\n");
1327 os << "wide " << convert<string>(is_starred)
1328 << "\nsideways true"
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 preamble.registerAutomaticallyLoadedPackage("rotfloat");
1339 else if (name == "wrapfigure" || name == "wraptable") {
1340 // syntax is \begin{wrapfigure}[lines]{placement}[overhang]{width}
1341 eat_whitespace(p, os, parent_context, false);
1342 parent_context.check_layout(os);
1345 string overhang = "0col%";
1348 lines = p.getArg('[', ']');
1349 string const placement = p.getArg('{', '}');
1351 overhang = p.getArg('[', ']');
1352 string const width = p.getArg('{', '}');
1354 if (name == "wrapfigure")
1355 begin_inset(os, "Wrap figure\n");
1357 begin_inset(os, "Wrap table\n");
1358 os << "lines " << lines
1359 << "\nplacement " << placement
1360 << "\noverhang " << lyx::translate_len(overhang)
1361 << "\nwidth " << lyx::translate_len(width)
1362 << "\nstatus open\n\n";
1363 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1365 // We don't need really a new paragraph, but
1366 // we must make sure that the next item gets a \begin_layout.
1367 parent_context.new_paragraph(os);
1369 preamble.registerAutomaticallyLoadedPackage("wrapfig");
1372 else if (name == "minipage") {
1373 eat_whitespace(p, os, parent_context, false);
1374 // Test whether this is an outer box of a shaded box
1376 // swallow arguments
1377 while (p.hasOpt()) {
1379 p.skip_spaces(true);
1382 p.skip_spaces(true);
1383 Token t = p.get_token();
1384 bool shaded = false;
1385 if (t.asInput() == "\\begin") {
1386 p.skip_spaces(true);
1387 if (p.getArg('{', '}') == "shaded")
1392 parse_outer_box(p, os, FLAG_END, outer,
1393 parent_context, name, "shaded");
1395 parse_box(p, os, 0, FLAG_END, outer, parent_context,
1400 else if (name == "comment") {
1401 eat_whitespace(p, os, parent_context, false);
1402 parent_context.check_layout(os);
1403 begin_inset(os, "Note Comment\n");
1404 os << "status open\n";
1405 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1408 skip_braces(p); // eat {} that might by set by LyX behind comments
1409 preamble.registerAutomaticallyLoadedPackage("verbatim");
1412 else if (name == "verbatim") {
1413 os << "\n\\end_layout\n\n\\begin_layout Verbatim\n";
1414 string const s = p.plainEnvironment("verbatim");
1415 string::const_iterator it2 = s.begin();
1416 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
1418 os << "\\backslash ";
1419 else if (*it == '\n') {
1421 // avoid adding an empty paragraph at the end
1422 // FIXME: if there are 2 consecutive spaces at the end ignore it
1423 // because LyX will re-add a \n
1424 // This hack must be removed once bug 8049 is fixed!
1425 if ((it + 1 != et) && (it + 2 != et || *it2 != '\n'))
1426 os << "\n\\end_layout\n\\begin_layout Verbatim\n";
1430 os << "\n\\end_layout\n\n";
1432 // reset to Standard layout
1433 os << "\n\\begin_layout Standard\n";
1436 else if (name == "lyxgreyedout") {
1437 eat_whitespace(p, os, parent_context, false);
1438 parent_context.check_layout(os);
1439 begin_inset(os, "Note Greyedout\n");
1440 os << "status open\n";
1441 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1444 if (!preamble.notefontcolor().empty())
1445 preamble.registerAutomaticallyLoadedPackage("color");
1448 else if (name == "framed" || name == "shaded") {
1449 eat_whitespace(p, os, parent_context, false);
1450 parse_outer_box(p, os, FLAG_END, outer, parent_context, name, "");
1454 else if (name == "lstlisting") {
1455 eat_whitespace(p, os, parent_context, false);
1456 // FIXME handle the automatic color package loading
1457 // uwestoehr asks: In what case color is loaded?
1458 parse_listings(p, os, parent_context, false);
1462 else if (!parent_context.new_layout_allowed)
1463 parse_unknown_environment(p, name, os, FLAG_END, outer,
1466 // Alignment and spacing settings
1467 // FIXME (bug xxxx): These settings can span multiple paragraphs and
1468 // therefore are totally broken!
1469 // Note that \centering, raggedright, and raggedleft cannot be handled, as
1470 // they are commands not environments. They are furthermore switches that
1471 // can be ended by another switches, but also by commands like \footnote or
1472 // \parbox. So the only safe way is to leave them untouched.
1473 else if (name == "center" || name == "centering" ||
1474 name == "flushleft" || name == "flushright" ||
1475 name == "singlespace" || name == "onehalfspace" ||
1476 name == "doublespace" || name == "spacing") {
1477 eat_whitespace(p, os, parent_context, false);
1478 // We must begin a new paragraph if not already done
1479 if (! parent_context.atParagraphStart()) {
1480 parent_context.check_end_layout(os);
1481 parent_context.new_paragraph(os);
1483 if (name == "flushleft")
1484 parent_context.add_extra_stuff("\\align left\n");
1485 else if (name == "flushright")
1486 parent_context.add_extra_stuff("\\align right\n");
1487 else if (name == "center" || name == "centering")
1488 parent_context.add_extra_stuff("\\align center\n");
1489 else if (name == "singlespace")
1490 parent_context.add_extra_stuff("\\paragraph_spacing single\n");
1491 else if (name == "onehalfspace") {
1492 parent_context.add_extra_stuff("\\paragraph_spacing onehalf\n");
1493 preamble.registerAutomaticallyLoadedPackage("setspace");
1494 } else if (name == "doublespace") {
1495 parent_context.add_extra_stuff("\\paragraph_spacing double\n");
1496 preamble.registerAutomaticallyLoadedPackage("setspace");
1497 } else if (name == "spacing") {
1498 parent_context.add_extra_stuff("\\paragraph_spacing other " + p.verbatim_item() + "\n");
1499 preamble.registerAutomaticallyLoadedPackage("setspace");
1501 parse_text(p, os, FLAG_END, outer, parent_context);
1502 // Just in case the environment is empty
1503 parent_context.extra_stuff.erase();
1504 // We must begin a new paragraph to reset the alignment
1505 parent_context.new_paragraph(os);
1509 // The single '=' is meant here.
1510 else if ((newlayout = findLayout(parent_context.textclass, name, false))) {
1511 eat_whitespace(p, os, parent_context, false);
1512 Context context(true, parent_context.textclass, newlayout,
1513 parent_context.layout, parent_context.font);
1514 if (parent_context.deeper_paragraph) {
1515 // We are beginning a nested environment after a
1516 // deeper paragraph inside the outer list environment.
1517 // Therefore we don't need to output a "begin deeper".
1518 context.need_end_deeper = true;
1520 parent_context.check_end_layout(os);
1521 if (last_env == name) {
1522 // we need to output a separator since LyX would export
1523 // the two environments as one otherwise (bug 5716)
1524 docstring const sep = from_ascii("--Separator--");
1525 TeX2LyXDocClass const & textclass(parent_context.textclass);
1526 if (textclass.hasLayout(sep)) {
1527 Context newcontext(parent_context);
1528 newcontext.layout = &(textclass[sep]);
1529 newcontext.check_layout(os);
1530 newcontext.check_end_layout(os);
1532 parent_context.check_layout(os);
1533 begin_inset(os, "Note Note\n");
1534 os << "status closed\n";
1535 Context newcontext(true, textclass,
1536 &(textclass.defaultLayout()));
1537 newcontext.check_layout(os);
1538 newcontext.check_end_layout(os);
1540 parent_context.check_end_layout(os);
1543 switch (context.layout->latextype) {
1544 case LATEX_LIST_ENVIRONMENT:
1545 context.add_par_extra_stuff("\\labelwidthstring "
1546 + p.verbatim_item() + '\n');
1549 case LATEX_BIB_ENVIRONMENT:
1550 p.verbatim_item(); // swallow next arg
1556 context.check_deeper(os);
1557 // handle known optional and required arguments
1558 // layouts require all optional arguments before the required ones
1559 // Unfortunately LyX can't handle arguments of list arguments (bug 7468):
1560 // It is impossible to place anything after the environment name,
1561 // but before the first \\item.
1562 if (context.layout->latextype == LATEX_ENVIRONMENT) {
1563 bool need_layout = true;
1564 unsigned int optargs = 0;
1565 while (optargs < context.layout->optargs) {
1566 eat_whitespace(p, os, context, false);
1567 if (p.next_token().cat() == catEscape ||
1568 p.next_token().character() != '[')
1570 p.get_token(); // eat '['
1572 context.check_layout(os);
1573 need_layout = false;
1575 begin_inset(os, "Argument\n");
1576 os << "status collapsed\n\n";
1577 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
1579 eat_whitespace(p, os, context, false);
1582 unsigned int reqargs = 0;
1583 while (reqargs < context.layout->reqargs) {
1584 eat_whitespace(p, os, context, false);
1585 if (p.next_token().cat() != catBegin)
1587 p.get_token(); // eat '{'
1589 context.check_layout(os);
1590 need_layout = false;
1592 begin_inset(os, "Argument\n");
1593 os << "status collapsed\n\n";
1594 parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context);
1596 eat_whitespace(p, os, context, false);
1600 parse_text(p, os, FLAG_END, outer, context);
1601 context.check_end_layout(os);
1602 if (parent_context.deeper_paragraph) {
1603 // We must suppress the "end deeper" because we
1604 // suppressed the "begin deeper" above.
1605 context.need_end_deeper = false;
1607 context.check_end_deeper(os);
1608 parent_context.new_paragraph(os);
1610 if (!preamble.titleLayoutFound())
1611 preamble.titleLayoutFound(newlayout->intitle);
1612 set<string> const & req = newlayout->requires();
1613 set<string>::const_iterator it = req.begin();
1614 set<string>::const_iterator en = req.end();
1615 for (; it != en; ++it)
1616 preamble.registerAutomaticallyLoadedPackage(*it);
1619 // The single '=' is meant here.
1620 else if ((newinsetlayout = findInsetLayout(parent_context.textclass, name, false))) {
1621 eat_whitespace(p, os, parent_context, false);
1622 parent_context.check_layout(os);
1623 begin_inset(os, "Flex ");
1624 os << to_utf8(newinsetlayout->name()) << '\n'
1625 << "status collapsed\n";
1626 parse_text_in_inset(p, os, FLAG_END, false, parent_context, newinsetlayout);
1630 else if (name == "appendix") {
1631 // This is no good latex style, but it works and is used in some documents...
1632 eat_whitespace(p, os, parent_context, false);
1633 parent_context.check_end_layout(os);
1634 Context context(true, parent_context.textclass, parent_context.layout,
1635 parent_context.layout, parent_context.font);
1636 context.check_layout(os);
1637 os << "\\start_of_appendix\n";
1638 parse_text(p, os, FLAG_END, outer, context);
1639 context.check_end_layout(os);
1643 else if (known_environments.find(name) != known_environments.end()) {
1644 vector<ArgumentType> arguments = known_environments[name];
1645 // The last "argument" denotes wether we may translate the
1646 // environment contents to LyX
1647 // The default required if no argument is given makes us
1648 // compatible with the reLyXre environment.
1649 ArgumentType contents = arguments.empty() ?
1652 if (!arguments.empty())
1653 arguments.pop_back();
1654 // See comment in parse_unknown_environment()
1655 bool const specialfont =
1656 (parent_context.font != parent_context.normalfont);
1657 bool const new_layout_allowed =
1658 parent_context.new_layout_allowed;
1660 parent_context.new_layout_allowed = false;
1661 parse_arguments("\\begin{" + name + "}", arguments, p, os,
1662 outer, parent_context);
1663 if (contents == verbatim)
1664 handle_ert(os, p.verbatimEnvironment(name),
1667 parse_text_snippet(p, os, FLAG_END, outer,
1669 handle_ert(os, "\\end{" + name + "}", parent_context);
1671 parent_context.new_layout_allowed = new_layout_allowed;
1675 parse_unknown_environment(p, name, os, FLAG_END, outer,
1679 active_environments.pop_back();
1683 /// parses a comment and outputs it to \p os.
1684 void parse_comment(Parser & p, ostream & os, Token const & t, Context & context)
1686 LASSERT(t.cat() == catComment, return);
1687 if (!t.cs().empty()) {
1688 context.check_layout(os);
1689 handle_comment(os, '%' + t.cs(), context);
1690 if (p.next_token().cat() == catNewline) {
1691 // A newline after a comment line starts a new
1693 if (context.new_layout_allowed) {
1694 if(!context.atParagraphStart())
1695 // Only start a new paragraph if not already
1696 // done (we might get called recursively)
1697 context.new_paragraph(os);
1699 handle_ert(os, "\n", context);
1700 eat_whitespace(p, os, context, true);
1703 // "%\n" combination
1710 * Reads spaces and comments until the first non-space, non-comment token.
1711 * New paragraphs (double newlines or \\par) are handled like simple spaces
1712 * if \p eatParagraph is true.
1713 * Spaces are skipped, but comments are written to \p os.
1715 void eat_whitespace(Parser & p, ostream & os, Context & context,
1719 Token const & t = p.get_token();
1720 if (t.cat() == catComment)
1721 parse_comment(p, os, t, context);
1722 else if ((! eatParagraph && p.isParagraph()) ||
1723 (t.cat() != catSpace && t.cat() != catNewline)) {
1732 * Set a font attribute, parse text and reset the font attribute.
1733 * \param attribute Attribute name (e.g. \\family, \\shape etc.)
1734 * \param currentvalue Current value of the attribute. Is set to the new
1735 * value during parsing.
1736 * \param newvalue New value of the attribute
1738 void parse_text_attributes(Parser & p, ostream & os, unsigned flags, bool outer,
1739 Context & context, string const & attribute,
1740 string & currentvalue, string const & newvalue)
1742 context.check_layout(os);
1743 string const oldvalue = currentvalue;
1744 currentvalue = newvalue;
1745 os << '\n' << attribute << ' ' << newvalue << "\n";
1746 parse_text_snippet(p, os, flags, outer, context);
1747 context.check_layout(os);
1748 os << '\n' << attribute << ' ' << oldvalue << "\n";
1749 currentvalue = oldvalue;
1753 /// get the arguments of a natbib or jurabib citation command
1754 void get_cite_arguments(Parser & p, bool natbibOrder,
1755 string & before, string & after)
1757 // We need to distinguish "" and "[]", so we can't use p.getOpt().
1759 // text before the citation
1761 // text after the citation
1762 after = p.getFullOpt();
1764 if (!after.empty()) {
1765 before = p.getFullOpt();
1766 if (natbibOrder && !before.empty())
1767 swap(before, after);
1772 /// Convert filenames with TeX macros and/or quotes to something LyX
1774 string const normalize_filename(string const & name)
1776 Parser p(trim(name, "\""));
1779 Token const & t = p.get_token();
1780 if (t.cat() != catEscape)
1782 else if (t.cs() == "lyxdot") {
1783 // This is used by LyX for simple dots in relative
1787 } else if (t.cs() == "space") {
1797 /// Convert \p name from TeX convention (relative to master file) to LyX
1798 /// convention (relative to .lyx file) if it is relative
1799 void fix_relative_filename(string & name)
1801 if (FileName::isAbsolute(name))
1804 name = to_utf8(makeRelPath(from_utf8(makeAbsPath(name, getMasterFilePath()).absFileName()),
1805 from_utf8(getParentFilePath())));
1809 /// Parse a NoWeb Scrap section. The initial "<<" is already parsed.
1810 void parse_noweb(Parser & p, ostream & os, Context & context)
1812 // assemble the rest of the keyword
1816 Token const & t = p.get_token();
1817 if (t.asInput() == ">" && p.next_token().asInput() == ">") {
1820 scrap = (p.good() && p.next_token().asInput() == "=");
1822 name += p.get_token().asInput();
1825 name += t.asInput();
1828 if (!scrap || !context.new_layout_allowed ||
1829 !context.textclass.hasLayout(from_ascii("Scrap"))) {
1830 cerr << "Warning: Could not interpret '" << name
1831 << "'. Ignoring it." << endl;
1835 // We use new_paragraph instead of check_end_layout because the stuff
1836 // following the noweb chunk needs to start with a \begin_layout.
1837 // This may create a new paragraph even if there was none in the
1838 // noweb file, but the alternative is an invalid LyX file. Since
1839 // noweb code chunks are implemented with a layout style in LyX they
1840 // always must be in an own paragraph.
1841 context.new_paragraph(os);
1842 Context newcontext(true, context.textclass,
1843 &context.textclass[from_ascii("Scrap")]);
1844 newcontext.check_layout(os);
1847 Token const & t = p.get_token();
1848 // We abuse the parser a bit, because this is no TeX syntax
1850 if (t.cat() == catEscape)
1851 os << subst(t.asInput(), "\\", "\n\\backslash\n");
1854 Context tmp(false, context.textclass,
1855 &context.textclass[from_ascii("Scrap")]);
1856 tmp.need_end_layout = true;
1857 tmp.check_layout(oss);
1858 os << subst(t.asInput(), "\n", oss.str());
1860 // The scrap chunk is ended by an @ at the beginning of a line.
1861 // After the @ the line may contain a comment and/or
1862 // whitespace, but nothing else.
1863 if (t.asInput() == "@" && p.prev_token().cat() == catNewline &&
1864 (p.next_token().cat() == catSpace ||
1865 p.next_token().cat() == catNewline ||
1866 p.next_token().cat() == catComment)) {
1867 while (p.good() && p.next_token().cat() == catSpace)
1868 os << p.get_token().asInput();
1869 if (p.next_token().cat() == catComment)
1870 // The comment includes a final '\n'
1871 os << p.get_token().asInput();
1873 if (p.next_token().cat() == catNewline)
1880 newcontext.check_end_layout(os);
1884 /// detects \\def, \\long\\def and \\global\\long\\def with ws and comments
1885 bool is_macro(Parser & p)
1887 Token first = p.curr_token();
1888 if (first.cat() != catEscape || !p.good())
1890 if (first.cs() == "def")
1892 if (first.cs() != "global" && first.cs() != "long")
1894 Token second = p.get_token();
1896 while (p.good() && !p.isParagraph() && (second.cat() == catSpace ||
1897 second.cat() == catNewline || second.cat() == catComment)) {
1898 second = p.get_token();
1901 bool secondvalid = second.cat() == catEscape;
1903 bool thirdvalid = false;
1904 if (p.good() && first.cs() == "global" && secondvalid &&
1905 second.cs() == "long") {
1906 third = p.get_token();
1908 while (p.good() && !p.isParagraph() &&
1909 (third.cat() == catSpace ||
1910 third.cat() == catNewline ||
1911 third.cat() == catComment)) {
1912 third = p.get_token();
1915 thirdvalid = third.cat() == catEscape;
1917 for (int i = 0; i < pos; ++i)
1922 return (first.cs() == "global" || first.cs() == "long") &&
1923 second.cs() == "def";
1924 return first.cs() == "global" && second.cs() == "long" &&
1925 third.cs() == "def";
1929 /// Parse a macro definition (assumes that is_macro() returned true)
1930 void parse_macro(Parser & p, ostream & os, Context & context)
1932 context.check_layout(os);
1933 Token first = p.curr_token();
1936 string command = first.asInput();
1937 if (first.cs() != "def") {
1939 eat_whitespace(p, os, context, false);
1940 second = p.curr_token();
1941 command += second.asInput();
1942 if (second.cs() != "def") {
1944 eat_whitespace(p, os, context, false);
1945 third = p.curr_token();
1946 command += third.asInput();
1949 eat_whitespace(p, os, context, false);
1950 string const name = p.get_token().cs();
1951 eat_whitespace(p, os, context, false);
1957 while (p.next_token().cat() != catBegin) {
1958 if (p.next_token().cat() == catParameter) {
1963 // followed by number?
1964 if (p.next_token().cat() == catOther) {
1965 char c = p.getChar();
1967 // number = current arity + 1?
1968 if (c == arity + '0' + 1)
1973 paramtext += p.get_token().cs();
1975 paramtext += p.get_token().cs();
1980 // only output simple (i.e. compatible) macro as FormulaMacros
1981 string ert = '\\' + name + ' ' + paramtext + '{' + p.verbatim_item() + '}';
1983 context.check_layout(os);
1984 begin_inset(os, "FormulaMacro");
1985 os << "\n\\def" << ert;
1988 handle_ert(os, command + ert, context);
1992 void registerExternalTemplatePackages(string const & name)
1994 external::TemplateManager const & etm = external::TemplateManager::get();
1995 external::Template const * const et = etm.getTemplateByName(name);
1998 external::Template::Formats::const_iterator cit = et->formats.end();
2000 cit = et->formats.find("PDFLaTeX");
2001 if (cit == et->formats.end())
2002 // If the template has not specified a PDFLaTeX output,
2003 // we try the LaTeX format.
2004 cit = et->formats.find("LaTeX");
2005 if (cit == et->formats.end())
2007 vector<string>::const_iterator qit = cit->second.requirements.begin();
2008 vector<string>::const_iterator qend = cit->second.requirements.end();
2009 for (; qit != qend; ++qit)
2010 preamble.registerAutomaticallyLoadedPackage(*qit);
2013 } // anonymous namespace
2016 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
2019 Layout const * newlayout = 0;
2020 InsetLayout const * newinsetlayout = 0;
2021 char const * const * where = 0;
2022 // Store the latest bibliographystyle and nocite{*} option
2023 // (needed for bibtex inset)
2025 string bibliographystyle = "default";
2026 bool const use_natbib = preamble.isPackageUsed("natbib");
2027 bool const use_jurabib = preamble.isPackageUsed("jurabib");
2030 Token const & t = p.get_token();
2033 debugToken(cerr, t, flags);
2036 if (flags & FLAG_ITEM) {
2037 if (t.cat() == catSpace)
2040 flags &= ~FLAG_ITEM;
2041 if (t.cat() == catBegin) {
2042 // skip the brace and collect everything to the next matching
2044 flags |= FLAG_BRACE_LAST;
2048 // handle only this single token, leave the loop if done
2049 flags |= FLAG_LEAVE;
2052 if (t.cat() != catEscape && t.character() == ']' &&
2053 (flags & FLAG_BRACK_LAST))
2055 if (t.cat() == catEnd && (flags & FLAG_BRACE_LAST))
2058 // If there is anything between \end{env} and \begin{env} we
2059 // don't need to output a separator.
2060 if (t.cat() != catSpace && t.cat() != catNewline &&
2061 t.asInput() != "\\begin")
2067 if (t.cat() == catMath) {
2068 // we are inside some text mode thingy, so opening new math is allowed
2069 context.check_layout(os);
2070 begin_inset(os, "Formula ");
2071 Token const & n = p.get_token();
2072 bool const display(n.cat() == catMath && outer);
2074 // TeX's $$...$$ syntax for displayed math
2076 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
2078 p.get_token(); // skip the second '$' token
2080 // simple $...$ stuff
2083 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
2088 // Prevent the conversion of a line break to a
2089 // space (bug 7668). This does not change the
2090 // output, but looks ugly in LyX.
2091 eat_whitespace(p, os, context, false);
2095 else if (t.cat() == catSuper || t.cat() == catSub)
2096 cerr << "catcode " << t << " illegal in text mode\n";
2098 // Basic support for english quotes. This should be
2099 // extended to other quotes, but is not so easy (a
2100 // left english quote is the same as a right german
2102 else if (t.asInput() == "`" && p.next_token().asInput() == "`") {
2103 context.check_layout(os);
2104 begin_inset(os, "Quotes ");
2110 else if (t.asInput() == "'" && p.next_token().asInput() == "'") {
2111 context.check_layout(os);
2112 begin_inset(os, "Quotes ");
2119 else if (t.asInput() == ">" && p.next_token().asInput() == ">") {
2120 context.check_layout(os);
2121 begin_inset(os, "Quotes ");
2128 else if (t.asInput() == "<" && p.next_token().asInput() == "<") {
2129 context.check_layout(os);
2130 begin_inset(os, "Quotes ");
2137 else if (t.asInput() == "<"
2138 && p.next_token().asInput() == "<" && noweb_mode) {
2140 parse_noweb(p, os, context);
2143 else if (t.cat() == catSpace || (t.cat() == catNewline && ! p.isParagraph()))
2144 check_space(p, os, context);
2146 else if (t.character() == '[' && noweb_mode &&
2147 p.next_token().character() == '[') {
2148 // These can contain underscores
2150 string const s = p.getFullOpt() + ']';
2151 if (p.next_token().character() == ']')
2154 cerr << "Warning: Inserting missing ']' in '"
2155 << s << "'." << endl;
2156 handle_ert(os, s, context);
2159 else if (t.cat() == catLetter) {
2160 context.check_layout(os);
2161 // Workaround for bug 4752.
2162 // FIXME: This whole code block needs to be removed
2163 // when the bug is fixed and tex2lyx produces
2164 // the updated file format.
2165 // The replacement algorithm in LyX is so stupid that
2166 // it even translates a phrase if it is part of a word.
2167 bool handled = false;
2168 for (int const * l = known_phrase_lengths; *l; ++l) {
2169 string phrase = t.cs();
2170 for (int i = 1; i < *l && p.next_token().isAlnumASCII(); ++i)
2171 phrase += p.get_token().cs();
2172 if (is_known(phrase, known_coded_phrases)) {
2173 handle_ert(os, phrase, context);
2177 for (size_t i = 1; i < phrase.length(); ++i)
2185 else if (t.cat() == catOther ||
2186 t.cat() == catAlign ||
2187 t.cat() == catParameter) {
2188 // This translates "&" to "\\&" which may be wrong...
2189 context.check_layout(os);
2193 else if (p.isParagraph()) {
2194 if (context.new_layout_allowed)
2195 context.new_paragraph(os);
2197 handle_ert(os, "\\par ", context);
2198 eat_whitespace(p, os, context, true);
2201 else if (t.cat() == catActive) {
2202 context.check_layout(os);
2203 if (t.character() == '~') {
2204 if (context.layout->free_spacing)
2207 begin_inset(os, "space ~\n");
2214 else if (t.cat() == catBegin) {
2215 Token const next = p.next_token();
2216 Token const end = p.next_next_token();
2217 if (next.cat() == catEnd) {
2219 Token const prev = p.prev_token();
2221 if (p.next_token().character() == '`' ||
2222 (prev.character() == '-' &&
2223 p.next_token().character() == '-'))
2224 ; // ignore it in {}`` or -{}-
2226 handle_ert(os, "{}", context);
2227 } else if (next.cat() == catEscape &&
2228 is_known(next.cs(), known_quotes) &&
2229 end.cat() == catEnd) {
2230 // Something like {\textquoteright} (e.g.
2231 // from writer2latex). LyX writes
2232 // \textquoteright{}, so we may skip the
2233 // braces here for better readability.
2234 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2237 context.check_layout(os);
2238 // special handling of font attribute changes
2239 Token const prev = p.prev_token();
2240 TeXFont const oldFont = context.font;
2241 if (next.character() == '[' ||
2242 next.character() == ']' ||
2243 next.character() == '*') {
2245 if (p.next_token().cat() == catEnd) {
2250 handle_ert(os, "{", context);
2251 parse_text_snippet(p, os,
2254 handle_ert(os, "}", context);
2256 } else if (! context.new_layout_allowed) {
2257 handle_ert(os, "{", context);
2258 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2260 handle_ert(os, "}", context);
2261 } else if (is_known(next.cs(), known_sizes)) {
2262 // next will change the size, so we must
2264 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2266 if (!context.atParagraphStart())
2268 << context.font.size << "\n";
2269 } else if (is_known(next.cs(), known_font_families)) {
2270 // next will change the font family, so we
2271 // must reset it here
2272 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2274 if (!context.atParagraphStart())
2276 << context.font.family << "\n";
2277 } else if (is_known(next.cs(), known_font_series)) {
2278 // next will change the font series, so we
2279 // must reset it here
2280 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2282 if (!context.atParagraphStart())
2284 << context.font.series << "\n";
2285 } else if (is_known(next.cs(), known_font_shapes)) {
2286 // next will change the font shape, so we
2287 // must reset it here
2288 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2290 if (!context.atParagraphStart())
2292 << context.font.shape << "\n";
2293 } else if (is_known(next.cs(), known_old_font_families) ||
2294 is_known(next.cs(), known_old_font_series) ||
2295 is_known(next.cs(), known_old_font_shapes)) {
2296 // next will change the font family, series
2297 // and shape, so we must reset it here
2298 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2300 if (!context.atParagraphStart())
2302 << context.font.family
2304 << context.font.series
2306 << context.font.shape << "\n";
2308 handle_ert(os, "{", context);
2309 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2311 handle_ert(os, "}", context);
2316 else if (t.cat() == catEnd) {
2317 if (flags & FLAG_BRACE_LAST) {
2320 cerr << "stray '}' in text\n";
2321 handle_ert(os, "}", context);
2324 else if (t.cat() == catComment)
2325 parse_comment(p, os, t, context);
2328 // control sequences
2331 else if (t.cs() == "(") {
2332 context.check_layout(os);
2333 begin_inset(os, "Formula");
2335 parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
2340 else if (t.cs() == "[") {
2341 context.check_layout(os);
2342 begin_inset(os, "Formula");
2344 parse_math(p, os, FLAG_EQUATION, MATH_MODE);
2347 // Prevent the conversion of a line break to a space
2348 // (bug 7668). This does not change the output, but
2349 // looks ugly in LyX.
2350 eat_whitespace(p, os, context, false);
2353 else if (t.cs() == "begin")
2354 parse_environment(p, os, outer, last_env,
2357 else if (t.cs() == "end") {
2358 if (flags & FLAG_END) {
2359 // eat environment name
2360 string const name = p.getArg('{', '}');
2361 if (name != active_environment())
2362 cerr << "\\end{" + name + "} does not match \\begin{"
2363 + active_environment() + "}\n";
2366 p.error("found 'end' unexpectedly");
2369 else if (t.cs() == "item") {
2371 bool const optarg = p.hasOpt();
2373 // FIXME: This swallows comments, but we cannot use
2374 // eat_whitespace() since we must not output
2375 // anything before the item.
2376 p.skip_spaces(true);
2377 s = p.verbatimOption();
2379 p.skip_spaces(false);
2381 context.check_layout(os);
2382 if (context.has_item) {
2383 // An item in an unknown list-like environment
2384 // FIXME: Do this in check_layout()!
2385 context.has_item = false;
2387 handle_ert(os, "\\item", context);
2389 handle_ert(os, "\\item ", context);
2392 if (context.layout->labeltype != LABEL_MANUAL) {
2393 // LyX does not support \item[\mybullet]
2394 // in itemize environments
2396 os << parse_text_snippet(p2,
2397 FLAG_BRACK_LAST, outer, context);
2398 } else if (!s.empty()) {
2399 // LyX adds braces around the argument,
2400 // so we need to remove them here.
2401 if (s.size() > 2 && s[0] == '{' &&
2402 s[s.size()-1] == '}')
2403 s = s.substr(1, s.size()-2);
2404 // If the argument contains a space we
2405 // must put it into ERT: Otherwise LyX
2406 // would misinterpret the space as
2407 // item delimiter (bug 7663)
2408 if (contains(s, ' ')) {
2409 handle_ert(os, s, context);
2412 os << parse_text_snippet(p2,
2416 // The space is needed to separate the
2417 // item from the rest of the sentence.
2419 eat_whitespace(p, os, context, false);
2424 else if (t.cs() == "bibitem") {
2426 context.check_layout(os);
2427 eat_whitespace(p, os, context, false);
2428 string label = convert_command_inset_arg(p.verbatimOption());
2429 string key = convert_command_inset_arg(p.verbatim_item());
2430 if (contains(label, '\\') || contains(key, '\\')) {
2431 // LyX can't handle LaTeX commands in labels or keys
2432 handle_ert(os, t.asInput() + '[' + label +
2433 "]{" + p.verbatim_item() + '}',
2436 begin_command_inset(os, "bibitem", "bibitem");
2437 os << "label \"" << label << "\"\n"
2438 "key \"" << key << "\"\n";
2443 else if (is_macro(p)) {
2444 // catch the case of \def\inputGnumericTable
2446 if (t.cs() == "def") {
2447 Token second = p.next_token();
2448 if (second.cs() == "inputGnumericTable") {
2452 Token third = p.get_token();
2454 if (third.cs() == "input") {
2458 string name = normalize_filename(p.verbatim_item());
2459 string const path = getMasterFilePath();
2460 // We want to preserve relative / absolute filenames,
2461 // therefore path is only used for testing
2462 // The file extension is in every case ".tex".
2463 // So we need to remove this extension and check for
2464 // the original one.
2465 name = removeExtension(name);
2466 if (!makeAbsPath(name, path).exists()) {
2467 char const * const Gnumeric_formats[] = {"gnumeric",
2469 string const Gnumeric_name =
2470 find_file(name, path, Gnumeric_formats);
2471 if (!Gnumeric_name.empty())
2472 name = Gnumeric_name;
2474 if (makeAbsPath(name, path).exists())
2475 fix_relative_filename(name);
2477 cerr << "Warning: Could not find file '"
2478 << name << "'." << endl;
2479 context.check_layout(os);
2480 begin_inset(os, "External\n\ttemplate ");
2481 os << "GnumericSpreadsheet\n\tfilename "
2484 context.check_layout(os);
2486 // register the packages that are automatically reloaded
2487 // by the Gnumeric template
2488 registerExternalTemplatePackages("GnumericSpreadsheet");
2493 parse_macro(p, os, context);
2496 else if (t.cs() == "noindent") {
2498 context.add_par_extra_stuff("\\noindent\n");
2501 else if (t.cs() == "appendix") {
2502 context.add_par_extra_stuff("\\start_of_appendix\n");
2503 // We need to start a new paragraph. Otherwise the
2504 // appendix in 'bla\appendix\chapter{' would start
2506 context.new_paragraph(os);
2507 // We need to make sure that the paragraph is
2508 // generated even if it is empty. Otherwise the
2509 // appendix in '\par\appendix\par\chapter{' would
2511 context.check_layout(os);
2512 // FIXME: This is a hack to prevent paragraph
2513 // deletion if it is empty. Handle this better!
2515 "%dummy comment inserted by tex2lyx to "
2516 "ensure that this paragraph is not empty",
2518 // Both measures above may generate an additional
2519 // empty paragraph, but that does not hurt, because
2520 // whitespace does not matter here.
2521 eat_whitespace(p, os, context, true);
2524 // Must catch empty dates before findLayout is called below
2525 else if (t.cs() == "date") {
2526 eat_whitespace(p, os, context, false);
2528 string const date = p.verbatim_item();
2531 preamble.suppressDate(true);
2534 preamble.suppressDate(false);
2535 if (context.new_layout_allowed &&
2536 (newlayout = findLayout(context.textclass,
2539 output_command_layout(os, p, outer,
2540 context, newlayout);
2541 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2542 if (!preamble.titleLayoutFound())
2543 preamble.titleLayoutFound(newlayout->intitle);
2544 set<string> const & req = newlayout->requires();
2545 set<string>::const_iterator it = req.begin();
2546 set<string>::const_iterator en = req.end();
2547 for (; it != en; ++it)
2548 preamble.registerAutomaticallyLoadedPackage(*it);
2551 "\\date{" + p.verbatim_item() + '}',
2556 // Starred section headings
2557 // Must attempt to parse "Section*" before "Section".
2558 else if ((p.next_token().asInput() == "*") &&
2559 context.new_layout_allowed &&
2560 (newlayout = findLayout(context.textclass, t.cs() + '*', true))) {
2563 output_command_layout(os, p, outer, context, newlayout);
2565 if (!preamble.titleLayoutFound())
2566 preamble.titleLayoutFound(newlayout->intitle);
2567 set<string> const & req = newlayout->requires();
2568 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
2569 preamble.registerAutomaticallyLoadedPackage(*it);
2572 // Section headings and the like
2573 else if (context.new_layout_allowed &&
2574 (newlayout = findLayout(context.textclass, t.cs(), true))) {
2576 output_command_layout(os, p, outer, context, newlayout);
2578 if (!preamble.titleLayoutFound())
2579 preamble.titleLayoutFound(newlayout->intitle);
2580 set<string> const & req = newlayout->requires();
2581 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
2582 preamble.registerAutomaticallyLoadedPackage(*it);
2585 else if (t.cs() == "caption") {
2587 context.check_layout(os);
2589 begin_inset(os, "Caption\n");
2590 Context newcontext(true, context.textclass);
2591 newcontext.font = context.font;
2592 newcontext.check_layout(os);
2593 if (p.next_token().cat() != catEscape &&
2594 p.next_token().character() == '[') {
2595 p.get_token(); // eat '['
2596 begin_inset(os, "Argument\n");
2597 os << "status collapsed\n";
2598 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
2600 eat_whitespace(p, os, context, false);
2602 parse_text(p, os, FLAG_ITEM, outer, context);
2603 context.check_end_layout(os);
2604 // We don't need really a new paragraph, but
2605 // we must make sure that the next item gets a \begin_layout.
2606 context.new_paragraph(os);
2609 newcontext.check_end_layout(os);
2612 else if (t.cs() == "subfloat") {
2613 // the syntax is \subfloat[caption]{content}
2614 // if it is a table of figure depends on the surrounding float
2615 bool has_caption = false;
2617 // do nothing if there is no outer float
2618 if (!float_type.empty()) {
2619 context.check_layout(os);
2621 begin_inset(os, "Float " + float_type + "\n");
2623 << "\nsideways false"
2624 << "\nstatus collapsed\n\n";
2627 if (p.next_token().cat() != catEscape &&
2628 p.next_token().character() == '[') {
2629 p.get_token(); // eat '['
2630 caption = parse_text_snippet(p, FLAG_BRACK_LAST, outer, context);
2634 parse_text_in_inset(p, os, FLAG_ITEM, outer, context);
2635 // the caption comes always as the last
2637 // we must make sure that the caption gets a \begin_layout
2638 os << "\n\\begin_layout Plain Layout";
2640 begin_inset(os, "Caption\n");
2641 Context newcontext(true, context.textclass);
2642 newcontext.font = context.font;
2643 newcontext.check_layout(os);
2644 os << caption << "\n";
2645 newcontext.check_end_layout(os);
2646 // We don't need really a new paragraph, but
2647 // we must make sure that the next item gets a \begin_layout.
2648 //newcontext.new_paragraph(os);
2652 // We don't need really a new paragraph, but
2653 // we must make sure that the next item gets a \begin_layout.
2655 context.new_paragraph(os);
2658 context.check_end_layout(os);
2659 // close the layout we opened
2661 os << "\n\\end_layout\n";
2663 // if the float type is not supported or there is no surrounding float
2666 string opt_arg = convert_command_inset_arg(p.getArg('[', ']'));
2667 handle_ert(os, t.asInput() + '[' + opt_arg +
2668 "]{" + p.verbatim_item() + '}', context);
2670 handle_ert(os, t.asInput() + "{" + p.verbatim_item() + '}', context);
2674 else if (t.cs() == "includegraphics") {
2675 bool const clip = p.next_token().asInput() == "*";
2678 string const arg = p.getArg('[', ']');
2679 map<string, string> opts;
2680 vector<string> keys;
2681 split_map(arg, opts, keys);
2683 opts["clip"] = string();
2684 string name = normalize_filename(p.verbatim_item());
2686 string const path = getMasterFilePath();
2687 // We want to preserve relative / absolute filenames,
2688 // therefore path is only used for testing
2689 if (!makeAbsPath(name, path).exists()) {
2690 // The file extension is probably missing.
2691 // Now try to find it out.
2692 string const dvips_name =
2693 find_file(name, path,
2694 known_dvips_graphics_formats);
2695 string const pdftex_name =
2696 find_file(name, path,
2697 known_pdftex_graphics_formats);
2698 if (!dvips_name.empty()) {
2699 if (!pdftex_name.empty()) {
2700 cerr << "This file contains the "
2702 "\"\\includegraphics{"
2704 "However, files\n\""
2705 << dvips_name << "\" and\n\""
2706 << pdftex_name << "\"\n"
2707 "both exist, so I had to make a "
2708 "choice and took the first one.\n"
2709 "Please move the unwanted one "
2710 "someplace else and try again\n"
2711 "if my choice was wrong."
2715 } else if (!pdftex_name.empty()) {
2721 if (makeAbsPath(name, path).exists())
2722 fix_relative_filename(name);
2724 cerr << "Warning: Could not find graphics file '"
2725 << name << "'." << endl;
2727 context.check_layout(os);
2728 begin_inset(os, "Graphics ");
2729 os << "\n\tfilename " << name << '\n';
2730 if (opts.find("width") != opts.end())
2732 << translate_len(opts["width"]) << '\n';
2733 if (opts.find("height") != opts.end())
2735 << translate_len(opts["height"]) << '\n';
2736 if (opts.find("scale") != opts.end()) {
2737 istringstream iss(opts["scale"]);
2741 os << "\tscale " << val << '\n';
2743 if (opts.find("angle") != opts.end()) {
2744 os << "\trotateAngle "
2745 << opts["angle"] << '\n';
2746 vector<string>::const_iterator a =
2747 find(keys.begin(), keys.end(), "angle");
2748 vector<string>::const_iterator s =
2749 find(keys.begin(), keys.end(), "width");
2750 if (s == keys.end())
2751 s = find(keys.begin(), keys.end(), "height");
2752 if (s == keys.end())
2753 s = find(keys.begin(), keys.end(), "scale");
2754 if (s != keys.end() && distance(s, a) > 0)
2755 os << "\tscaleBeforeRotation\n";
2757 if (opts.find("origin") != opts.end()) {
2759 string const opt = opts["origin"];
2760 if (opt.find('l') != string::npos) ss << "left";
2761 if (opt.find('r') != string::npos) ss << "right";
2762 if (opt.find('c') != string::npos) ss << "center";
2763 if (opt.find('t') != string::npos) ss << "Top";
2764 if (opt.find('b') != string::npos) ss << "Bottom";
2765 if (opt.find('B') != string::npos) ss << "Baseline";
2766 if (!ss.str().empty())
2767 os << "\trotateOrigin " << ss.str() << '\n';
2769 cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
2771 if (opts.find("keepaspectratio") != opts.end())
2772 os << "\tkeepAspectRatio\n";
2773 if (opts.find("clip") != opts.end())
2775 if (opts.find("draft") != opts.end())
2777 if (opts.find("bb") != opts.end())
2778 os << "\tBoundingBox "
2779 << opts["bb"] << '\n';
2780 int numberOfbbOptions = 0;
2781 if (opts.find("bbllx") != opts.end())
2782 numberOfbbOptions++;
2783 if (opts.find("bblly") != opts.end())
2784 numberOfbbOptions++;
2785 if (opts.find("bburx") != opts.end())
2786 numberOfbbOptions++;
2787 if (opts.find("bbury") != opts.end())
2788 numberOfbbOptions++;
2789 if (numberOfbbOptions == 4)
2790 os << "\tBoundingBox "
2791 << opts["bbllx"] << " " << opts["bblly"] << " "
2792 << opts["bburx"] << " " << opts["bbury"] << '\n';
2793 else if (numberOfbbOptions > 0)
2794 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
2795 numberOfbbOptions = 0;
2796 if (opts.find("natwidth") != opts.end())
2797 numberOfbbOptions++;
2798 if (opts.find("natheight") != opts.end())
2799 numberOfbbOptions++;
2800 if (numberOfbbOptions == 2)
2801 os << "\tBoundingBox 0bp 0bp "
2802 << opts["natwidth"] << " " << opts["natheight"] << '\n';
2803 else if (numberOfbbOptions > 0)
2804 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
2805 ostringstream special;
2806 if (opts.find("hiresbb") != opts.end())
2807 special << "hiresbb,";
2808 if (opts.find("trim") != opts.end())
2810 if (opts.find("viewport") != opts.end())
2811 special << "viewport=" << opts["viewport"] << ',';
2812 if (opts.find("totalheight") != opts.end())
2813 special << "totalheight=" << opts["totalheight"] << ',';
2814 if (opts.find("type") != opts.end())
2815 special << "type=" << opts["type"] << ',';
2816 if (opts.find("ext") != opts.end())
2817 special << "ext=" << opts["ext"] << ',';
2818 if (opts.find("read") != opts.end())
2819 special << "read=" << opts["read"] << ',';
2820 if (opts.find("command") != opts.end())
2821 special << "command=" << opts["command"] << ',';
2822 string s_special = special.str();
2823 if (!s_special.empty()) {
2824 // We had special arguments. Remove the trailing ','.
2825 os << "\tspecial " << s_special.substr(0, s_special.size() - 1) << '\n';
2827 // TODO: Handle the unknown settings better.
2828 // Warn about invalid options.
2829 // Check whether some option was given twice.
2831 preamble.registerAutomaticallyLoadedPackage("graphicx");
2834 else if (t.cs() == "footnote" ||
2835 (t.cs() == "thanks" && context.layout->intitle)) {
2837 context.check_layout(os);
2838 begin_inset(os, "Foot\n");
2839 os << "status collapsed\n\n";
2840 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
2844 else if (t.cs() == "marginpar") {
2846 context.check_layout(os);
2847 begin_inset(os, "Marginal\n");
2848 os << "status collapsed\n\n";
2849 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
2853 else if (t.cs() == "lstinline") {
2855 parse_listings(p, os, context, true);
2858 else if (t.cs() == "ensuremath") {
2860 context.check_layout(os);
2861 string const s = p.verbatim_item();
2862 //FIXME: this never triggers in UTF8
2863 if (s == "\xb1" || s == "\xb3" || s == "\xb2" || s == "\xb5")
2866 handle_ert(os, "\\ensuremath{" + s + "}",
2870 else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
2871 if (preamble.titleLayoutFound()) {
2873 skip_spaces_braces(p);
2875 handle_ert(os, t.asInput(), context);
2878 else if (t.cs() == "tableofcontents" || t.cs() == "lstlistoflistings") {
2879 context.check_layout(os);
2880 begin_command_inset(os, "toc", t.cs());
2882 skip_spaces_braces(p);
2883 if (t.cs() == "lstlistoflistings")
2884 preamble.registerAutomaticallyLoadedPackage("listings");
2887 else if (t.cs() == "listoffigures") {
2888 context.check_layout(os);
2889 begin_inset(os, "FloatList figure\n");
2891 skip_spaces_braces(p);
2894 else if (t.cs() == "listoftables") {
2895 context.check_layout(os);
2896 begin_inset(os, "FloatList table\n");
2898 skip_spaces_braces(p);
2901 else if (t.cs() == "listof") {
2902 p.skip_spaces(true);
2903 string const name = p.get_token().cs();
2904 if (context.textclass.floats().typeExist(name)) {
2905 context.check_layout(os);
2906 begin_inset(os, "FloatList ");
2909 p.get_token(); // swallow second arg
2911 handle_ert(os, "\\listof{" + name + "}", context);
2914 else if ((where = is_known(t.cs(), known_text_font_families)))
2915 parse_text_attributes(p, os, FLAG_ITEM, outer,
2916 context, "\\family", context.font.family,
2917 known_coded_font_families[where - known_text_font_families]);
2919 else if ((where = is_known(t.cs(), known_text_font_series)))
2920 parse_text_attributes(p, os, FLAG_ITEM, outer,
2921 context, "\\series", context.font.series,
2922 known_coded_font_series[where - known_text_font_series]);
2924 else if ((where = is_known(t.cs(), known_text_font_shapes)))
2925 parse_text_attributes(p, os, FLAG_ITEM, outer,
2926 context, "\\shape", context.font.shape,
2927 known_coded_font_shapes[where - known_text_font_shapes]);
2929 else if (t.cs() == "textnormal" || t.cs() == "normalfont") {
2930 context.check_layout(os);
2931 TeXFont oldFont = context.font;
2932 context.font.init();
2933 context.font.size = oldFont.size;
2934 os << "\n\\family " << context.font.family << "\n";
2935 os << "\n\\series " << context.font.series << "\n";
2936 os << "\n\\shape " << context.font.shape << "\n";
2937 if (t.cs() == "textnormal") {
2938 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2939 output_font_change(os, context.font, oldFont);
2940 context.font = oldFont;
2942 eat_whitespace(p, os, context, false);
2945 else if (t.cs() == "textcolor") {
2946 // scheme is \textcolor{color name}{text}
2947 string const color = p.verbatim_item();
2948 // we only support the predefined colors of the color package
2949 if (color == "black" || color == "blue" || color == "cyan"
2950 || color == "green" || color == "magenta" || color == "red"
2951 || color == "white" || color == "yellow") {
2952 context.check_layout(os);
2953 os << "\n\\color " << color << "\n";
2954 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2955 context.check_layout(os);
2956 os << "\n\\color inherit\n";
2957 preamble.registerAutomaticallyLoadedPackage("color");
2959 // for custom defined colors
2960 handle_ert(os, t.asInput() + "{" + color + "}", context);
2963 else if (t.cs() == "underbar" || t.cs() == "uline") {
2964 // \underbar is not 100% correct (LyX outputs \uline
2965 // of ulem.sty). The difference is that \ulem allows
2966 // line breaks, and \underbar does not.
2967 // Do NOT handle \underline.
2968 // \underbar cuts through y, g, q, p etc.,
2969 // \underline does not.
2970 context.check_layout(os);
2971 os << "\n\\bar under\n";
2972 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2973 context.check_layout(os);
2974 os << "\n\\bar default\n";
2975 preamble.registerAutomaticallyLoadedPackage("ulem");
2978 else if (t.cs() == "sout") {
2979 context.check_layout(os);
2980 os << "\n\\strikeout on\n";
2981 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2982 context.check_layout(os);
2983 os << "\n\\strikeout default\n";
2984 preamble.registerAutomaticallyLoadedPackage("ulem");
2987 else if (t.cs() == "uuline" || t.cs() == "uwave" ||
2988 t.cs() == "emph" || t.cs() == "noun") {
2989 context.check_layout(os);
2990 os << "\n\\" << t.cs() << " on\n";
2991 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2992 context.check_layout(os);
2993 os << "\n\\" << t.cs() << " default\n";
2994 if (t.cs() == "uuline" || t.cs() == "uwave")
2995 preamble.registerAutomaticallyLoadedPackage("ulem");
2998 else if (t.cs() == "lyxadded" || t.cs() == "lyxdeleted") {
2999 context.check_layout(os);
3000 string name = p.getArg('{', '}');
3001 string localtime = p.getArg('{', '}');
3002 preamble.registerAuthor(name);
3003 Author const & author = preamble.getAuthor(name);
3004 // from_ctime() will fail if LyX decides to output the
3005 // time in the text language. It might also use a wrong
3006 // time zone (if the original LyX document was exported
3007 // with a different time zone).
3008 time_t ptime = from_ctime(localtime);
3009 if (ptime == static_cast<time_t>(-1)) {
3010 cerr << "Warning: Could not parse time `" << localtime
3011 << "´ for change tracking, using current time instead.\n";
3012 ptime = current_time();
3014 if (t.cs() == "lyxadded")
3015 os << "\n\\change_inserted ";
3017 os << "\n\\change_deleted ";
3018 os << author.bufferId() << ' ' << ptime << '\n';
3019 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
3020 bool dvipost = LaTeXPackages::isAvailable("dvipost");
3021 bool xcolorulem = LaTeXPackages::isAvailable("ulem") &&
3022 LaTeXPackages::isAvailable("xcolor");
3023 // No need to test for luatex, since luatex comes in
3024 // two flavours (dvi and pdf), like latex, and those
3025 // are detected by pdflatex.
3026 if (pdflatex || xetex) {
3028 preamble.registerAutomaticallyLoadedPackage("ulem");
3029 preamble.registerAutomaticallyLoadedPackage("xcolor");
3030 preamble.registerAutomaticallyLoadedPackage("pdfcolmk");
3034 preamble.registerAutomaticallyLoadedPackage("dvipost");
3035 } else if (xcolorulem) {
3036 preamble.registerAutomaticallyLoadedPackage("ulem");
3037 preamble.registerAutomaticallyLoadedPackage("xcolor");
3042 else if (t.cs() == "phantom" || t.cs() == "hphantom" ||
3043 t.cs() == "vphantom") {
3044 context.check_layout(os);
3045 if (t.cs() == "phantom")
3046 begin_inset(os, "Phantom Phantom\n");
3047 if (t.cs() == "hphantom")
3048 begin_inset(os, "Phantom HPhantom\n");
3049 if (t.cs() == "vphantom")
3050 begin_inset(os, "Phantom VPhantom\n");
3051 os << "status open\n";
3052 parse_text_in_inset(p, os, FLAG_ITEM, outer, context,
3057 else if (t.cs() == "href") {
3058 context.check_layout(os);
3059 string target = p.getArg('{', '}');
3060 string name = p.getArg('{', '}');
3062 size_t i = target.find(':');
3063 if (i != string::npos) {
3064 type = target.substr(0, i + 1);
3065 if (type == "mailto:" || type == "file:")
3066 target = target.substr(i + 1);
3067 // handle the case that name is equal to target, except of "http://"
3068 else if (target.substr(i + 3) == name && type == "http:")
3071 begin_command_inset(os, "href", "href");
3073 os << "name \"" << name << "\"\n";
3074 os << "target \"" << target << "\"\n";
3075 if (type == "mailto:" || type == "file:")
3076 os << "type \"" << type << "\"\n";
3078 skip_spaces_braces(p);
3081 else if (t.cs() == "lyxline") {
3082 // swallow size argument (it is not used anyway)
3084 if (!context.atParagraphStart()) {
3085 // so our line is in the middle of a paragraph
3086 // we need to add a new line, lest this line
3087 // follow the other content on that line and
3088 // run off the side of the page
3089 // FIXME: This may create an empty paragraph,
3090 // but without that it would not be
3091 // possible to set noindent below.
3092 // Fortunately LaTeX does not care
3093 // about the empty paragraph.
3094 context.new_paragraph(os);
3096 if (preamble.indentParagraphs()) {
3097 // we need to unindent, lest the line be too long
3098 context.add_par_extra_stuff("\\noindent\n");
3100 context.check_layout(os);
3101 begin_command_inset(os, "line", "rule");
3102 os << "offset \"0.5ex\"\n"
3103 "width \"100line%\"\n"
3108 else if (t.cs() == "rule") {
3109 string const offset = (p.hasOpt() ? p.getArg('[', ']') : string());
3110 string const width = p.getArg('{', '}');
3111 string const thickness = p.getArg('{', '}');
3112 context.check_layout(os);
3113 begin_command_inset(os, "line", "rule");
3114 if (!offset.empty())
3115 os << "offset \"" << translate_len(offset) << "\"\n";
3116 os << "width \"" << translate_len(width) << "\"\n"
3117 "height \"" << translate_len(thickness) << "\"\n";
3121 else if (is_known(t.cs(), known_phrases) ||
3122 (t.cs() == "protect" &&
3123 p.next_token().cat() == catEscape &&
3124 is_known(p.next_token().cs(), known_phrases))) {
3125 // LyX sometimes puts a \protect in front, so we have to ignore it
3126 // FIXME: This needs to be changed when bug 4752 is fixed.
3128 t.cs() == "protect" ? p.get_token().cs() : t.cs(),
3130 context.check_layout(os);
3131 os << known_coded_phrases[where - known_phrases];
3132 skip_spaces_braces(p);
3135 else if ((where = is_known(t.cs(), known_ref_commands))) {
3136 string const opt = p.getOpt();
3138 context.check_layout(os);
3139 begin_command_inset(os, "ref",
3140 known_coded_ref_commands[where - known_ref_commands]);
3141 os << "reference \""
3142 << convert_command_inset_arg(p.verbatim_item())
3145 if (t.cs() == "vref" || t.cs() == "vpageref")
3146 preamble.registerAutomaticallyLoadedPackage("varioref");
3149 // LyX does not support optional arguments of ref commands
3150 handle_ert(os, t.asInput() + '[' + opt + "]{" +
3151 p.verbatim_item() + "}", context);
3155 else if (use_natbib &&
3156 is_known(t.cs(), known_natbib_commands) &&
3157 ((t.cs() != "citefullauthor" &&
3158 t.cs() != "citeyear" &&
3159 t.cs() != "citeyearpar") ||
3160 p.next_token().asInput() != "*")) {
3161 context.check_layout(os);
3162 string command = t.cs();
3163 if (p.next_token().asInput() == "*") {
3167 if (command == "citefullauthor")
3168 // alternative name for "\\citeauthor*"
3169 command = "citeauthor*";
3171 // text before the citation
3173 // text after the citation
3175 get_cite_arguments(p, true, before, after);
3177 if (command == "cite") {
3178 // \cite without optional argument means
3179 // \citet, \cite with at least one optional
3180 // argument means \citep.
3181 if (before.empty() && after.empty())
3186 if (before.empty() && after == "[]")
3187 // avoid \citet[]{a}
3189 else if (before == "[]" && after == "[]") {
3190 // avoid \citet[][]{a}
3194 // remove the brackets around after and before
3195 if (!after.empty()) {
3197 after.erase(after.length() - 1, 1);
3198 after = convert_command_inset_arg(after);
3200 if (!before.empty()) {
3202 before.erase(before.length() - 1, 1);
3203 before = convert_command_inset_arg(before);
3205 begin_command_inset(os, "citation", command);
3206 os << "after " << '"' << after << '"' << "\n";
3207 os << "before " << '"' << before << '"' << "\n";
3209 << convert_command_inset_arg(p.verbatim_item())
3214 else if (use_jurabib &&
3215 is_known(t.cs(), known_jurabib_commands) &&
3216 (t.cs() == "cite" || p.next_token().asInput() != "*")) {
3217 context.check_layout(os);
3218 string command = t.cs();
3219 if (p.next_token().asInput() == "*") {
3223 char argumentOrder = '\0';
3224 vector<string> const options =
3225 preamble.getPackageOptions("jurabib");
3226 if (find(options.begin(), options.end(),
3227 "natbiborder") != options.end())
3228 argumentOrder = 'n';
3229 else if (find(options.begin(), options.end(),
3230 "jurabiborder") != options.end())
3231 argumentOrder = 'j';
3233 // text before the citation
3235 // text after the citation
3237 get_cite_arguments(p, argumentOrder != 'j', before, after);
3239 string const citation = p.verbatim_item();
3240 if (!before.empty() && argumentOrder == '\0') {
3241 cerr << "Warning: Assuming argument order "
3242 "of jurabib version 0.6 for\n'"
3243 << command << before << after << '{'
3244 << citation << "}'.\n"
3245 "Add 'jurabiborder' to the jurabib "
3246 "package options if you used an\n"
3247 "earlier jurabib version." << endl;
3249 if (!after.empty()) {
3251 after.erase(after.length() - 1, 1);
3253 if (!before.empty()) {
3255 before.erase(before.length() - 1, 1);
3257 begin_command_inset(os, "citation", command);
3258 os << "after " << '"' << after << '"' << "\n";
3259 os << "before " << '"' << before << '"' << "\n";
3260 os << "key " << '"' << citation << '"' << "\n";
3264 else if (t.cs() == "cite"
3265 || t.cs() == "nocite") {
3266 context.check_layout(os);
3267 string after = convert_command_inset_arg(p.getArg('[', ']'));
3268 string key = convert_command_inset_arg(p.verbatim_item());
3269 // store the case that it is "\nocite{*}" to use it later for
3272 begin_command_inset(os, "citation", t.cs());
3273 os << "after " << '"' << after << '"' << "\n";
3274 os << "key " << '"' << key << '"' << "\n";
3276 } else if (t.cs() == "nocite")
3280 else if (t.cs() == "index" ||
3281 (t.cs() == "sindex" && preamble.use_indices() == "true")) {
3282 context.check_layout(os);
3283 string const arg = (t.cs() == "sindex" && p.hasOpt()) ?
3284 p.getArg('[', ']') : "";
3285 string const kind = arg.empty() ? "idx" : arg;
3286 begin_inset(os, "Index ");
3287 os << kind << "\nstatus collapsed\n";
3288 parse_text_in_inset(p, os, FLAG_ITEM, false, context, "Index");
3291 preamble.registerAutomaticallyLoadedPackage("splitidx");
3294 else if (t.cs() == "nomenclature") {
3295 context.check_layout(os);
3296 begin_command_inset(os, "nomenclature", "nomenclature");
3297 string prefix = convert_command_inset_arg(p.getArg('[', ']'));
3298 if (!prefix.empty())
3299 os << "prefix " << '"' << prefix << '"' << "\n";
3300 os << "symbol " << '"'
3301 << convert_command_inset_arg(p.verbatim_item());
3302 os << "\"\ndescription \""
3303 << convert_command_inset_arg(p.verbatim_item())
3306 preamble.registerAutomaticallyLoadedPackage("nomencl");
3309 else if (t.cs() == "label") {
3310 context.check_layout(os);
3311 begin_command_inset(os, "label", "label");
3313 << convert_command_inset_arg(p.verbatim_item())
3318 else if (t.cs() == "printindex") {
3319 context.check_layout(os);
3320 begin_command_inset(os, "index_print", "printindex");
3321 os << "type \"idx\"\n";
3323 skip_spaces_braces(p);
3324 preamble.registerAutomaticallyLoadedPackage("makeidx");
3325 if (preamble.use_indices() == "true")
3326 preamble.registerAutomaticallyLoadedPackage("splitidx");
3329 else if (t.cs() == "printnomenclature") {
3331 string width_type = "";
3332 context.check_layout(os);
3333 begin_command_inset(os, "nomencl_print", "printnomenclature");
3334 // case of a custom width
3336 width = p.getArg('[', ']');
3337 width = translate_len(width);
3338 width_type = "custom";
3340 // case of no custom width
3341 // the case of no custom width but the width set
3342 // via \settowidth{\nomlabelwidth}{***} cannot be supported
3343 // because the user could have set anything, not only the width
3344 // of the longest label (which would be width_type = "auto")
3345 string label = convert_command_inset_arg(p.getArg('{', '}'));
3346 if (label.empty() && width_type.empty())
3347 width_type = "none";
3348 os << "set_width \"" << width_type << "\"\n";
3349 if (width_type == "custom")
3350 os << "width \"" << width << '\"';
3352 skip_spaces_braces(p);
3353 preamble.registerAutomaticallyLoadedPackage("nomencl");
3356 else if ((t.cs() == "textsuperscript" || t.cs() == "textsubscript")) {
3357 context.check_layout(os);
3358 begin_inset(os, "script ");
3359 os << t.cs().substr(4) << '\n';
3360 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
3362 if (t.cs() == "textsubscript")
3363 preamble.registerAutomaticallyLoadedPackage("subscript");
3366 else if ((where = is_known(t.cs(), known_quotes))) {
3367 context.check_layout(os);
3368 begin_inset(os, "Quotes ");
3369 os << known_coded_quotes[where - known_quotes];
3371 // LyX adds {} after the quote, so we have to eat
3372 // spaces here if there are any before a possible
3374 eat_whitespace(p, os, context, false);
3378 else if ((where = is_known(t.cs(), known_sizes)) &&
3379 context.new_layout_allowed) {
3380 context.check_layout(os);
3381 TeXFont const oldFont = context.font;
3382 context.font.size = known_coded_sizes[where - known_sizes];
3383 output_font_change(os, oldFont, context.font);
3384 eat_whitespace(p, os, context, false);
3387 else if ((where = is_known(t.cs(), known_font_families)) &&
3388 context.new_layout_allowed) {
3389 context.check_layout(os);
3390 TeXFont const oldFont = context.font;
3391 context.font.family =
3392 known_coded_font_families[where - known_font_families];
3393 output_font_change(os, oldFont, context.font);
3394 eat_whitespace(p, os, context, false);
3397 else if ((where = is_known(t.cs(), known_font_series)) &&
3398 context.new_layout_allowed) {
3399 context.check_layout(os);
3400 TeXFont const oldFont = context.font;
3401 context.font.series =
3402 known_coded_font_series[where - known_font_series];
3403 output_font_change(os, oldFont, context.font);
3404 eat_whitespace(p, os, context, false);
3407 else if ((where = is_known(t.cs(), known_font_shapes)) &&
3408 context.new_layout_allowed) {
3409 context.check_layout(os);
3410 TeXFont const oldFont = context.font;
3411 context.font.shape =
3412 known_coded_font_shapes[where - known_font_shapes];
3413 output_font_change(os, oldFont, context.font);
3414 eat_whitespace(p, os, context, false);
3416 else if ((where = is_known(t.cs(), known_old_font_families)) &&
3417 context.new_layout_allowed) {
3418 context.check_layout(os);
3419 TeXFont const oldFont = context.font;
3420 context.font.init();
3421 context.font.size = oldFont.size;
3422 context.font.family =
3423 known_coded_font_families[where - known_old_font_families];
3424 output_font_change(os, oldFont, context.font);
3425 eat_whitespace(p, os, context, false);
3428 else if ((where = is_known(t.cs(), known_old_font_series)) &&
3429 context.new_layout_allowed) {
3430 context.check_layout(os);
3431 TeXFont const oldFont = context.font;
3432 context.font.init();
3433 context.font.size = oldFont.size;
3434 context.font.series =
3435 known_coded_font_series[where - known_old_font_series];
3436 output_font_change(os, oldFont, context.font);
3437 eat_whitespace(p, os, context, false);
3440 else if ((where = is_known(t.cs(), known_old_font_shapes)) &&
3441 context.new_layout_allowed) {
3442 context.check_layout(os);
3443 TeXFont const oldFont = context.font;
3444 context.font.init();
3445 context.font.size = oldFont.size;
3446 context.font.shape =
3447 known_coded_font_shapes[where - known_old_font_shapes];
3448 output_font_change(os, oldFont, context.font);
3449 eat_whitespace(p, os, context, false);
3452 else if (t.cs() == "selectlanguage") {
3453 context.check_layout(os);
3454 // save the language for the case that a
3455 // \foreignlanguage is used
3456 context.font.language = babel2lyx(p.verbatim_item());
3457 os << "\n\\lang " << context.font.language << "\n";
3460 else if (t.cs() == "foreignlanguage") {
3461 string const lang = babel2lyx(p.verbatim_item());
3462 parse_text_attributes(p, os, FLAG_ITEM, outer,
3464 context.font.language, lang);
3467 else if (is_known(t.cs().substr(4, string::npos), polyglossia_languages)) {
3468 // scheme is \textLANGUAGE{text} where LANGUAGE is in polyglossia_languages[]
3470 // We have to output the whole command if it has an option
3471 // because LyX doesn't support this yet, see bug #8214,
3472 // only if there is a single option specifying a variant, we can handle it.
3474 string langopts = p.getOpt();
3475 // check if the option contains a variant, if yes, extract it
3476 string::size_type pos_var = langopts.find("variant");
3477 string::size_type i = langopts.find(',');
3478 if (pos_var != string::npos){
3480 if (i == string::npos) {
3481 variant = langopts.substr(pos_var + 8, langopts.length() - pos_var - 9);
3482 lang = polyglossia2lyx(variant);
3483 parse_text_attributes(p, os, FLAG_ITEM, outer,
3485 context.font.language, lang);
3488 handle_ert(os, t.asInput() + langopts, context);
3490 handle_ert(os, t.asInput() + langopts, context);
3492 lang = polyglossia2lyx(t.cs().substr(4, string::npos));
3493 parse_text_attributes(p, os, FLAG_ITEM, outer,
3495 context.font.language, lang);
3499 else if (t.cs() == "inputencoding") {
3500 // nothing to write here
3501 string const enc = subst(p.verbatim_item(), "\n", " ");
3505 else if ((where = is_known(t.cs(), known_special_chars))) {
3506 context.check_layout(os);
3507 os << "\\SpecialChar \\"
3508 << known_coded_special_chars[where - known_special_chars]
3510 skip_spaces_braces(p);
3513 else if (t.cs() == "nobreakdash" && p.next_token().asInput() == "-") {
3514 context.check_layout(os);
3515 os << "\\SpecialChar \\nobreakdash-\n";
3519 else if (t.cs() == "textquotedbl") {
3520 context.check_layout(os);
3525 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
3526 context.check_layout(os);
3527 os << "\\SpecialChar \\@.\n";
3531 else if (t.cs() == "-") {
3532 context.check_layout(os);
3533 os << "\\SpecialChar \\-\n";
3536 else if (t.cs() == "textasciitilde") {
3537 context.check_layout(os);
3539 skip_spaces_braces(p);
3542 else if (t.cs() == "textasciicircum") {
3543 context.check_layout(os);
3545 skip_spaces_braces(p);
3548 else if (t.cs() == "textbackslash") {
3549 context.check_layout(os);
3550 os << "\n\\backslash\n";
3551 skip_spaces_braces(p);
3554 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
3555 || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
3557 context.check_layout(os);
3561 else if (t.cs() == "char") {
3562 context.check_layout(os);
3563 if (p.next_token().character() == '`') {
3565 if (p.next_token().cs() == "\"") {
3570 handle_ert(os, "\\char`", context);
3573 handle_ert(os, "\\char", context);
3577 else if (t.cs() == "verb") {
3578 context.check_layout(os);
3579 char const delimiter = p.next_token().character();
3580 string const arg = p.getArg(delimiter, delimiter);
3582 oss << "\\verb" << delimiter << arg << delimiter;
3583 handle_ert(os, oss.str(), context);
3586 // Problem: \= creates a tabstop inside the tabbing environment
3587 // and else an accent. In the latter case we really would want
3588 // \={o} instead of \= o.
3589 else if (t.cs() == "=" && (flags & FLAG_TABBING))
3590 handle_ert(os, t.asInput(), context);
3592 // accents (see Table 6 in Comprehensive LaTeX Symbol List)
3593 else if (t.cs().size() == 1
3594 && contains("\"'.=^`bcdHkrtuv~", t.cs())) {
3595 context.check_layout(os);
3596 // try to see whether the string is in unicodesymbols
3599 string command = t.asInput() + "{"
3600 + trimSpaceAndEol(p.verbatim_item())
3603 docstring s = encodings.fromLaTeXCommand(from_utf8(command),
3604 Encodings::TEXT_CMD | Encodings::MATH_CMD,
3605 termination, rem, &req);
3608 cerr << "When parsing " << command
3609 << ", result is " << to_utf8(s)
3610 << "+" << to_utf8(rem) << endl;
3612 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
3613 preamble.registerAutomaticallyLoadedPackage(*it);
3615 // we did not find a non-ert version
3616 handle_ert(os, command, context);
3619 else if (t.cs() == "\\") {
3620 context.check_layout(os);
3622 handle_ert(os, "\\\\" + p.getOpt(), context);
3623 else if (p.next_token().asInput() == "*") {
3625 // getOpt() eats the following space if there
3626 // is no optional argument, but that is OK
3627 // here since it has no effect in the output.
3628 handle_ert(os, "\\\\*" + p.getOpt(), context);
3631 begin_inset(os, "Newline newline");
3636 else if (t.cs() == "newline" ||
3637 (t.cs() == "linebreak" && !p.hasOpt())) {
3638 context.check_layout(os);
3639 begin_inset(os, "Newline ");
3642 skip_spaces_braces(p);
3645 else if (t.cs() == "input" || t.cs() == "include"
3646 || t.cs() == "verbatiminput") {
3647 string name = t.cs();
3648 if (t.cs() == "verbatiminput"
3649 && p.next_token().asInput() == "*")
3650 name += p.get_token().asInput();
3651 context.check_layout(os);
3652 string filename(normalize_filename(p.getArg('{', '}')));
3653 string const path = getMasterFilePath();
3654 // We want to preserve relative / absolute filenames,
3655 // therefore path is only used for testing
3656 if ((t.cs() == "include" || t.cs() == "input") &&
3657 !makeAbsPath(filename, path).exists()) {
3658 // The file extension is probably missing.
3659 // Now try to find it out.
3660 string const tex_name =
3661 find_file(filename, path,
3662 known_tex_extensions);
3663 if (!tex_name.empty())
3664 filename = tex_name;
3666 bool external = false;
3668 if (makeAbsPath(filename, path).exists()) {
3669 string const abstexname =
3670 makeAbsPath(filename, path).absFileName();
3671 string const abslyxname =
3672 changeExtension(abstexname, ".lyx");
3673 string const absfigname =
3674 changeExtension(abstexname, ".fig");
3675 fix_relative_filename(filename);
3676 string const lyxname =
3677 changeExtension(filename, ".lyx");
3679 external = FileName(absfigname).exists();
3680 if (t.cs() == "input") {
3681 string const ext = getExtension(abstexname);
3683 // Combined PS/LaTeX:
3684 // x.eps, x.pstex_t (old xfig)
3685 // x.pstex, x.pstex_t (new xfig, e.g. 3.2.5)
3686 FileName const absepsname(
3687 changeExtension(abstexname, ".eps"));
3688 FileName const abspstexname(
3689 changeExtension(abstexname, ".pstex"));
3690 bool const xfigeps =
3691 (absepsname.exists() ||
3692 abspstexname.exists()) &&
3695 // Combined PDF/LaTeX:
3696 // x.pdf, x.pdftex_t (old xfig)
3697 // x.pdf, x.pdf_t (new xfig, e.g. 3.2.5)
3698 FileName const abspdfname(
3699 changeExtension(abstexname, ".pdf"));
3700 bool const xfigpdf =
3701 abspdfname.exists() &&
3702 (ext == "pdftex_t" || ext == "pdf_t");
3706 // Combined PS/PDF/LaTeX:
3707 // x_pspdftex.eps, x_pspdftex.pdf, x.pspdftex
3708 string const absbase2(
3709 removeExtension(abstexname) + "_pspdftex");
3710 FileName const abseps2name(
3711 addExtension(absbase2, ".eps"));
3712 FileName const abspdf2name(
3713 addExtension(absbase2, ".pdf"));
3714 bool const xfigboth =
3715 abspdf2name.exists() &&
3716 abseps2name.exists() && ext == "pspdftex";
3718 xfig = xfigpdf || xfigeps || xfigboth;
3719 external = external && xfig;
3722 outname = changeExtension(filename, ".fig");
3724 // Don't try to convert, the result
3725 // would be full of ERT.
3727 } else if (t.cs() != "verbatiminput" &&
3728 tex2lyx(abstexname, FileName(abslyxname),
3735 cerr << "Warning: Could not find included file '"
3736 << filename << "'." << endl;
3740 begin_inset(os, "External\n");
3741 os << "\ttemplate XFig\n"
3742 << "\tfilename " << outname << '\n';
3743 registerExternalTemplatePackages("XFig");
3745 begin_command_inset(os, "include", name);
3746 os << "preview false\n"
3747 "filename \"" << outname << "\"\n";
3748 if (t.cs() == "verbatiminput")
3749 preamble.registerAutomaticallyLoadedPackage("verbatim");
3754 else if (t.cs() == "bibliographystyle") {
3755 // store new bibliographystyle
3756 bibliographystyle = p.verbatim_item();
3757 // If any other command than \bibliography and
3758 // \nocite{*} follows, we need to output the style
3759 // (because it might be used by that command).
3760 // Otherwise, it will automatically be output by LyX.
3763 for (Token t2 = p.get_token(); p.good(); t2 = p.get_token()) {
3764 if (t2.cat() == catBegin)
3766 if (t2.cat() != catEscape)
3768 if (t2.cs() == "nocite") {
3769 if (p.getArg('{', '}') == "*")
3771 } else if (t2.cs() == "bibliography")
3778 "\\bibliographystyle{" + bibliographystyle + '}',
3783 else if (t.cs() == "bibliography") {
3784 context.check_layout(os);
3785 begin_command_inset(os, "bibtex", "bibtex");
3786 if (!btprint.empty()) {
3787 os << "btprint " << '"' << "btPrintAll" << '"' << "\n";
3788 // clear the string because the next BibTeX inset can be without the
3789 // \nocite{*} option
3792 os << "bibfiles " << '"' << p.verbatim_item() << '"' << "\n";
3793 // Do we have a bibliographystyle set?
3794 if (!bibliographystyle.empty())
3795 os << "options " << '"' << bibliographystyle << '"' << "\n";
3799 else if (t.cs() == "parbox") {
3800 // Test whether this is an outer box of a shaded box
3802 // swallow arguments
3803 while (p.hasOpt()) {
3805 p.skip_spaces(true);
3808 p.skip_spaces(true);
3810 if (p.next_token().cat() == catBegin)
3812 p.skip_spaces(true);
3813 Token to = p.get_token();
3814 bool shaded = false;
3815 if (to.asInput() == "\\begin") {
3816 p.skip_spaces(true);
3817 if (p.getArg('{', '}') == "shaded")
3822 parse_outer_box(p, os, FLAG_ITEM, outer,
3823 context, "parbox", "shaded");
3825 parse_box(p, os, 0, FLAG_ITEM, outer, context,
3829 else if (t.cs() == "ovalbox" || t.cs() == "Ovalbox" ||
3830 t.cs() == "shadowbox" || t.cs() == "doublebox")
3831 parse_outer_box(p, os, FLAG_ITEM, outer, context, t.cs(), "");
3833 else if (t.cs() == "framebox") {
3834 if (p.next_token().character() == '(') {
3835 //the syntax is: \framebox(x,y)[position]{content}
3836 string arg = t.asInput();
3837 arg += p.getFullParentheseArg();
3838 arg += p.getFullOpt();
3839 eat_whitespace(p, os, context, false);
3840 handle_ert(os, arg + '{', context);
3841 eat_whitespace(p, os, context, false);
3842 parse_text(p, os, FLAG_ITEM, outer, context);
3843 handle_ert(os, "}", context);
3845 string special = p.getFullOpt();
3846 special += p.getOpt();
3847 parse_outer_box(p, os, FLAG_ITEM, outer,
3848 context, t.cs(), special);
3852 //\makebox() is part of the picture environment and different from \makebox{}
3853 //\makebox{} will be parsed by parse_box
3854 else if (t.cs() == "makebox") {
3855 if (p.next_token().character() == '(') {
3856 //the syntax is: \makebox(x,y)[position]{content}
3857 string arg = t.asInput();
3858 arg += p.getFullParentheseArg();
3859 arg += p.getFullOpt();
3860 eat_whitespace(p, os, context, false);
3861 handle_ert(os, arg + '{', context);
3862 eat_whitespace(p, os, context, false);
3863 parse_text(p, os, FLAG_ITEM, outer, context);
3864 handle_ert(os, "}", context);
3866 //the syntax is: \makebox[width][position]{content}
3867 parse_box(p, os, 0, FLAG_ITEM, outer, context,
3871 else if (t.cs() == "smallskip" ||
3872 t.cs() == "medskip" ||
3873 t.cs() == "bigskip" ||
3874 t.cs() == "vfill") {
3875 context.check_layout(os);
3876 begin_inset(os, "VSpace ");
3879 skip_spaces_braces(p);
3882 else if ((where = is_known(t.cs(), known_spaces))) {
3883 context.check_layout(os);
3884 begin_inset(os, "space ");
3885 os << '\\' << known_coded_spaces[where - known_spaces]
3888 // LaTeX swallows whitespace after all spaces except
3889 // "\\,". We have to do that here, too, because LyX
3890 // adds "{}" which would make the spaces significant.
3892 eat_whitespace(p, os, context, false);
3893 // LyX adds "{}" after all spaces except "\\ " and
3894 // "\\,", so we have to remove "{}".
3895 // "\\,{}" is equivalent to "\\," in LaTeX, so we
3896 // remove the braces after "\\,", too.
3901 else if (t.cs() == "newpage" ||
3902 (t.cs() == "pagebreak" && !p.hasOpt()) ||
3903 t.cs() == "clearpage" ||
3904 t.cs() == "cleardoublepage") {
3905 context.check_layout(os);
3906 begin_inset(os, "Newpage ");
3909 skip_spaces_braces(p);
3912 else if (t.cs() == "DeclareRobustCommand" ||
3913 t.cs() == "DeclareRobustCommandx" ||
3914 t.cs() == "newcommand" ||
3915 t.cs() == "newcommandx" ||
3916 t.cs() == "providecommand" ||
3917 t.cs() == "providecommandx" ||
3918 t.cs() == "renewcommand" ||
3919 t.cs() == "renewcommandx") {
3920 // DeclareRobustCommand, DeclareRobustCommandx,
3921 // providecommand and providecommandx could be handled
3922 // by parse_command(), but we need to call
3923 // add_known_command() here.
3924 string name = t.asInput();
3925 if (p.next_token().asInput() == "*") {
3926 // Starred form. Eat '*'
3930 string const command = p.verbatim_item();
3931 string const opt1 = p.getFullOpt();
3932 string const opt2 = p.getFullOpt();
3933 add_known_command(command, opt1, !opt2.empty());
3934 string const ert = name + '{' + command + '}' +
3936 '{' + p.verbatim_item() + '}';
3938 if (t.cs() == "DeclareRobustCommand" ||
3939 t.cs() == "DeclareRobustCommandx" ||
3940 t.cs() == "providecommand" ||
3941 t.cs() == "providecommandx" ||
3942 name[name.length()-1] == '*')
3943 handle_ert(os, ert, context);
3945 context.check_layout(os);
3946 begin_inset(os, "FormulaMacro");
3952 else if (t.cs() == "let" && p.next_token().asInput() != "*") {
3953 // let could be handled by parse_command(),
3954 // but we need to call add_known_command() here.
3955 string ert = t.asInput();
3958 if (p.next_token().cat() == catBegin) {
3959 name = p.verbatim_item();
3960 ert += '{' + name + '}';
3962 name = p.verbatim_item();
3967 if (p.next_token().cat() == catBegin) {
3968 command = p.verbatim_item();
3969 ert += '{' + command + '}';
3971 command = p.verbatim_item();
3974 // If command is known, make name known too, to parse
3975 // its arguments correctly. For this reason we also
3976 // have commands in syntax.default that are hardcoded.
3977 CommandMap::iterator it = known_commands.find(command);
3978 if (it != known_commands.end())
3979 known_commands[t.asInput()] = it->second;
3980 handle_ert(os, ert, context);
3983 else if (t.cs() == "hspace" || t.cs() == "vspace") {
3984 bool starred = false;
3985 if (p.next_token().asInput() == "*") {
3989 string name = t.asInput();
3990 string const length = p.verbatim_item();
3993 bool valid = splitLatexLength(length, valstring, unit);
3994 bool known_hspace = false;
3995 bool known_vspace = false;
3996 bool known_unit = false;
3999 istringstream iss(valstring);
4002 if (t.cs()[0] == 'h') {
4003 if (unit == "\\fill") {
4008 known_hspace = true;
4011 if (unit == "\\smallskipamount") {
4013 known_vspace = true;
4014 } else if (unit == "\\medskipamount") {
4016 known_vspace = true;
4017 } else if (unit == "\\bigskipamount") {
4019 known_vspace = true;
4020 } else if (unit == "\\fill") {
4022 known_vspace = true;
4026 if (!known_hspace && !known_vspace) {
4027 switch (unitFromString(unit)) {
4048 if (t.cs()[0] == 'h' && (known_unit || known_hspace)) {
4049 // Literal horizontal length or known variable
4050 context.check_layout(os);
4051 begin_inset(os, "space ");
4059 if (known_unit && !known_hspace)
4061 << translate_len(length);
4063 } else if (known_unit || known_vspace) {
4064 // Literal vertical length or known variable
4065 context.check_layout(os);
4066 begin_inset(os, "VSpace ");
4074 // LyX can't handle other length variables in Inset VSpace/space
4079 handle_ert(os, name + '{' + unit + '}', context);
4080 else if (value == -1.0)
4081 handle_ert(os, name + "{-" + unit + '}', context);
4083 handle_ert(os, name + '{' + valstring + unit + '}', context);
4085 handle_ert(os, name + '{' + length + '}', context);
4089 // The single '=' is meant here.
4090 else if ((newinsetlayout = findInsetLayout(context.textclass, t.cs(), true))) {
4092 context.check_layout(os);
4093 begin_inset(os, "Flex ");
4094 os << to_utf8(newinsetlayout->name()) << '\n'
4095 << "status collapsed\n";
4096 parse_text_in_inset(p, os, FLAG_ITEM, false, context, newinsetlayout);
4100 else if (t.cs() == "includepdf") {
4102 string const arg = p.getArg('[', ']');
4103 map<string, string> opts;
4104 vector<string> keys;
4105 split_map(arg, opts, keys);
4106 string name = normalize_filename(p.verbatim_item());
4107 string const path = getMasterFilePath();
4108 // We want to preserve relative / absolute filenames,
4109 // therefore path is only used for testing
4110 if (!makeAbsPath(name, path).exists()) {
4111 // The file extension is probably missing.
4112 // Now try to find it out.
4113 char const * const pdfpages_format[] = {"pdf", 0};
4114 string const pdftex_name =
4115 find_file(name, path, pdfpages_format);
4116 if (!pdftex_name.empty()) {
4121 if (makeAbsPath(name, path).exists())
4122 fix_relative_filename(name);
4124 cerr << "Warning: Could not find file '"
4125 << name << "'." << endl;
4127 context.check_layout(os);
4128 begin_inset(os, "External\n\ttemplate ");
4129 os << "PDFPages\n\tfilename "
4131 // parse the options
4132 if (opts.find("pages") != opts.end())
4133 os << "\textra LaTeX \"pages="
4134 << opts["pages"] << "\"\n";
4135 if (opts.find("angle") != opts.end())
4136 os << "\trotateAngle "
4137 << opts["angle"] << '\n';
4138 if (opts.find("origin") != opts.end()) {
4140 string const opt = opts["origin"];
4141 if (opt == "tl") ss << "topleft";
4142 if (opt == "bl") ss << "bottomleft";
4143 if (opt == "Bl") ss << "baselineleft";
4144 if (opt == "c") ss << "center";
4145 if (opt == "tc") ss << "topcenter";
4146 if (opt == "bc") ss << "bottomcenter";
4147 if (opt == "Bc") ss << "baselinecenter";
4148 if (opt == "tr") ss << "topright";
4149 if (opt == "br") ss << "bottomright";
4150 if (opt == "Br") ss << "baselineright";
4151 if (!ss.str().empty())
4152 os << "\trotateOrigin " << ss.str() << '\n';
4154 cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
4156 if (opts.find("width") != opts.end())
4158 << translate_len(opts["width"]) << '\n';
4159 if (opts.find("height") != opts.end())
4161 << translate_len(opts["height"]) << '\n';
4162 if (opts.find("keepaspectratio") != opts.end())
4163 os << "\tkeepAspectRatio\n";
4165 context.check_layout(os);
4166 registerExternalTemplatePackages("PDFPages");
4169 else if (t.cs() == "loadgame") {
4171 string name = normalize_filename(p.verbatim_item());
4172 string const path = getMasterFilePath();
4173 // We want to preserve relative / absolute filenames,
4174 // therefore path is only used for testing
4175 if (!makeAbsPath(name, path).exists()) {
4176 // The file extension is probably missing.
4177 // Now try to find it out.
4178 char const * const lyxskak_format[] = {"fen", 0};
4179 string const lyxskak_name =
4180 find_file(name, path, lyxskak_format);
4181 if (!lyxskak_name.empty())
4182 name = lyxskak_name;
4184 if (makeAbsPath(name, path).exists())
4185 fix_relative_filename(name);
4187 cerr << "Warning: Could not find file '"
4188 << name << "'." << endl;
4189 context.check_layout(os);
4190 begin_inset(os, "External\n\ttemplate ");
4191 os << "ChessDiagram\n\tfilename "
4194 context.check_layout(os);
4195 // after a \loadgame follows a \showboard
4196 if (p.get_token().asInput() == "showboard")
4198 registerExternalTemplatePackages("ChessDiagram");
4202 // try to see whether the string is in unicodesymbols
4203 // Only use text mode commands, since we are in text mode here,
4204 // and math commands may be invalid (bug 6797)
4208 docstring s = encodings.fromLaTeXCommand(from_utf8(t.asInput()),
4209 Encodings::TEXT_CMD, termination, rem, &req);
4212 cerr << "When parsing " << t.cs()
4213 << ", result is " << to_utf8(s)
4214 << "+" << to_utf8(rem) << endl;
4215 context.check_layout(os);
4218 skip_spaces_braces(p);
4219 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
4220 preamble.registerAutomaticallyLoadedPackage(*it);
4222 //cerr << "#: " << t << " mode: " << mode << endl;
4223 // heuristic: read up to next non-nested space
4225 string s = t.asInput();
4226 string z = p.verbatim_item();
4227 while (p.good() && z != " " && z.size()) {
4228 //cerr << "read: " << z << endl;
4230 z = p.verbatim_item();
4232 cerr << "found ERT: " << s << endl;
4233 handle_ert(os, s + ' ', context);
4236 string name = t.asInput();
4237 if (p.next_token().asInput() == "*") {
4238 // Starred commands like \vspace*{}
4239 p.get_token(); // Eat '*'
4242 if (!parse_command(name, p, os, outer, context))
4243 handle_ert(os, name, context);
4247 if (flags & FLAG_LEAVE) {
4248 flags &= ~FLAG_LEAVE;