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 (inluding synomyms)
123 const char * const polyglossia_languages[] = {
124 "albanian", "croatian", "hebrew", "norsk", "swedish", "amharic", "czech", "hindi",
125 "nynorsk", "syriac", "arabic", "danish", "icelandic", "occitan", "tamil",
126 "armenian", "divehi", "interlingua", "polish", "telugu", "asturian", "dutch",
127 "irish", "portuges", "thai", "bahasai", "english", "italian", "romanian", "turkish",
128 "bahasam", "esperanto", "lao", "russian", "turkmen", "basque", "estonian", "latin",
129 "samin", "ukrainian", "bengali", "farsi", "latvian", "sanskrit", "urdu", "brazil",
130 "brazilian", "finnish", "lithuanian", "scottish", "usorbian", "breton", "french",
131 "lsorbian", "serbian", "vietnamese", "bulgarian", "galician", "magyar", "slovak",
132 "welsh", "catalan", "german", "malayalam", "slovenian", "coptic", "greek",
133 "marathi", "spanish", 0};
136 * the same as polyglossia_languages with .lyx names
137 * please keep this in sync with polyglossia_languages line by line!
139 const char * const coded_polyglossia_languages[] = {
140 "albanian", "croatian", "hebrew", "norsk", "swedish", "amharic", "czech", "hindi",
141 "nynorsk", "syriac", "arabic_arabi", "danish", "icelandic", "occitan", "tamil",
142 "armenian", "divehi", "interlingua", "polish", "telugu", "asturian", "dutch",
143 "irish", "portuges", "thai", "bahasa", "english", "italian", "romanian", "turkish",
144 "bahasam", "esperanto", "lao", "russian", "turkmen", "basque", "estonian", "latin",
145 "samin", "ukrainian", "bengali", "farsi", "latvian", "sanskrit", "urdu", "brazilian",
146 "brazilian", "finnish", "lithuanian", "scottish", "uppersorbian", "breton", "french",
147 "lowersorbian", "serbian", "vietnamese", "bulgarian", "galician", "magyar", "slovak",
148 "welsh", "catalan", "ngerman", "malayalam", "slovene", "coptic", "greek",
149 "marathi", "spanish", 0};
151 string polyglossia2lyx(string const & language)
153 char const * const * where = is_known(language, polyglossia_languages);
155 return coded_polyglossia_languages[where - polyglossia_languages];
161 * The starred forms are also known except for "citefullauthor",
162 * "citeyear" and "citeyearpar".
164 char const * const known_natbib_commands[] = { "cite", "citet", "citep",
165 "citealt", "citealp", "citeauthor", "citeyear", "citeyearpar",
166 "citefullauthor", "Citet", "Citep", "Citealt", "Citealp", "Citeauthor", 0 };
170 * No starred form other than "cite*" known.
172 char const * const known_jurabib_commands[] = { "cite", "citet", "citep",
173 "citealt", "citealp", "citeauthor", "citeyear", "citeyearpar",
174 // jurabib commands not (yet) supported by LyX:
176 // "footcite", "footcitet", "footcitep", "footcitealt", "footcitealp",
177 // "footciteauthor", "footciteyear", "footciteyearpar",
178 "citefield", "citetitle", 0 };
180 /// LaTeX names for quotes
181 char const * const known_quotes[] = { "dq", "guillemotleft", "flqq", "og",
182 "guillemotright", "frqq", "fg", "glq", "glqq", "textquoteleft", "grq", "grqq",
183 "quotedblbase", "textquotedblleft", "quotesinglbase", "textquoteright", "flq",
184 "guilsinglleft", "frq", "guilsinglright", 0};
186 /// the same as known_quotes with .lyx names
187 char const * const known_coded_quotes[] = { "prd", "ard", "ard", "ard",
188 "ald", "ald", "ald", "gls", "gld", "els", "els", "grd",
189 "gld", "grd", "gls", "ers", "fls",
190 "fls", "frs", "frs", 0};
192 /// LaTeX names for font sizes
193 char const * const known_sizes[] = { "tiny", "scriptsize", "footnotesize",
194 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
196 /// the same as known_sizes with .lyx names
197 char const * const known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
198 "small", "normal", "large", "larger", "largest", "huge", "giant", 0};
200 /// LaTeX 2.09 names for font families
201 char const * const known_old_font_families[] = { "rm", "sf", "tt", 0};
203 /// LaTeX names for font families
204 char const * const known_font_families[] = { "rmfamily", "sffamily",
207 /// LaTeX names for font family changing commands
208 char const * const known_text_font_families[] = { "textrm", "textsf",
211 /// The same as known_old_font_families, known_font_families and
212 /// known_text_font_families with .lyx names
213 char const * const known_coded_font_families[] = { "roman", "sans",
216 /// LaTeX 2.09 names for font series
217 char const * const known_old_font_series[] = { "bf", 0};
219 /// LaTeX names for font series
220 char const * const known_font_series[] = { "bfseries", "mdseries", 0};
222 /// LaTeX names for font series changing commands
223 char const * const known_text_font_series[] = { "textbf", "textmd", 0};
225 /// The same as known_old_font_series, known_font_series and
226 /// known_text_font_series with .lyx names
227 char const * const known_coded_font_series[] = { "bold", "medium", 0};
229 /// LaTeX 2.09 names for font shapes
230 char const * const known_old_font_shapes[] = { "it", "sl", "sc", 0};
232 /// LaTeX names for font shapes
233 char const * const known_font_shapes[] = { "itshape", "slshape", "scshape",
236 /// LaTeX names for font shape changing commands
237 char const * const known_text_font_shapes[] = { "textit", "textsl", "textsc",
240 /// The same as known_old_font_shapes, known_font_shapes and
241 /// known_text_font_shapes with .lyx names
242 char const * const known_coded_font_shapes[] = { "italic", "slanted",
243 "smallcaps", "up", 0};
245 /// Known special characters which need skip_spaces_braces() afterwards
246 char const * const known_special_chars[] = {"ldots", "lyxarrow",
247 "textcompwordmark", "slash", 0};
249 /// the same as known_special_chars with .lyx names
250 char const * const known_coded_special_chars[] = {"ldots{}", "menuseparator",
251 "textcompwordmark{}", "slash{}", 0};
254 * Graphics file extensions known by the dvips driver of the graphics package.
255 * These extensions are used to complete the filename of an included
256 * graphics file if it does not contain an extension.
257 * The order must be the same that latex uses to find a file, because we
258 * will use the first extension that matches.
259 * This is only an approximation for the common cases. If we would want to
260 * do it right in all cases, we would need to know which graphics driver is
261 * used and know the extensions of every driver of the graphics package.
263 char const * const known_dvips_graphics_formats[] = {"eps", "ps", "eps.gz",
264 "ps.gz", "eps.Z", "ps.Z", 0};
267 * Graphics file extensions known by the pdftex driver of the graphics package.
268 * \sa known_dvips_graphics_formats
270 char const * const known_pdftex_graphics_formats[] = {"png", "pdf", "jpg",
274 * Known file extensions for TeX files as used by \\include.
276 char const * const known_tex_extensions[] = {"tex", 0};
278 /// spaces known by InsetSpace
279 char const * const known_spaces[] = { " ", "space", ",",
280 "thinspace", "quad", "qquad", "enspace", "enskip",
281 "negthinspace", "negmedspace", "negthickspace", "textvisiblespace",
282 "hfill", "dotfill", "hrulefill", "leftarrowfill", "rightarrowfill",
283 "upbracefill", "downbracefill", 0};
285 /// the same as known_spaces with .lyx names
286 char const * const known_coded_spaces[] = { "space{}", "space{}",
287 "thinspace{}", "thinspace{}", "quad{}", "qquad{}", "enspace{}", "enskip{}",
288 "negthinspace{}", "negmedspace{}", "negthickspace{}", "textvisiblespace{}",
289 "hfill{}", "dotfill{}", "hrulefill{}", "leftarrowfill{}", "rightarrowfill{}",
290 "upbracefill{}", "downbracefill{}", 0};
292 /// These are translated by LyX to commands like "\\LyX{}", so we have to put
293 /// them in ERT. "LaTeXe" must come before "LaTeX"!
294 char const * const known_phrases[] = {"LyX", "TeX", "LaTeXe", "LaTeX", 0};
295 char const * const known_coded_phrases[] = {"LyX", "TeX", "LaTeX2e", "LaTeX", 0};
296 int const known_phrase_lengths[] = {3, 5, 7, 0};
298 // string to store the float type to be able to determine the type of subfloats
299 string float_type = "";
302 /// splits "x=z, y=b" into a map and an ordered keyword vector
303 void split_map(string const & s, map<string, string> & res, vector<string> & keys)
308 keys.resize(v.size());
309 for (size_t i = 0; i < v.size(); ++i) {
310 size_t const pos = v[i].find('=');
311 string const index = trimSpaceAndEol(v[i].substr(0, pos));
312 string const value = trimSpaceAndEol(v[i].substr(pos + 1, string::npos));
320 * Split a LaTeX length into value and unit.
321 * The latter can be a real unit like "pt", or a latex length variable
322 * like "\textwidth". The unit may contain additional stuff like glue
323 * lengths, but we don't care, because such lengths are ERT anyway.
324 * \returns true if \p value and \p unit are valid.
326 bool splitLatexLength(string const & len, string & value, string & unit)
330 const string::size_type i = len.find_first_not_of(" -+0123456789.,");
331 //'4,5' is a valid LaTeX length number. Change it to '4.5'
332 string const length = subst(len, ',', '.');
333 if (i == string::npos)
336 if (len[0] == '\\') {
337 // We had something like \textwidth without a factor
343 value = trimSpaceAndEol(string(length, 0, i));
347 // 'cM' is a valid LaTeX length unit. Change it to 'cm'
348 if (contains(len, '\\'))
349 unit = trimSpaceAndEol(string(len, i));
351 unit = ascii_lowercase(trimSpaceAndEol(string(len, i)));
356 /// A simple function to translate a latex length to something LyX can
357 /// understand. Not perfect, but rather best-effort.
358 bool translate_len(string const & length, string & valstring, string & unit)
360 if (!splitLatexLength(length, valstring, unit))
362 // LyX uses percent values
364 istringstream iss(valstring);
369 string const percentval = oss.str();
371 if (unit.empty() || unit[0] != '\\')
373 string::size_type const i = unit.find(' ');
374 string const endlen = (i == string::npos) ? string() : string(unit, i);
375 if (unit == "\\textwidth") {
376 valstring = percentval;
377 unit = "text%" + endlen;
378 } else if (unit == "\\columnwidth") {
379 valstring = percentval;
380 unit = "col%" + endlen;
381 } else if (unit == "\\paperwidth") {
382 valstring = percentval;
383 unit = "page%" + endlen;
384 } else if (unit == "\\linewidth") {
385 valstring = percentval;
386 unit = "line%" + endlen;
387 } else if (unit == "\\paperheight") {
388 valstring = percentval;
389 unit = "pheight%" + endlen;
390 } else if (unit == "\\textheight") {
391 valstring = percentval;
392 unit = "theight%" + endlen;
400 string translate_len(string const & length)
404 if (translate_len(length, value, unit))
406 // If the input is invalid, return what we have.
414 * Translates a LaTeX length into \p value, \p unit and
415 * \p special parts suitable for a box inset.
416 * The difference from translate_len() is that a box inset knows about
417 * some special "units" that are stored in \p special.
419 void translate_box_len(string const & length, string & value, string & unit, string & special)
421 if (translate_len(length, value, unit)) {
422 if (unit == "\\height" || unit == "\\depth" ||
423 unit == "\\totalheight" || unit == "\\width") {
424 special = unit.substr(1);
425 // The unit is not used, but LyX requires a dummy setting
438 * Find a file with basename \p name in path \p path and an extension
441 string find_file(string const & name, string const & path,
442 char const * const * extensions)
444 for (char const * const * what = extensions; *what; ++what) {
445 string const trial = addExtension(name, *what);
446 if (makeAbsPath(trial, path).exists())
453 void begin_inset(ostream & os, string const & name)
455 os << "\n\\begin_inset " << name;
459 void begin_command_inset(ostream & os, string const & name,
460 string const & latexname)
462 begin_inset(os, "CommandInset ");
463 os << name << "\nLatexCommand " << latexname << '\n';
467 void end_inset(ostream & os)
469 os << "\n\\end_inset\n\n";
473 bool skip_braces(Parser & p)
475 if (p.next_token().cat() != catBegin)
478 if (p.next_token().cat() == catEnd) {
487 /// replace LaTeX commands in \p s from the unicodesymbols file with their
489 docstring convert_unicodesymbols(docstring s)
492 for (size_t i = 0; i < s.size();) {
501 docstring parsed = encodings.fromLaTeXCommand(s,
502 Encodings::TEXT_CMD, termination, rem, &req);
503 set<string>::const_iterator it = req.begin();
504 set<string>::const_iterator en = req.end();
505 for (; it != en; ++it)
506 preamble.registerAutomaticallyLoadedPackage(*it);
509 if (s.empty() || s[0] != '\\')
518 /// try to convert \p s to a valid InsetCommand argument
519 string convert_command_inset_arg(string s)
522 // since we don't know the input encoding we can't use from_utf8
523 s = to_utf8(convert_unicodesymbols(from_ascii(s)));
524 // LyX cannot handle newlines in a latex command
525 return subst(s, "\n", " ");
529 void handle_backslash(ostream & os, string const & s)
531 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
533 os << "\n\\backslash\n";
540 void handle_ert(ostream & os, string const & s, Context & context)
542 // We must have a valid layout before outputting the ERT inset.
543 context.check_layout(os);
544 Context newcontext(true, context.textclass);
545 begin_inset(os, "ERT");
546 os << "\nstatus collapsed\n";
547 newcontext.check_layout(os);
548 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
550 os << "\n\\backslash\n";
551 else if (*it == '\n') {
552 newcontext.new_paragraph(os);
553 newcontext.check_layout(os);
557 newcontext.check_end_layout(os);
562 void handle_comment(ostream & os, string const & s, Context & context)
564 // TODO: Handle this better
565 Context newcontext(true, context.textclass);
566 begin_inset(os, "ERT");
567 os << "\nstatus collapsed\n";
568 newcontext.check_layout(os);
569 handle_backslash(os, s);
570 // make sure that our comment is the last thing on the line
571 newcontext.new_paragraph(os);
572 newcontext.check_layout(os);
573 newcontext.check_end_layout(os);
578 Layout const * findLayout(TextClass const & textclass, string const & name, bool command)
580 Layout const * layout = findLayoutWithoutModule(textclass, name, command);
583 if (checkModule(name, command))
584 return findLayoutWithoutModule(textclass, name, command);
589 InsetLayout const * findInsetLayout(TextClass const & textclass, string const & name, bool command)
591 InsetLayout const * insetlayout = findInsetLayoutWithoutModule(textclass, name, command);
594 if (checkModule(name, command))
595 return findInsetLayoutWithoutModule(textclass, name, command);
600 void eat_whitespace(Parser &, ostream &, Context &, bool);
604 * Skips whitespace and braces.
605 * This should be called after a command has been parsed that is not put into
606 * ERT, and where LyX adds "{}" if needed.
608 void skip_spaces_braces(Parser & p, bool keepws = false)
610 /* The following four examples produce the same typeset output and
611 should be handled by this function:
619 // Unfortunately we need to skip comments, too.
620 // We can't use eat_whitespace since writing them after the {}
621 // results in different output in some cases.
622 bool const skipped_spaces = p.skip_spaces(true);
623 bool const skipped_braces = skip_braces(p);
624 if (keepws && skipped_spaces && !skipped_braces)
625 // put back the space (it is better handled by check_space)
626 p.unskip_spaces(true);
630 void output_command_layout(ostream & os, Parser & p, bool outer,
631 Context & parent_context,
632 Layout const * newlayout)
634 TeXFont const oldFont = parent_context.font;
635 // save the current font size
636 string const size = oldFont.size;
637 // reset the font size to default, because the font size switches
638 // don't affect section headings and the like
639 parent_context.font.size = Context::normalfont.size;
640 // we only need to write the font change if we have an open layout
641 if (!parent_context.atParagraphStart())
642 output_font_change(os, oldFont, parent_context.font);
643 parent_context.check_end_layout(os);
644 Context context(true, parent_context.textclass, newlayout,
645 parent_context.layout, parent_context.font);
646 if (parent_context.deeper_paragraph) {
647 // We are beginning a nested environment after a
648 // deeper paragraph inside the outer list environment.
649 // Therefore we don't need to output a "begin deeper".
650 context.need_end_deeper = true;
652 context.check_deeper(os);
653 context.check_layout(os);
654 unsigned int optargs = 0;
655 while (optargs < context.layout->optargs) {
656 eat_whitespace(p, os, context, false);
657 if (p.next_token().cat() == catEscape ||
658 p.next_token().character() != '[')
660 p.get_token(); // eat '['
661 begin_inset(os, "Argument\n");
662 os << "status collapsed\n\n";
663 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
665 eat_whitespace(p, os, context, false);
668 unsigned int reqargs = 0;
669 while (reqargs < context.layout->reqargs) {
670 eat_whitespace(p, os, context, false);
671 if (p.next_token().cat() != catBegin)
673 p.get_token(); // eat '{'
674 begin_inset(os, "Argument\n");
675 os << "status collapsed\n\n";
676 parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context);
678 eat_whitespace(p, os, context, false);
681 parse_text(p, os, FLAG_ITEM, outer, context);
682 context.check_end_layout(os);
683 if (parent_context.deeper_paragraph) {
684 // We must suppress the "end deeper" because we
685 // suppressed the "begin deeper" above.
686 context.need_end_deeper = false;
688 context.check_end_deeper(os);
689 // We don't need really a new paragraph, but
690 // we must make sure that the next item gets a \begin_layout.
691 parent_context.new_paragraph(os);
692 // Set the font size to the original value. No need to output it here
693 // (Context::begin_layout() will do that if needed)
694 parent_context.font.size = size;
699 * Output a space if necessary.
700 * This function gets called for every whitespace token.
702 * We have three cases here:
703 * 1. A space must be suppressed. Example: The lyxcode case below
704 * 2. A space may be suppressed. Example: Spaces before "\par"
705 * 3. A space must not be suppressed. Example: A space between two words
707 * We currently handle only 1. and 3 and from 2. only the case of
708 * spaces before newlines as a side effect.
710 * 2. could be used to suppress as many spaces as possible. This has two effects:
711 * - Reimporting LyX generated LaTeX files changes almost no whitespace
712 * - Superflous whitespace from non LyX generated LaTeX files is removed.
713 * The drawback is that the logic inside the function becomes
714 * complicated, and that is the reason why it is not implemented.
716 void check_space(Parser & p, ostream & os, Context & context)
718 Token const next = p.next_token();
719 Token const curr = p.curr_token();
720 // A space before a single newline and vice versa must be ignored
721 // LyX emits a newline before \end{lyxcode}.
722 // This newline must be ignored,
723 // otherwise LyX will add an additional protected space.
724 if (next.cat() == catSpace ||
725 next.cat() == catNewline ||
726 (next.cs() == "end" && context.layout->free_spacing && curr.cat() == catNewline)) {
729 context.check_layout(os);
735 * Parse all arguments of \p command
737 void parse_arguments(string const & command,
738 vector<ArgumentType> const & template_arguments,
739 Parser & p, ostream & os, bool outer, Context & context)
741 string ert = command;
742 size_t no_arguments = template_arguments.size();
743 for (size_t i = 0; i < no_arguments; ++i) {
744 switch (template_arguments[i]) {
747 // This argument contains regular LaTeX
748 handle_ert(os, ert + '{', context);
749 eat_whitespace(p, os, context, false);
750 if (template_arguments[i] == required)
751 parse_text(p, os, FLAG_ITEM, outer, context);
753 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
757 // This argument consists only of a single item.
758 // The presence of '{' or not must be preserved.
760 if (p.next_token().cat() == catBegin)
761 ert += '{' + p.verbatim_item() + '}';
763 ert += p.verbatim_item();
767 // This argument may contain special characters
768 ert += '{' + p.verbatim_item() + '}';
772 // true because we must not eat whitespace
773 // if an optional arg follows we must not strip the
774 // brackets from this one
775 if (i < no_arguments - 1 &&
776 template_arguments[i+1] == optional)
777 ert += p.getFullOpt(true);
779 ert += p.getOpt(true);
783 handle_ert(os, ert, context);
788 * Check whether \p command is a known command. If yes,
789 * handle the command with all arguments.
790 * \return true if the command was parsed, false otherwise.
792 bool parse_command(string const & command, Parser & p, ostream & os,
793 bool outer, Context & context)
795 if (known_commands.find(command) != known_commands.end()) {
796 parse_arguments(command, known_commands[command], p, os,
804 /// Parses a minipage or parbox
805 void parse_box(Parser & p, ostream & os, unsigned outer_flags,
806 unsigned inner_flags, bool outer, Context & parent_context,
807 string const & outer_type, string const & special,
808 string const & inner_type)
812 string hor_pos = "c";
813 // We need to set the height to the LaTeX default of 1\\totalheight
814 // for the case when no height argument is given
815 string height_value = "1";
816 string height_unit = "in";
817 string height_special = "totalheight";
822 string width_special = "none";
823 if (!inner_type.empty() && p.hasOpt()) {
824 if (inner_type != "makebox")
825 position = p.getArg('[', ']');
827 latex_width = p.getArg('[', ']');
828 translate_box_len(latex_width, width_value, width_unit, width_special);
831 if (position != "t" && position != "c" && position != "b") {
832 cerr << "invalid position " << position << " for "
833 << inner_type << endl;
837 if (inner_type != "makebox") {
838 latex_height = p.getArg('[', ']');
839 translate_box_len(latex_height, height_value, height_unit, height_special);
841 hor_pos = p.getArg('[', ']');
844 inner_pos = p.getArg('[', ']');
845 if (inner_pos != "c" && inner_pos != "t" &&
846 inner_pos != "b" && inner_pos != "s") {
847 cerr << "invalid inner_pos "
848 << inner_pos << " for "
849 << inner_type << endl;
850 inner_pos = position;
855 if (inner_type.empty()) {
856 if (special.empty() && outer_type != "framebox")
857 latex_width = "1\\columnwidth";
860 latex_width = p2.getArg('[', ']');
861 string const opt = p2.getArg('[', ']');
864 if (hor_pos != "l" && hor_pos != "c" &&
866 cerr << "invalid hor_pos " << hor_pos
867 << " for " << outer_type << endl;
872 } else if (inner_type != "makebox")
873 latex_width = p.verbatim_item();
874 // if e.g. only \ovalbox{content} was used, set the width to 1\columnwidth
875 // as this is LyX's standard for such cases (except for makebox)
876 // \framebox is more special and handled below
877 if (latex_width.empty() && inner_type != "makebox"
878 && outer_type != "framebox")
879 latex_width = "1\\columnwidth";
881 translate_len(latex_width, width_value, width_unit);
883 bool shadedparbox = false;
884 if (inner_type == "shaded") {
885 eat_whitespace(p, os, parent_context, false);
886 if (outer_type == "parbox") {
888 if (p.next_token().cat() == catBegin)
890 eat_whitespace(p, os, parent_context, false);
896 // If we already read the inner box we have to push the inner env
897 if (!outer_type.empty() && !inner_type.empty() &&
898 (inner_flags & FLAG_END))
899 active_environments.push_back(inner_type);
900 // LyX can't handle length variables
901 bool use_ert = contains(width_unit, '\\') || contains(height_unit, '\\');
902 if (!use_ert && !outer_type.empty() && !inner_type.empty()) {
903 // Look whether there is some content after the end of the
904 // inner box, but before the end of the outer box.
905 // If yes, we need to output ERT.
907 if (inner_flags & FLAG_END)
908 p.verbatimEnvironment(inner_type);
912 bool const outer_env(outer_type == "framed" || outer_type == "minipage");
913 if ((outer_env && p.next_token().asInput() != "\\end") ||
914 (!outer_env && p.next_token().cat() != catEnd)) {
915 // something is between the end of the inner box and
916 // the end of the outer box, so we need to use ERT.
921 // if only \makebox{content} was used we can set its width to 1\width
922 // because this identic and also identic to \mbox
923 // this doesn't work for \framebox{content}, thus we have to use ERT for this
924 if (latex_width.empty() && inner_type == "makebox") {
927 width_special = "width";
928 } else if (latex_width.empty() && outer_type == "framebox") {
933 if (!outer_type.empty()) {
934 if (outer_flags & FLAG_END)
935 ss << "\\begin{" << outer_type << '}';
937 ss << '\\' << outer_type << '{';
938 if (!special.empty())
942 if (!inner_type.empty()) {
943 if (inner_type != "shaded") {
944 if (inner_flags & FLAG_END)
945 ss << "\\begin{" << inner_type << '}';
947 ss << '\\' << inner_type;
949 if (!position.empty())
950 ss << '[' << position << ']';
951 if (!latex_height.empty())
952 ss << '[' << latex_height << ']';
953 if (!inner_pos.empty())
954 ss << '[' << inner_pos << ']';
955 ss << '{' << latex_width << '}';
956 if (!(inner_flags & FLAG_END))
959 if (inner_type == "shaded")
960 ss << "\\begin{shaded}";
961 handle_ert(os, ss.str(), parent_context);
962 if (!inner_type.empty()) {
963 parse_text(p, os, inner_flags, outer, parent_context);
964 if (inner_flags & FLAG_END)
965 handle_ert(os, "\\end{" + inner_type + '}',
968 handle_ert(os, "}", parent_context);
970 if (!outer_type.empty()) {
971 // If we already read the inner box we have to pop
973 if (!inner_type.empty() && (inner_flags & FLAG_END))
974 active_environments.pop_back();
976 // Ensure that the end of the outer box is parsed correctly:
977 // The opening brace has been eaten by parse_outer_box()
978 if (!outer_type.empty() && (outer_flags & FLAG_ITEM)) {
979 outer_flags &= ~FLAG_ITEM;
980 outer_flags |= FLAG_BRACE_LAST;
982 parse_text(p, os, outer_flags, outer, parent_context);
983 if (outer_flags & FLAG_END)
984 handle_ert(os, "\\end{" + outer_type + '}',
986 else if (inner_type.empty() && outer_type == "framebox")
987 // in this case it is already closed later
990 handle_ert(os, "}", parent_context);
993 // LyX does not like empty positions, so we have
994 // to set them to the LaTeX default values here.
995 if (position.empty())
997 if (inner_pos.empty())
998 inner_pos = position;
999 parent_context.check_layout(os);
1000 begin_inset(os, "Box ");
1001 if (outer_type == "framed")
1003 else if (outer_type == "framebox")
1005 else if (outer_type == "shadowbox")
1006 os << "Shadowbox\n";
1007 else if ((outer_type == "shaded" && inner_type.empty()) ||
1008 (outer_type == "minipage" && inner_type == "shaded") ||
1009 (outer_type == "parbox" && inner_type == "shaded")) {
1011 preamble.registerAutomaticallyLoadedPackage("color");
1012 } else if (outer_type == "doublebox")
1013 os << "Doublebox\n";
1014 else if (outer_type.empty())
1015 os << "Frameless\n";
1017 os << outer_type << '\n';
1018 os << "position \"" << position << "\"\n";
1019 os << "hor_pos \"" << hor_pos << "\"\n";
1020 os << "has_inner_box " << !inner_type.empty() << "\n";
1021 os << "inner_pos \"" << inner_pos << "\"\n";
1022 os << "use_parbox " << (inner_type == "parbox" || shadedparbox)
1024 os << "use_makebox " << (inner_type == "makebox") << '\n';
1025 os << "width \"" << width_value << width_unit << "\"\n";
1026 os << "special \"" << width_special << "\"\n";
1027 os << "height \"" << height_value << height_unit << "\"\n";
1028 os << "height_special \"" << height_special << "\"\n";
1029 os << "status open\n\n";
1031 // Unfortunately we can't use parse_text_in_inset:
1032 // InsetBox::forcePlainLayout() is hard coded and does not
1033 // use the inset layout. Apart from that do we call parse_text
1034 // up to two times, but need only one check_end_layout.
1035 bool const forcePlainLayout =
1036 (!inner_type.empty() || inner_type == "makebox") &&
1037 outer_type != "shaded" && outer_type != "framed";
1038 Context context(true, parent_context.textclass);
1039 if (forcePlainLayout)
1040 context.layout = &context.textclass.plainLayout();
1042 context.font = parent_context.font;
1044 // If we have no inner box the contents will be read with the outer box
1045 if (!inner_type.empty())
1046 parse_text(p, os, inner_flags, outer, context);
1048 // Ensure that the end of the outer box is parsed correctly:
1049 // The opening brace has been eaten by parse_outer_box()
1050 if (!outer_type.empty() && (outer_flags & FLAG_ITEM)) {
1051 outer_flags &= ~FLAG_ITEM;
1052 outer_flags |= FLAG_BRACE_LAST;
1055 // Find end of outer box, output contents if inner_type is
1056 // empty and output possible comments
1057 if (!outer_type.empty()) {
1058 // If we already read the inner box we have to pop
1060 if (!inner_type.empty() && (inner_flags & FLAG_END))
1061 active_environments.pop_back();
1062 // This does not output anything but comments if
1063 // inner_type is not empty (see use_ert)
1064 parse_text(p, os, outer_flags, outer, context);
1067 context.check_end_layout(os);
1069 #ifdef PRESERVE_LAYOUT
1070 // LyX puts a % after the end of the minipage
1071 if (p.next_token().cat() == catNewline && p.next_token().cs().size() > 1) {
1073 //handle_comment(os, "%dummy", parent_context);
1076 parent_context.new_paragraph(os);
1078 else if (p.next_token().cat() == catSpace || p.next_token().cat() == catNewline) {
1079 //handle_comment(os, "%dummy", parent_context);
1082 // We add a protected space if something real follows
1083 if (p.good() && p.next_token().cat() != catComment) {
1084 begin_inset(os, "space ~\n");
1093 void parse_outer_box(Parser & p, ostream & os, unsigned flags, bool outer,
1094 Context & parent_context, string const & outer_type,
1095 string const & special)
1097 eat_whitespace(p, os, parent_context, false);
1098 if (flags & FLAG_ITEM) {
1100 if (p.next_token().cat() == catBegin)
1103 cerr << "Warning: Ignoring missing '{' after \\"
1104 << outer_type << '.' << endl;
1105 eat_whitespace(p, os, parent_context, false);
1108 unsigned int inner_flags = 0;
1110 if (outer_type == "minipage" || outer_type == "parbox") {
1111 p.skip_spaces(true);
1112 while (p.hasOpt()) {
1114 p.skip_spaces(true);
1117 p.skip_spaces(true);
1118 if (outer_type == "parbox") {
1120 if (p.next_token().cat() == catBegin)
1122 p.skip_spaces(true);
1125 if (outer_type == "shaded") {
1126 // These boxes never have an inner box
1128 } else if (p.next_token().asInput() == "\\parbox") {
1129 inner = p.get_token().cs();
1130 inner_flags = FLAG_ITEM;
1131 } else if (p.next_token().asInput() == "\\begin") {
1132 // Is this a minipage or shaded box?
1135 inner = p.getArg('{', '}');
1137 if (inner == "minipage" || inner == "shaded")
1138 inner_flags = FLAG_END;
1143 if (inner_flags == FLAG_END) {
1144 if (inner != "shaded")
1148 eat_whitespace(p, os, parent_context, false);
1150 parse_box(p, os, flags, FLAG_END, outer, parent_context,
1151 outer_type, special, inner);
1153 if (inner_flags == FLAG_ITEM) {
1155 eat_whitespace(p, os, parent_context, false);
1157 parse_box(p, os, flags, inner_flags, outer, parent_context,
1158 outer_type, special, inner);
1163 void parse_listings(Parser & p, ostream & os, Context & parent_context, bool in_line)
1165 parent_context.check_layout(os);
1166 begin_inset(os, "listings\n");
1168 string arg = p.verbatimOption();
1169 os << "lstparams " << '"' << arg << '"' << '\n';
1172 os << "inline true\n";
1174 os << "inline false\n";
1175 os << "status collapsed\n";
1176 Context context(true, parent_context.textclass);
1177 context.layout = &parent_context.textclass.plainLayout();
1180 s = p.plainCommand('!', '!', "lstinline");
1181 context.new_paragraph(os);
1182 context.check_layout(os);
1184 s = p.plainEnvironment("lstlisting");
1185 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
1187 os << "\n\\backslash\n";
1188 else if (*it == '\n') {
1189 // avoid adding an empty paragraph at the end
1191 context.new_paragraph(os);
1192 context.check_layout(os);
1197 context.check_end_layout(os);
1202 /// parse an unknown environment
1203 void parse_unknown_environment(Parser & p, string const & name, ostream & os,
1204 unsigned flags, bool outer,
1205 Context & parent_context)
1207 if (name == "tabbing")
1208 // We need to remember that we have to handle '\=' specially
1209 flags |= FLAG_TABBING;
1211 // We need to translate font changes and paragraphs inside the
1212 // environment to ERT if we have a non standard font.
1213 // Otherwise things like
1214 // \large\begin{foo}\huge bar\end{foo}
1216 bool const specialfont =
1217 (parent_context.font != parent_context.normalfont);
1218 bool const new_layout_allowed = parent_context.new_layout_allowed;
1220 parent_context.new_layout_allowed = false;
1221 handle_ert(os, "\\begin{" + name + "}", parent_context);
1222 parse_text_snippet(p, os, flags, outer, parent_context);
1223 handle_ert(os, "\\end{" + name + "}", parent_context);
1225 parent_context.new_layout_allowed = new_layout_allowed;
1229 void parse_environment(Parser & p, ostream & os, bool outer,
1230 string & last_env, Context & parent_context)
1232 Layout const * newlayout;
1233 InsetLayout const * newinsetlayout = 0;
1234 string const name = p.getArg('{', '}');
1235 const bool is_starred = suffixIs(name, '*');
1236 string const unstarred_name = rtrim(name, "*");
1237 active_environments.push_back(name);
1239 if (is_math_env(name)) {
1240 parent_context.check_layout(os);
1241 begin_inset(os, "Formula ");
1242 os << "\\begin{" << name << "}";
1243 parse_math(p, os, FLAG_END, MATH_MODE);
1244 os << "\\end{" << name << "}";
1246 if (is_display_math_env(name)) {
1247 // Prevent the conversion of a line break to a space
1248 // (bug 7668). This does not change the output, but
1249 // looks ugly in LyX.
1250 eat_whitespace(p, os, parent_context, false);
1254 else if (is_known(name, polyglossia_languages)) {
1255 // We must begin a new paragraph if not already done
1256 if (! parent_context.atParagraphStart()) {
1257 parent_context.check_end_layout(os);
1258 parent_context.new_paragraph(os);
1260 // save the language for the case that a
1261 // \textLANGUAGE is used
1262 parent_context.font.language = polyglossia2lyx(name);
1263 os << "\n\\lang " << parent_context.font.language << "\n";
1264 parse_text(p, os, FLAG_END, outer, parent_context);
1265 // Just in case the environment is empty
1266 parent_context.extra_stuff.erase();
1267 // We must begin a new paragraph to reset the language
1268 parent_context.new_paragraph(os);
1272 else if (unstarred_name == "tabular" || name == "longtable") {
1273 eat_whitespace(p, os, parent_context, false);
1274 string width = "0pt";
1275 if (name == "tabular*") {
1276 width = lyx::translate_len(p.getArg('{', '}'));
1277 eat_whitespace(p, os, parent_context, false);
1279 parent_context.check_layout(os);
1280 begin_inset(os, "Tabular ");
1281 handle_tabular(p, os, name, width, parent_context);
1286 else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
1287 eat_whitespace(p, os, parent_context, false);
1288 string const opt = p.hasOpt() ? p.getArg('[', ']') : string();
1289 eat_whitespace(p, os, parent_context, false);
1290 parent_context.check_layout(os);
1291 begin_inset(os, "Float " + unstarred_name + "\n");
1292 // store the float type for subfloats
1293 // subfloats only work with figures and tables
1294 if (unstarred_name == "figure")
1295 float_type = unstarred_name;
1296 else if (unstarred_name == "table")
1297 float_type = unstarred_name;
1301 os << "placement " << opt << '\n';
1302 if (contains(opt, "H"))
1303 preamble.registerAutomaticallyLoadedPackage("float");
1305 Floating const & fl = parent_context.textclass.floats()
1306 .getType(unstarred_name);
1307 if (!fl.floattype().empty() && fl.usesFloatPkg())
1308 preamble.registerAutomaticallyLoadedPackage("float");
1311 os << "wide " << convert<string>(is_starred)
1312 << "\nsideways false"
1313 << "\nstatus open\n\n";
1314 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1316 // We don't need really a new paragraph, but
1317 // we must make sure that the next item gets a \begin_layout.
1318 parent_context.new_paragraph(os);
1320 // the float is parsed thus delete the type
1324 else if (unstarred_name == "sidewaysfigure"
1325 || unstarred_name == "sidewaystable") {
1326 eat_whitespace(p, os, parent_context, false);
1327 parent_context.check_layout(os);
1328 if (unstarred_name == "sidewaysfigure")
1329 begin_inset(os, "Float figure\n");
1331 begin_inset(os, "Float table\n");
1332 os << "wide " << convert<string>(is_starred)
1333 << "\nsideways true"
1334 << "\nstatus open\n\n";
1335 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1337 // We don't need really a new paragraph, but
1338 // we must make sure that the next item gets a \begin_layout.
1339 parent_context.new_paragraph(os);
1341 preamble.registerAutomaticallyLoadedPackage("rotfloat");
1344 else if (name == "wrapfigure" || name == "wraptable") {
1345 // syntax is \begin{wrapfigure}[lines]{placement}[overhang]{width}
1346 eat_whitespace(p, os, parent_context, false);
1347 parent_context.check_layout(os);
1350 string overhang = "0col%";
1353 lines = p.getArg('[', ']');
1354 string const placement = p.getArg('{', '}');
1356 overhang = p.getArg('[', ']');
1357 string const width = p.getArg('{', '}');
1359 if (name == "wrapfigure")
1360 begin_inset(os, "Wrap figure\n");
1362 begin_inset(os, "Wrap table\n");
1363 os << "lines " << lines
1364 << "\nplacement " << placement
1365 << "\noverhang " << lyx::translate_len(overhang)
1366 << "\nwidth " << lyx::translate_len(width)
1367 << "\nstatus open\n\n";
1368 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1370 // We don't need really a new paragraph, but
1371 // we must make sure that the next item gets a \begin_layout.
1372 parent_context.new_paragraph(os);
1374 preamble.registerAutomaticallyLoadedPackage("wrapfig");
1377 else if (name == "minipage") {
1378 eat_whitespace(p, os, parent_context, false);
1379 // Test whether this is an outer box of a shaded box
1381 // swallow arguments
1382 while (p.hasOpt()) {
1384 p.skip_spaces(true);
1387 p.skip_spaces(true);
1388 Token t = p.get_token();
1389 bool shaded = false;
1390 if (t.asInput() == "\\begin") {
1391 p.skip_spaces(true);
1392 if (p.getArg('{', '}') == "shaded")
1397 parse_outer_box(p, os, FLAG_END, outer,
1398 parent_context, name, "shaded");
1400 parse_box(p, os, 0, FLAG_END, outer, parent_context,
1405 else if (name == "comment") {
1406 eat_whitespace(p, os, parent_context, false);
1407 parent_context.check_layout(os);
1408 begin_inset(os, "Note Comment\n");
1409 os << "status open\n";
1410 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1413 skip_braces(p); // eat {} that might by set by LyX behind comments
1414 preamble.registerAutomaticallyLoadedPackage("verbatim");
1417 else if (name == "verbatim") {
1418 os << "\n\\end_layout\n\n\\begin_layout Verbatim\n";
1419 string const s = p.plainEnvironment("verbatim");
1420 string::const_iterator it2 = s.begin();
1421 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
1423 os << "\\backslash ";
1424 else if (*it == '\n') {
1426 // avoid adding an empty paragraph at the end
1427 // FIXME: if there are 2 consecutive spaces at the end ignore it
1428 // because LyX will re-add a \n
1429 // This hack must be removed once bug 8049 is fixed!
1430 if ((it + 1 != et) && (it + 2 != et || *it2 != '\n'))
1431 os << "\n\\end_layout\n\\begin_layout Verbatim\n";
1435 os << "\n\\end_layout\n\n";
1437 // reset to Standard layout
1438 os << "\n\\begin_layout Standard\n";
1441 else if (name == "lyxgreyedout") {
1442 eat_whitespace(p, os, parent_context, false);
1443 parent_context.check_layout(os);
1444 begin_inset(os, "Note Greyedout\n");
1445 os << "status open\n";
1446 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1449 if (!preamble.notefontcolor().empty())
1450 preamble.registerAutomaticallyLoadedPackage("color");
1453 else if (name == "framed" || name == "shaded") {
1454 eat_whitespace(p, os, parent_context, false);
1455 parse_outer_box(p, os, FLAG_END, outer, parent_context, name, "");
1459 else if (name == "lstlisting") {
1460 eat_whitespace(p, os, parent_context, false);
1461 // FIXME handle the automatic color package loading
1462 // uwestoehr asks: In what case color is loaded?
1463 parse_listings(p, os, parent_context, false);
1467 else if (!parent_context.new_layout_allowed)
1468 parse_unknown_environment(p, name, os, FLAG_END, outer,
1471 // Alignment and spacing settings
1472 // FIXME (bug xxxx): These settings can span multiple paragraphs and
1473 // therefore are totally broken!
1474 // Note that \centering, raggedright, and raggedleft cannot be handled, as
1475 // they are commands not environments. They are furthermore switches that
1476 // can be ended by another switches, but also by commands like \footnote or
1477 // \parbox. So the only safe way is to leave them untouched.
1478 else if (name == "center" || name == "centering" ||
1479 name == "flushleft" || name == "flushright" ||
1480 name == "singlespace" || name == "onehalfspace" ||
1481 name == "doublespace" || name == "spacing") {
1482 eat_whitespace(p, os, parent_context, false);
1483 // We must begin a new paragraph if not already done
1484 if (! parent_context.atParagraphStart()) {
1485 parent_context.check_end_layout(os);
1486 parent_context.new_paragraph(os);
1488 if (name == "flushleft")
1489 parent_context.add_extra_stuff("\\align left\n");
1490 else if (name == "flushright")
1491 parent_context.add_extra_stuff("\\align right\n");
1492 else if (name == "center" || name == "centering")
1493 parent_context.add_extra_stuff("\\align center\n");
1494 else if (name == "singlespace")
1495 parent_context.add_extra_stuff("\\paragraph_spacing single\n");
1496 else if (name == "onehalfspace") {
1497 parent_context.add_extra_stuff("\\paragraph_spacing onehalf\n");
1498 preamble.registerAutomaticallyLoadedPackage("setspace");
1499 } else if (name == "doublespace") {
1500 parent_context.add_extra_stuff("\\paragraph_spacing double\n");
1501 preamble.registerAutomaticallyLoadedPackage("setspace");
1502 } else if (name == "spacing") {
1503 parent_context.add_extra_stuff("\\paragraph_spacing other " + p.verbatim_item() + "\n");
1504 preamble.registerAutomaticallyLoadedPackage("setspace");
1506 parse_text(p, os, FLAG_END, outer, parent_context);
1507 // Just in case the environment is empty
1508 parent_context.extra_stuff.erase();
1509 // We must begin a new paragraph to reset the alignment
1510 parent_context.new_paragraph(os);
1514 // The single '=' is meant here.
1515 else if ((newlayout = findLayout(parent_context.textclass, name, false))) {
1516 eat_whitespace(p, os, parent_context, false);
1517 Context context(true, parent_context.textclass, newlayout,
1518 parent_context.layout, parent_context.font);
1519 if (parent_context.deeper_paragraph) {
1520 // We are beginning a nested environment after a
1521 // deeper paragraph inside the outer list environment.
1522 // Therefore we don't need to output a "begin deeper".
1523 context.need_end_deeper = true;
1525 parent_context.check_end_layout(os);
1526 if (last_env == name) {
1527 // we need to output a separator since LyX would export
1528 // the two environments as one otherwise (bug 5716)
1529 docstring const sep = from_ascii("--Separator--");
1530 TeX2LyXDocClass const & textclass(parent_context.textclass);
1531 if (textclass.hasLayout(sep)) {
1532 Context newcontext(parent_context);
1533 newcontext.layout = &(textclass[sep]);
1534 newcontext.check_layout(os);
1535 newcontext.check_end_layout(os);
1537 parent_context.check_layout(os);
1538 begin_inset(os, "Note Note\n");
1539 os << "status closed\n";
1540 Context newcontext(true, textclass,
1541 &(textclass.defaultLayout()));
1542 newcontext.check_layout(os);
1543 newcontext.check_end_layout(os);
1545 parent_context.check_end_layout(os);
1548 switch (context.layout->latextype) {
1549 case LATEX_LIST_ENVIRONMENT:
1550 context.add_par_extra_stuff("\\labelwidthstring "
1551 + p.verbatim_item() + '\n');
1554 case LATEX_BIB_ENVIRONMENT:
1555 p.verbatim_item(); // swallow next arg
1561 context.check_deeper(os);
1562 // handle known optional and required arguments
1563 // layouts require all optional arguments before the required ones
1564 // Unfortunately LyX can't handle arguments of list arguments (bug 7468):
1565 // It is impossible to place anything after the environment name,
1566 // but before the first \\item.
1567 if (context.layout->latextype == LATEX_ENVIRONMENT) {
1568 bool need_layout = true;
1569 unsigned int optargs = 0;
1570 while (optargs < context.layout->optargs) {
1571 eat_whitespace(p, os, context, false);
1572 if (p.next_token().cat() == catEscape ||
1573 p.next_token().character() != '[')
1575 p.get_token(); // eat '['
1577 context.check_layout(os);
1578 need_layout = false;
1580 begin_inset(os, "Argument\n");
1581 os << "status collapsed\n\n";
1582 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
1584 eat_whitespace(p, os, context, false);
1587 unsigned int reqargs = 0;
1588 while (reqargs < context.layout->reqargs) {
1589 eat_whitespace(p, os, context, false);
1590 if (p.next_token().cat() != catBegin)
1592 p.get_token(); // eat '{'
1594 context.check_layout(os);
1595 need_layout = false;
1597 begin_inset(os, "Argument\n");
1598 os << "status collapsed\n\n";
1599 parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context);
1601 eat_whitespace(p, os, context, false);
1605 parse_text(p, os, FLAG_END, outer, context);
1606 context.check_end_layout(os);
1607 if (parent_context.deeper_paragraph) {
1608 // We must suppress the "end deeper" because we
1609 // suppressed the "begin deeper" above.
1610 context.need_end_deeper = false;
1612 context.check_end_deeper(os);
1613 parent_context.new_paragraph(os);
1615 if (!preamble.titleLayoutFound())
1616 preamble.titleLayoutFound(newlayout->intitle);
1617 set<string> const & req = newlayout->requires();
1618 set<string>::const_iterator it = req.begin();
1619 set<string>::const_iterator en = req.end();
1620 for (; it != en; ++it)
1621 preamble.registerAutomaticallyLoadedPackage(*it);
1624 // The single '=' is meant here.
1625 else if ((newinsetlayout = findInsetLayout(parent_context.textclass, name, false))) {
1626 eat_whitespace(p, os, parent_context, false);
1627 parent_context.check_layout(os);
1628 begin_inset(os, "Flex ");
1629 os << to_utf8(newinsetlayout->name()) << '\n'
1630 << "status collapsed\n";
1631 parse_text_in_inset(p, os, FLAG_END, false, parent_context, newinsetlayout);
1635 else if (name == "appendix") {
1636 // This is no good latex style, but it works and is used in some documents...
1637 eat_whitespace(p, os, parent_context, false);
1638 parent_context.check_end_layout(os);
1639 Context context(true, parent_context.textclass, parent_context.layout,
1640 parent_context.layout, parent_context.font);
1641 context.check_layout(os);
1642 os << "\\start_of_appendix\n";
1643 parse_text(p, os, FLAG_END, outer, context);
1644 context.check_end_layout(os);
1648 else if (known_environments.find(name) != known_environments.end()) {
1649 vector<ArgumentType> arguments = known_environments[name];
1650 // The last "argument" denotes wether we may translate the
1651 // environment contents to LyX
1652 // The default required if no argument is given makes us
1653 // compatible with the reLyXre environment.
1654 ArgumentType contents = arguments.empty() ?
1657 if (!arguments.empty())
1658 arguments.pop_back();
1659 // See comment in parse_unknown_environment()
1660 bool const specialfont =
1661 (parent_context.font != parent_context.normalfont);
1662 bool const new_layout_allowed =
1663 parent_context.new_layout_allowed;
1665 parent_context.new_layout_allowed = false;
1666 parse_arguments("\\begin{" + name + "}", arguments, p, os,
1667 outer, parent_context);
1668 if (contents == verbatim)
1669 handle_ert(os, p.verbatimEnvironment(name),
1672 parse_text_snippet(p, os, FLAG_END, outer,
1674 handle_ert(os, "\\end{" + name + "}", parent_context);
1676 parent_context.new_layout_allowed = new_layout_allowed;
1680 parse_unknown_environment(p, name, os, FLAG_END, outer,
1684 active_environments.pop_back();
1688 /// parses a comment and outputs it to \p os.
1689 void parse_comment(Parser & p, ostream & os, Token const & t, Context & context)
1691 LASSERT(t.cat() == catComment, return);
1692 if (!t.cs().empty()) {
1693 context.check_layout(os);
1694 handle_comment(os, '%' + t.cs(), context);
1695 if (p.next_token().cat() == catNewline) {
1696 // A newline after a comment line starts a new
1698 if (context.new_layout_allowed) {
1699 if(!context.atParagraphStart())
1700 // Only start a new paragraph if not already
1701 // done (we might get called recursively)
1702 context.new_paragraph(os);
1704 handle_ert(os, "\n", context);
1705 eat_whitespace(p, os, context, true);
1708 // "%\n" combination
1715 * Reads spaces and comments until the first non-space, non-comment token.
1716 * New paragraphs (double newlines or \\par) are handled like simple spaces
1717 * if \p eatParagraph is true.
1718 * Spaces are skipped, but comments are written to \p os.
1720 void eat_whitespace(Parser & p, ostream & os, Context & context,
1724 Token const & t = p.get_token();
1725 if (t.cat() == catComment)
1726 parse_comment(p, os, t, context);
1727 else if ((! eatParagraph && p.isParagraph()) ||
1728 (t.cat() != catSpace && t.cat() != catNewline)) {
1737 * Set a font attribute, parse text and reset the font attribute.
1738 * \param attribute Attribute name (e.g. \\family, \\shape etc.)
1739 * \param currentvalue Current value of the attribute. Is set to the new
1740 * value during parsing.
1741 * \param newvalue New value of the attribute
1743 void parse_text_attributes(Parser & p, ostream & os, unsigned flags, bool outer,
1744 Context & context, string const & attribute,
1745 string & currentvalue, string const & newvalue)
1747 context.check_layout(os);
1748 string const oldvalue = currentvalue;
1749 currentvalue = newvalue;
1750 os << '\n' << attribute << ' ' << newvalue << "\n";
1751 parse_text_snippet(p, os, flags, outer, context);
1752 context.check_layout(os);
1753 os << '\n' << attribute << ' ' << oldvalue << "\n";
1754 currentvalue = oldvalue;
1758 /// get the arguments of a natbib or jurabib citation command
1759 void get_cite_arguments(Parser & p, bool natbibOrder,
1760 string & before, string & after)
1762 // We need to distinguish "" and "[]", so we can't use p.getOpt().
1764 // text before the citation
1766 // text after the citation
1767 after = p.getFullOpt();
1769 if (!after.empty()) {
1770 before = p.getFullOpt();
1771 if (natbibOrder && !before.empty())
1772 swap(before, after);
1777 /// Convert filenames with TeX macros and/or quotes to something LyX
1779 string const normalize_filename(string const & name)
1781 Parser p(trim(name, "\""));
1784 Token const & t = p.get_token();
1785 if (t.cat() != catEscape)
1787 else if (t.cs() == "lyxdot") {
1788 // This is used by LyX for simple dots in relative
1792 } else if (t.cs() == "space") {
1802 /// Convert \p name from TeX convention (relative to master file) to LyX
1803 /// convention (relative to .lyx file) if it is relative
1804 void fix_relative_filename(string & name)
1806 if (FileName::isAbsolute(name))
1809 name = to_utf8(makeRelPath(from_utf8(makeAbsPath(name, getMasterFilePath()).absFileName()),
1810 from_utf8(getParentFilePath())));
1814 /// Parse a NoWeb Scrap section. The initial "<<" is already parsed.
1815 void parse_noweb(Parser & p, ostream & os, Context & context)
1817 // assemble the rest of the keyword
1821 Token const & t = p.get_token();
1822 if (t.asInput() == ">" && p.next_token().asInput() == ">") {
1825 scrap = (p.good() && p.next_token().asInput() == "=");
1827 name += p.get_token().asInput();
1830 name += t.asInput();
1833 if (!scrap || !context.new_layout_allowed ||
1834 !context.textclass.hasLayout(from_ascii("Scrap"))) {
1835 cerr << "Warning: Could not interpret '" << name
1836 << "'. Ignoring it." << endl;
1840 // We use new_paragraph instead of check_end_layout because the stuff
1841 // following the noweb chunk needs to start with a \begin_layout.
1842 // This may create a new paragraph even if there was none in the
1843 // noweb file, but the alternative is an invalid LyX file. Since
1844 // noweb code chunks are implemented with a layout style in LyX they
1845 // always must be in an own paragraph.
1846 context.new_paragraph(os);
1847 Context newcontext(true, context.textclass,
1848 &context.textclass[from_ascii("Scrap")]);
1849 newcontext.check_layout(os);
1852 Token const & t = p.get_token();
1853 // We abuse the parser a bit, because this is no TeX syntax
1855 if (t.cat() == catEscape)
1856 os << subst(t.asInput(), "\\", "\n\\backslash\n");
1859 Context tmp(false, context.textclass,
1860 &context.textclass[from_ascii("Scrap")]);
1861 tmp.need_end_layout = true;
1862 tmp.check_layout(oss);
1863 os << subst(t.asInput(), "\n", oss.str());
1865 // The scrap chunk is ended by an @ at the beginning of a line.
1866 // After the @ the line may contain a comment and/or
1867 // whitespace, but nothing else.
1868 if (t.asInput() == "@" && p.prev_token().cat() == catNewline &&
1869 (p.next_token().cat() == catSpace ||
1870 p.next_token().cat() == catNewline ||
1871 p.next_token().cat() == catComment)) {
1872 while (p.good() && p.next_token().cat() == catSpace)
1873 os << p.get_token().asInput();
1874 if (p.next_token().cat() == catComment)
1875 // The comment includes a final '\n'
1876 os << p.get_token().asInput();
1878 if (p.next_token().cat() == catNewline)
1885 newcontext.check_end_layout(os);
1889 /// detects \\def, \\long\\def and \\global\\long\\def with ws and comments
1890 bool is_macro(Parser & p)
1892 Token first = p.curr_token();
1893 if (first.cat() != catEscape || !p.good())
1895 if (first.cs() == "def")
1897 if (first.cs() != "global" && first.cs() != "long")
1899 Token second = p.get_token();
1901 while (p.good() && !p.isParagraph() && (second.cat() == catSpace ||
1902 second.cat() == catNewline || second.cat() == catComment)) {
1903 second = p.get_token();
1906 bool secondvalid = second.cat() == catEscape;
1908 bool thirdvalid = false;
1909 if (p.good() && first.cs() == "global" && secondvalid &&
1910 second.cs() == "long") {
1911 third = p.get_token();
1913 while (p.good() && !p.isParagraph() &&
1914 (third.cat() == catSpace ||
1915 third.cat() == catNewline ||
1916 third.cat() == catComment)) {
1917 third = p.get_token();
1920 thirdvalid = third.cat() == catEscape;
1922 for (int i = 0; i < pos; ++i)
1927 return (first.cs() == "global" || first.cs() == "long") &&
1928 second.cs() == "def";
1929 return first.cs() == "global" && second.cs() == "long" &&
1930 third.cs() == "def";
1934 /// Parse a macro definition (assumes that is_macro() returned true)
1935 void parse_macro(Parser & p, ostream & os, Context & context)
1937 context.check_layout(os);
1938 Token first = p.curr_token();
1941 string command = first.asInput();
1942 if (first.cs() != "def") {
1944 eat_whitespace(p, os, context, false);
1945 second = p.curr_token();
1946 command += second.asInput();
1947 if (second.cs() != "def") {
1949 eat_whitespace(p, os, context, false);
1950 third = p.curr_token();
1951 command += third.asInput();
1954 eat_whitespace(p, os, context, false);
1955 string const name = p.get_token().cs();
1956 eat_whitespace(p, os, context, false);
1962 while (p.next_token().cat() != catBegin) {
1963 if (p.next_token().cat() == catParameter) {
1968 // followed by number?
1969 if (p.next_token().cat() == catOther) {
1970 char c = p.getChar();
1972 // number = current arity + 1?
1973 if (c == arity + '0' + 1)
1978 paramtext += p.get_token().cs();
1980 paramtext += p.get_token().cs();
1985 // only output simple (i.e. compatible) macro as FormulaMacros
1986 string ert = '\\' + name + ' ' + paramtext + '{' + p.verbatim_item() + '}';
1988 context.check_layout(os);
1989 begin_inset(os, "FormulaMacro");
1990 os << "\n\\def" << ert;
1993 handle_ert(os, command + ert, context);
1997 void registerExternalTemplatePackages(string const & name)
1999 external::TemplateManager const & etm = external::TemplateManager::get();
2000 external::Template const * const et = etm.getTemplateByName(name);
2003 external::Template::Formats::const_iterator cit = et->formats.end();
2005 cit = et->formats.find("PDFLaTeX");
2006 if (cit == et->formats.end())
2007 // If the template has not specified a PDFLaTeX output,
2008 // we try the LaTeX format.
2009 cit = et->formats.find("LaTeX");
2010 if (cit == et->formats.end())
2012 vector<string>::const_iterator qit = cit->second.requirements.begin();
2013 vector<string>::const_iterator qend = cit->second.requirements.end();
2014 for (; qit != qend; ++qit)
2015 preamble.registerAutomaticallyLoadedPackage(*qit);
2018 } // anonymous namespace
2021 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
2024 Layout const * newlayout = 0;
2025 InsetLayout const * newinsetlayout = 0;
2026 char const * const * where = 0;
2027 // Store the latest bibliographystyle and nocite{*} option
2028 // (needed for bibtex inset)
2030 string bibliographystyle = "default";
2031 bool const use_natbib = preamble.isPackageUsed("natbib");
2032 bool const use_jurabib = preamble.isPackageUsed("jurabib");
2035 Token const & t = p.get_token();
2038 debugToken(cerr, t, flags);
2041 if (flags & FLAG_ITEM) {
2042 if (t.cat() == catSpace)
2045 flags &= ~FLAG_ITEM;
2046 if (t.cat() == catBegin) {
2047 // skip the brace and collect everything to the next matching
2049 flags |= FLAG_BRACE_LAST;
2053 // handle only this single token, leave the loop if done
2054 flags |= FLAG_LEAVE;
2057 if (t.cat() != catEscape && t.character() == ']' &&
2058 (flags & FLAG_BRACK_LAST))
2060 if (t.cat() == catEnd && (flags & FLAG_BRACE_LAST))
2063 // If there is anything between \end{env} and \begin{env} we
2064 // don't need to output a separator.
2065 if (t.cat() != catSpace && t.cat() != catNewline &&
2066 t.asInput() != "\\begin")
2072 if (t.cat() == catMath) {
2073 // we are inside some text mode thingy, so opening new math is allowed
2074 context.check_layout(os);
2075 begin_inset(os, "Formula ");
2076 Token const & n = p.get_token();
2077 bool const display(n.cat() == catMath && outer);
2079 // TeX's $$...$$ syntax for displayed math
2081 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
2083 p.get_token(); // skip the second '$' token
2085 // simple $...$ stuff
2088 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
2093 // Prevent the conversion of a line break to a
2094 // space (bug 7668). This does not change the
2095 // output, but looks ugly in LyX.
2096 eat_whitespace(p, os, context, false);
2100 else if (t.cat() == catSuper || t.cat() == catSub)
2101 cerr << "catcode " << t << " illegal in text mode\n";
2103 // Basic support for english quotes. This should be
2104 // extended to other quotes, but is not so easy (a
2105 // left english quote is the same as a right german
2107 else if (t.asInput() == "`" && p.next_token().asInput() == "`") {
2108 context.check_layout(os);
2109 begin_inset(os, "Quotes ");
2115 else if (t.asInput() == "'" && p.next_token().asInput() == "'") {
2116 context.check_layout(os);
2117 begin_inset(os, "Quotes ");
2124 else if (t.asInput() == ">" && p.next_token().asInput() == ">") {
2125 context.check_layout(os);
2126 begin_inset(os, "Quotes ");
2133 else if (t.asInput() == "<" && p.next_token().asInput() == "<") {
2134 context.check_layout(os);
2135 begin_inset(os, "Quotes ");
2142 else if (t.asInput() == "<"
2143 && p.next_token().asInput() == "<" && noweb_mode) {
2145 parse_noweb(p, os, context);
2148 else if (t.cat() == catSpace || (t.cat() == catNewline && ! p.isParagraph()))
2149 check_space(p, os, context);
2151 else if (t.character() == '[' && noweb_mode &&
2152 p.next_token().character() == '[') {
2153 // These can contain underscores
2155 string const s = p.getFullOpt() + ']';
2156 if (p.next_token().character() == ']')
2159 cerr << "Warning: Inserting missing ']' in '"
2160 << s << "'." << endl;
2161 handle_ert(os, s, context);
2164 else if (t.cat() == catLetter) {
2165 context.check_layout(os);
2166 // Workaround for bug 4752.
2167 // FIXME: This whole code block needs to be removed
2168 // when the bug is fixed and tex2lyx produces
2169 // the updated file format.
2170 // The replacement algorithm in LyX is so stupid that
2171 // it even translates a phrase if it is part of a word.
2172 bool handled = false;
2173 for (int const * l = known_phrase_lengths; *l; ++l) {
2174 string phrase = t.cs();
2175 for (int i = 1; i < *l && p.next_token().isAlnumASCII(); ++i)
2176 phrase += p.get_token().cs();
2177 if (is_known(phrase, known_coded_phrases)) {
2178 handle_ert(os, phrase, context);
2182 for (size_t i = 1; i < phrase.length(); ++i)
2190 else if (t.cat() == catOther ||
2191 t.cat() == catAlign ||
2192 t.cat() == catParameter) {
2193 // This translates "&" to "\\&" which may be wrong...
2194 context.check_layout(os);
2198 else if (p.isParagraph()) {
2199 if (context.new_layout_allowed)
2200 context.new_paragraph(os);
2202 handle_ert(os, "\\par ", context);
2203 eat_whitespace(p, os, context, true);
2206 else if (t.cat() == catActive) {
2207 context.check_layout(os);
2208 if (t.character() == '~') {
2209 if (context.layout->free_spacing)
2212 begin_inset(os, "space ~\n");
2219 else if (t.cat() == catBegin) {
2220 Token const next = p.next_token();
2221 Token const end = p.next_next_token();
2222 if (next.cat() == catEnd) {
2224 Token const prev = p.prev_token();
2226 if (p.next_token().character() == '`' ||
2227 (prev.character() == '-' &&
2228 p.next_token().character() == '-'))
2229 ; // ignore it in {}`` or -{}-
2231 handle_ert(os, "{}", context);
2232 } else if (next.cat() == catEscape &&
2233 is_known(next.cs(), known_quotes) &&
2234 end.cat() == catEnd) {
2235 // Something like {\textquoteright} (e.g.
2236 // from writer2latex). LyX writes
2237 // \textquoteright{}, so we may skip the
2238 // braces here for better readability.
2239 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2242 context.check_layout(os);
2243 // special handling of font attribute changes
2244 Token const prev = p.prev_token();
2245 TeXFont const oldFont = context.font;
2246 if (next.character() == '[' ||
2247 next.character() == ']' ||
2248 next.character() == '*') {
2250 if (p.next_token().cat() == catEnd) {
2255 handle_ert(os, "{", context);
2256 parse_text_snippet(p, os,
2259 handle_ert(os, "}", context);
2261 } else if (! context.new_layout_allowed) {
2262 handle_ert(os, "{", context);
2263 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2265 handle_ert(os, "}", context);
2266 } else if (is_known(next.cs(), known_sizes)) {
2267 // next will change the size, so we must
2269 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2271 if (!context.atParagraphStart())
2273 << context.font.size << "\n";
2274 } else if (is_known(next.cs(), known_font_families)) {
2275 // next will change the font family, so we
2276 // must reset it here
2277 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2279 if (!context.atParagraphStart())
2281 << context.font.family << "\n";
2282 } else if (is_known(next.cs(), known_font_series)) {
2283 // next will change the font series, so we
2284 // must reset it here
2285 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2287 if (!context.atParagraphStart())
2289 << context.font.series << "\n";
2290 } else if (is_known(next.cs(), known_font_shapes)) {
2291 // next will change the font shape, so we
2292 // must reset it here
2293 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2295 if (!context.atParagraphStart())
2297 << context.font.shape << "\n";
2298 } else if (is_known(next.cs(), known_old_font_families) ||
2299 is_known(next.cs(), known_old_font_series) ||
2300 is_known(next.cs(), known_old_font_shapes)) {
2301 // next will change the font family, series
2302 // and shape, so we must reset it here
2303 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2305 if (!context.atParagraphStart())
2307 << context.font.family
2309 << context.font.series
2311 << context.font.shape << "\n";
2313 handle_ert(os, "{", context);
2314 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2316 handle_ert(os, "}", context);
2321 else if (t.cat() == catEnd) {
2322 if (flags & FLAG_BRACE_LAST) {
2325 cerr << "stray '}' in text\n";
2326 handle_ert(os, "}", context);
2329 else if (t.cat() == catComment)
2330 parse_comment(p, os, t, context);
2333 // control sequences
2336 else if (t.cs() == "(") {
2337 context.check_layout(os);
2338 begin_inset(os, "Formula");
2340 parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
2345 else if (t.cs() == "[") {
2346 context.check_layout(os);
2347 begin_inset(os, "Formula");
2349 parse_math(p, os, FLAG_EQUATION, MATH_MODE);
2352 // Prevent the conversion of a line break to a space
2353 // (bug 7668). This does not change the output, but
2354 // looks ugly in LyX.
2355 eat_whitespace(p, os, context, false);
2358 else if (t.cs() == "begin")
2359 parse_environment(p, os, outer, last_env,
2362 else if (t.cs() == "end") {
2363 if (flags & FLAG_END) {
2364 // eat environment name
2365 string const name = p.getArg('{', '}');
2366 if (name != active_environment())
2367 cerr << "\\end{" + name + "} does not match \\begin{"
2368 + active_environment() + "}\n";
2371 p.error("found 'end' unexpectedly");
2374 else if (t.cs() == "item") {
2376 bool const optarg = p.hasOpt();
2378 // FIXME: This swallows comments, but we cannot use
2379 // eat_whitespace() since we must not output
2380 // anything before the item.
2381 p.skip_spaces(true);
2382 s = p.verbatimOption();
2384 p.skip_spaces(false);
2386 context.check_layout(os);
2387 if (context.has_item) {
2388 // An item in an unknown list-like environment
2389 // FIXME: Do this in check_layout()!
2390 context.has_item = false;
2392 handle_ert(os, "\\item", context);
2394 handle_ert(os, "\\item ", context);
2397 if (context.layout->labeltype != LABEL_MANUAL) {
2398 // LyX does not support \item[\mybullet]
2399 // in itemize environments
2401 os << parse_text_snippet(p2,
2402 FLAG_BRACK_LAST, outer, context);
2403 } else if (!s.empty()) {
2404 // LyX adds braces around the argument,
2405 // so we need to remove them here.
2406 if (s.size() > 2 && s[0] == '{' &&
2407 s[s.size()-1] == '}')
2408 s = s.substr(1, s.size()-2);
2409 // If the argument contains a space we
2410 // must put it into ERT: Otherwise LyX
2411 // would misinterpret the space as
2412 // item delimiter (bug 7663)
2413 if (contains(s, ' ')) {
2414 handle_ert(os, s, context);
2417 os << parse_text_snippet(p2,
2421 // The space is needed to separate the
2422 // item from the rest of the sentence.
2424 eat_whitespace(p, os, context, false);
2429 else if (t.cs() == "bibitem") {
2431 context.check_layout(os);
2432 eat_whitespace(p, os, context, false);
2433 string label = convert_command_inset_arg(p.verbatimOption());
2434 string key = convert_command_inset_arg(p.verbatim_item());
2435 if (contains(label, '\\') || contains(key, '\\')) {
2436 // LyX can't handle LaTeX commands in labels or keys
2437 handle_ert(os, t.asInput() + '[' + label +
2438 "]{" + p.verbatim_item() + '}',
2441 begin_command_inset(os, "bibitem", "bibitem");
2442 os << "label \"" << label << "\"\n"
2443 "key \"" << key << "\"\n";
2448 else if (is_macro(p)) {
2449 // catch the case of \def\inputGnumericTable
2451 if (t.cs() == "def") {
2452 Token second = p.next_token();
2453 if (second.cs() == "inputGnumericTable") {
2457 Token third = p.get_token();
2459 if (third.cs() == "input") {
2463 string name = normalize_filename(p.verbatim_item());
2464 string const path = getMasterFilePath();
2465 // We want to preserve relative / absolute filenames,
2466 // therefore path is only used for testing
2467 // The file extension is in every case ".tex".
2468 // So we need to remove this extension and check for
2469 // the original one.
2470 name = removeExtension(name);
2471 if (!makeAbsPath(name, path).exists()) {
2472 char const * const Gnumeric_formats[] = {"gnumeric",
2474 string const Gnumeric_name =
2475 find_file(name, path, Gnumeric_formats);
2476 if (!Gnumeric_name.empty())
2477 name = Gnumeric_name;
2479 if (makeAbsPath(name, path).exists())
2480 fix_relative_filename(name);
2482 cerr << "Warning: Could not find file '"
2483 << name << "'." << endl;
2484 context.check_layout(os);
2485 begin_inset(os, "External\n\ttemplate ");
2486 os << "GnumericSpreadsheet\n\tfilename "
2489 context.check_layout(os);
2491 // register the packages that are automatically reloaded
2492 // by the Gnumeric template
2493 registerExternalTemplatePackages("GnumericSpreadsheet");
2498 parse_macro(p, os, context);
2501 else if (t.cs() == "noindent") {
2503 context.add_par_extra_stuff("\\noindent\n");
2506 else if (t.cs() == "appendix") {
2507 context.add_par_extra_stuff("\\start_of_appendix\n");
2508 // We need to start a new paragraph. Otherwise the
2509 // appendix in 'bla\appendix\chapter{' would start
2511 context.new_paragraph(os);
2512 // We need to make sure that the paragraph is
2513 // generated even if it is empty. Otherwise the
2514 // appendix in '\par\appendix\par\chapter{' would
2516 context.check_layout(os);
2517 // FIXME: This is a hack to prevent paragraph
2518 // deletion if it is empty. Handle this better!
2520 "%dummy comment inserted by tex2lyx to "
2521 "ensure that this paragraph is not empty",
2523 // Both measures above may generate an additional
2524 // empty paragraph, but that does not hurt, because
2525 // whitespace does not matter here.
2526 eat_whitespace(p, os, context, true);
2529 // Must catch empty dates before findLayout is called below
2530 else if (t.cs() == "date") {
2531 eat_whitespace(p, os, context, false);
2533 string const date = p.verbatim_item();
2536 preamble.suppressDate(true);
2539 preamble.suppressDate(false);
2540 if (context.new_layout_allowed &&
2541 (newlayout = findLayout(context.textclass,
2544 output_command_layout(os, p, outer,
2545 context, newlayout);
2546 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2547 if (!preamble.titleLayoutFound())
2548 preamble.titleLayoutFound(newlayout->intitle);
2549 set<string> const & req = newlayout->requires();
2550 set<string>::const_iterator it = req.begin();
2551 set<string>::const_iterator en = req.end();
2552 for (; it != en; ++it)
2553 preamble.registerAutomaticallyLoadedPackage(*it);
2556 "\\date{" + p.verbatim_item() + '}',
2561 // Starred section headings
2562 // Must attempt to parse "Section*" before "Section".
2563 else if ((p.next_token().asInput() == "*") &&
2564 context.new_layout_allowed &&
2565 (newlayout = findLayout(context.textclass, t.cs() + '*', true))) {
2568 output_command_layout(os, p, outer, context, newlayout);
2570 if (!preamble.titleLayoutFound())
2571 preamble.titleLayoutFound(newlayout->intitle);
2572 set<string> const & req = newlayout->requires();
2573 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
2574 preamble.registerAutomaticallyLoadedPackage(*it);
2577 // Section headings and the like
2578 else if (context.new_layout_allowed &&
2579 (newlayout = findLayout(context.textclass, t.cs(), true))) {
2581 output_command_layout(os, p, outer, context, newlayout);
2583 if (!preamble.titleLayoutFound())
2584 preamble.titleLayoutFound(newlayout->intitle);
2585 set<string> const & req = newlayout->requires();
2586 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
2587 preamble.registerAutomaticallyLoadedPackage(*it);
2590 else if (t.cs() == "caption") {
2592 context.check_layout(os);
2594 begin_inset(os, "Caption\n");
2595 Context newcontext(true, context.textclass);
2596 newcontext.font = context.font;
2597 newcontext.check_layout(os);
2598 if (p.next_token().cat() != catEscape &&
2599 p.next_token().character() == '[') {
2600 p.get_token(); // eat '['
2601 begin_inset(os, "Argument\n");
2602 os << "status collapsed\n";
2603 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
2605 eat_whitespace(p, os, context, false);
2607 parse_text(p, os, FLAG_ITEM, outer, context);
2608 context.check_end_layout(os);
2609 // We don't need really a new paragraph, but
2610 // we must make sure that the next item gets a \begin_layout.
2611 context.new_paragraph(os);
2614 newcontext.check_end_layout(os);
2617 else if (t.cs() == "subfloat") {
2618 // the syntax is \subfloat[caption]{content}
2619 // if it is a table of figure depends on the surrounding float
2620 bool has_caption = false;
2622 // do nothing if there is no outer float
2623 if (!float_type.empty()) {
2624 context.check_layout(os);
2626 begin_inset(os, "Float " + float_type + "\n");
2628 << "\nsideways false"
2629 << "\nstatus collapsed\n\n";
2632 if (p.next_token().cat() != catEscape &&
2633 p.next_token().character() == '[') {
2634 p.get_token(); // eat '['
2635 caption = parse_text_snippet(p, FLAG_BRACK_LAST, outer, context);
2639 parse_text_in_inset(p, os, FLAG_ITEM, outer, context);
2640 // the caption comes always as the last
2642 // we must make sure that the caption gets a \begin_layout
2643 os << "\n\\begin_layout Plain Layout";
2645 begin_inset(os, "Caption\n");
2646 Context newcontext(true, context.textclass);
2647 newcontext.font = context.font;
2648 newcontext.check_layout(os);
2649 os << caption << "\n";
2650 newcontext.check_end_layout(os);
2651 // We don't need really a new paragraph, but
2652 // we must make sure that the next item gets a \begin_layout.
2653 //newcontext.new_paragraph(os);
2657 // We don't need really a new paragraph, but
2658 // we must make sure that the next item gets a \begin_layout.
2660 context.new_paragraph(os);
2663 context.check_end_layout(os);
2664 // close the layout we opened
2666 os << "\n\\end_layout\n";
2668 // if the float type is not supported or there is no surrounding float
2671 string opt_arg = convert_command_inset_arg(p.getArg('[', ']'));
2672 handle_ert(os, t.asInput() + '[' + opt_arg +
2673 "]{" + p.verbatim_item() + '}', context);
2675 handle_ert(os, t.asInput() + "{" + p.verbatim_item() + '}', context);
2679 else if (t.cs() == "includegraphics") {
2680 bool const clip = p.next_token().asInput() == "*";
2683 string const arg = p.getArg('[', ']');
2684 map<string, string> opts;
2685 vector<string> keys;
2686 split_map(arg, opts, keys);
2688 opts["clip"] = string();
2689 string name = normalize_filename(p.verbatim_item());
2691 string const path = getMasterFilePath();
2692 // We want to preserve relative / absolute filenames,
2693 // therefore path is only used for testing
2694 if (!makeAbsPath(name, path).exists()) {
2695 // The file extension is probably missing.
2696 // Now try to find it out.
2697 string const dvips_name =
2698 find_file(name, path,
2699 known_dvips_graphics_formats);
2700 string const pdftex_name =
2701 find_file(name, path,
2702 known_pdftex_graphics_formats);
2703 if (!dvips_name.empty()) {
2704 if (!pdftex_name.empty()) {
2705 cerr << "This file contains the "
2707 "\"\\includegraphics{"
2709 "However, files\n\""
2710 << dvips_name << "\" and\n\""
2711 << pdftex_name << "\"\n"
2712 "both exist, so I had to make a "
2713 "choice and took the first one.\n"
2714 "Please move the unwanted one "
2715 "someplace else and try again\n"
2716 "if my choice was wrong."
2720 } else if (!pdftex_name.empty()) {
2726 if (makeAbsPath(name, path).exists())
2727 fix_relative_filename(name);
2729 cerr << "Warning: Could not find graphics file '"
2730 << name << "'." << endl;
2732 context.check_layout(os);
2733 begin_inset(os, "Graphics ");
2734 os << "\n\tfilename " << name << '\n';
2735 if (opts.find("width") != opts.end())
2737 << translate_len(opts["width"]) << '\n';
2738 if (opts.find("height") != opts.end())
2740 << translate_len(opts["height"]) << '\n';
2741 if (opts.find("scale") != opts.end()) {
2742 istringstream iss(opts["scale"]);
2746 os << "\tscale " << val << '\n';
2748 if (opts.find("angle") != opts.end()) {
2749 os << "\trotateAngle "
2750 << opts["angle"] << '\n';
2751 vector<string>::const_iterator a =
2752 find(keys.begin(), keys.end(), "angle");
2753 vector<string>::const_iterator s =
2754 find(keys.begin(), keys.end(), "width");
2755 if (s == keys.end())
2756 s = find(keys.begin(), keys.end(), "height");
2757 if (s == keys.end())
2758 s = find(keys.begin(), keys.end(), "scale");
2759 if (s != keys.end() && distance(s, a) > 0)
2760 os << "\tscaleBeforeRotation\n";
2762 if (opts.find("origin") != opts.end()) {
2764 string const opt = opts["origin"];
2765 if (opt.find('l') != string::npos) ss << "left";
2766 if (opt.find('r') != string::npos) ss << "right";
2767 if (opt.find('c') != string::npos) ss << "center";
2768 if (opt.find('t') != string::npos) ss << "Top";
2769 if (opt.find('b') != string::npos) ss << "Bottom";
2770 if (opt.find('B') != string::npos) ss << "Baseline";
2771 if (!ss.str().empty())
2772 os << "\trotateOrigin " << ss.str() << '\n';
2774 cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
2776 if (opts.find("keepaspectratio") != opts.end())
2777 os << "\tkeepAspectRatio\n";
2778 if (opts.find("clip") != opts.end())
2780 if (opts.find("draft") != opts.end())
2782 if (opts.find("bb") != opts.end())
2783 os << "\tBoundingBox "
2784 << opts["bb"] << '\n';
2785 int numberOfbbOptions = 0;
2786 if (opts.find("bbllx") != opts.end())
2787 numberOfbbOptions++;
2788 if (opts.find("bblly") != opts.end())
2789 numberOfbbOptions++;
2790 if (opts.find("bburx") != opts.end())
2791 numberOfbbOptions++;
2792 if (opts.find("bbury") != opts.end())
2793 numberOfbbOptions++;
2794 if (numberOfbbOptions == 4)
2795 os << "\tBoundingBox "
2796 << opts["bbllx"] << " " << opts["bblly"] << " "
2797 << opts["bburx"] << " " << opts["bbury"] << '\n';
2798 else if (numberOfbbOptions > 0)
2799 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
2800 numberOfbbOptions = 0;
2801 if (opts.find("natwidth") != opts.end())
2802 numberOfbbOptions++;
2803 if (opts.find("natheight") != opts.end())
2804 numberOfbbOptions++;
2805 if (numberOfbbOptions == 2)
2806 os << "\tBoundingBox 0bp 0bp "
2807 << opts["natwidth"] << " " << opts["natheight"] << '\n';
2808 else if (numberOfbbOptions > 0)
2809 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
2810 ostringstream special;
2811 if (opts.find("hiresbb") != opts.end())
2812 special << "hiresbb,";
2813 if (opts.find("trim") != opts.end())
2815 if (opts.find("viewport") != opts.end())
2816 special << "viewport=" << opts["viewport"] << ',';
2817 if (opts.find("totalheight") != opts.end())
2818 special << "totalheight=" << opts["totalheight"] << ',';
2819 if (opts.find("type") != opts.end())
2820 special << "type=" << opts["type"] << ',';
2821 if (opts.find("ext") != opts.end())
2822 special << "ext=" << opts["ext"] << ',';
2823 if (opts.find("read") != opts.end())
2824 special << "read=" << opts["read"] << ',';
2825 if (opts.find("command") != opts.end())
2826 special << "command=" << opts["command"] << ',';
2827 string s_special = special.str();
2828 if (!s_special.empty()) {
2829 // We had special arguments. Remove the trailing ','.
2830 os << "\tspecial " << s_special.substr(0, s_special.size() - 1) << '\n';
2832 // TODO: Handle the unknown settings better.
2833 // Warn about invalid options.
2834 // Check whether some option was given twice.
2836 preamble.registerAutomaticallyLoadedPackage("graphicx");
2839 else if (t.cs() == "footnote" ||
2840 (t.cs() == "thanks" && context.layout->intitle)) {
2842 context.check_layout(os);
2843 begin_inset(os, "Foot\n");
2844 os << "status collapsed\n\n";
2845 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
2849 else if (t.cs() == "marginpar") {
2851 context.check_layout(os);
2852 begin_inset(os, "Marginal\n");
2853 os << "status collapsed\n\n";
2854 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
2858 else if (t.cs() == "lstinline") {
2860 parse_listings(p, os, context, true);
2863 else if (t.cs() == "ensuremath") {
2865 context.check_layout(os);
2866 string const s = p.verbatim_item();
2867 //FIXME: this never triggers in UTF8
2868 if (s == "\xb1" || s == "\xb3" || s == "\xb2" || s == "\xb5")
2871 handle_ert(os, "\\ensuremath{" + s + "}",
2875 else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
2876 if (preamble.titleLayoutFound()) {
2878 skip_spaces_braces(p);
2880 handle_ert(os, t.asInput(), context);
2883 else if (t.cs() == "tableofcontents" || t.cs() == "lstlistoflistings") {
2884 context.check_layout(os);
2885 begin_command_inset(os, "toc", t.cs());
2887 skip_spaces_braces(p);
2888 if (t.cs() == "lstlistoflistings")
2889 preamble.registerAutomaticallyLoadedPackage("listings");
2892 else if (t.cs() == "listoffigures") {
2893 context.check_layout(os);
2894 begin_inset(os, "FloatList figure\n");
2896 skip_spaces_braces(p);
2899 else if (t.cs() == "listoftables") {
2900 context.check_layout(os);
2901 begin_inset(os, "FloatList table\n");
2903 skip_spaces_braces(p);
2906 else if (t.cs() == "listof") {
2907 p.skip_spaces(true);
2908 string const name = p.get_token().cs();
2909 if (context.textclass.floats().typeExist(name)) {
2910 context.check_layout(os);
2911 begin_inset(os, "FloatList ");
2914 p.get_token(); // swallow second arg
2916 handle_ert(os, "\\listof{" + name + "}", context);
2919 else if ((where = is_known(t.cs(), known_text_font_families)))
2920 parse_text_attributes(p, os, FLAG_ITEM, outer,
2921 context, "\\family", context.font.family,
2922 known_coded_font_families[where - known_text_font_families]);
2924 else if ((where = is_known(t.cs(), known_text_font_series)))
2925 parse_text_attributes(p, os, FLAG_ITEM, outer,
2926 context, "\\series", context.font.series,
2927 known_coded_font_series[where - known_text_font_series]);
2929 else if ((where = is_known(t.cs(), known_text_font_shapes)))
2930 parse_text_attributes(p, os, FLAG_ITEM, outer,
2931 context, "\\shape", context.font.shape,
2932 known_coded_font_shapes[where - known_text_font_shapes]);
2934 else if (t.cs() == "textnormal" || t.cs() == "normalfont") {
2935 context.check_layout(os);
2936 TeXFont oldFont = context.font;
2937 context.font.init();
2938 context.font.size = oldFont.size;
2939 os << "\n\\family " << context.font.family << "\n";
2940 os << "\n\\series " << context.font.series << "\n";
2941 os << "\n\\shape " << context.font.shape << "\n";
2942 if (t.cs() == "textnormal") {
2943 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2944 output_font_change(os, context.font, oldFont);
2945 context.font = oldFont;
2947 eat_whitespace(p, os, context, false);
2950 else if (t.cs() == "textcolor") {
2951 // scheme is \textcolor{color name}{text}
2952 string const color = p.verbatim_item();
2953 // we only support the predefined colors of the color package
2954 if (color == "black" || color == "blue" || color == "cyan"
2955 || color == "green" || color == "magenta" || color == "red"
2956 || color == "white" || color == "yellow") {
2957 context.check_layout(os);
2958 os << "\n\\color " << color << "\n";
2959 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2960 context.check_layout(os);
2961 os << "\n\\color inherit\n";
2962 preamble.registerAutomaticallyLoadedPackage("color");
2964 // for custom defined colors
2965 handle_ert(os, t.asInput() + "{" + color + "}", context);
2968 else if (t.cs() == "underbar" || t.cs() == "uline") {
2969 // \underbar is not 100% correct (LyX outputs \uline
2970 // of ulem.sty). The difference is that \ulem allows
2971 // line breaks, and \underbar does not.
2972 // Do NOT handle \underline.
2973 // \underbar cuts through y, g, q, p etc.,
2974 // \underline does not.
2975 context.check_layout(os);
2976 os << "\n\\bar under\n";
2977 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2978 context.check_layout(os);
2979 os << "\n\\bar default\n";
2980 preamble.registerAutomaticallyLoadedPackage("ulem");
2983 else if (t.cs() == "sout") {
2984 context.check_layout(os);
2985 os << "\n\\strikeout on\n";
2986 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2987 context.check_layout(os);
2988 os << "\n\\strikeout default\n";
2989 preamble.registerAutomaticallyLoadedPackage("ulem");
2992 else if (t.cs() == "uuline" || t.cs() == "uwave" ||
2993 t.cs() == "emph" || t.cs() == "noun") {
2994 context.check_layout(os);
2995 os << "\n\\" << t.cs() << " on\n";
2996 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2997 context.check_layout(os);
2998 os << "\n\\" << t.cs() << " default\n";
2999 if (t.cs() == "uuline" || t.cs() == "uwave")
3000 preamble.registerAutomaticallyLoadedPackage("ulem");
3003 else if (t.cs() == "lyxadded" || t.cs() == "lyxdeleted") {
3004 context.check_layout(os);
3005 string name = p.getArg('{', '}');
3006 string localtime = p.getArg('{', '}');
3007 preamble.registerAuthor(name);
3008 Author const & author = preamble.getAuthor(name);
3009 // from_ctime() will fail if LyX decides to output the
3010 // time in the text language. It might also use a wrong
3011 // time zone (if the original LyX document was exported
3012 // with a different time zone).
3013 time_t ptime = from_ctime(localtime);
3014 if (ptime == static_cast<time_t>(-1)) {
3015 cerr << "Warning: Could not parse time `" << localtime
3016 << "´ for change tracking, using current time instead.\n";
3017 ptime = current_time();
3019 if (t.cs() == "lyxadded")
3020 os << "\n\\change_inserted ";
3022 os << "\n\\change_deleted ";
3023 os << author.bufferId() << ' ' << ptime << '\n';
3024 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
3025 bool dvipost = LaTeXPackages::isAvailable("dvipost");
3026 bool xcolorulem = LaTeXPackages::isAvailable("ulem") &&
3027 LaTeXPackages::isAvailable("xcolor");
3028 // No need to test for luatex, since luatex comes in
3029 // two flavours (dvi and pdf), like latex, and those
3030 // are detected by pdflatex.
3031 if (pdflatex || xetex) {
3033 preamble.registerAutomaticallyLoadedPackage("ulem");
3034 preamble.registerAutomaticallyLoadedPackage("xcolor");
3035 preamble.registerAutomaticallyLoadedPackage("pdfcolmk");
3039 preamble.registerAutomaticallyLoadedPackage("dvipost");
3040 } else if (xcolorulem) {
3041 preamble.registerAutomaticallyLoadedPackage("ulem");
3042 preamble.registerAutomaticallyLoadedPackage("xcolor");
3047 else if (t.cs() == "phantom" || t.cs() == "hphantom" ||
3048 t.cs() == "vphantom") {
3049 context.check_layout(os);
3050 if (t.cs() == "phantom")
3051 begin_inset(os, "Phantom Phantom\n");
3052 if (t.cs() == "hphantom")
3053 begin_inset(os, "Phantom HPhantom\n");
3054 if (t.cs() == "vphantom")
3055 begin_inset(os, "Phantom VPhantom\n");
3056 os << "status open\n";
3057 parse_text_in_inset(p, os, FLAG_ITEM, outer, context,
3062 else if (t.cs() == "href") {
3063 context.check_layout(os);
3064 string target = p.getArg('{', '}');
3065 string name = p.getArg('{', '}');
3067 size_t i = target.find(':');
3068 if (i != string::npos) {
3069 type = target.substr(0, i + 1);
3070 if (type == "mailto:" || type == "file:")
3071 target = target.substr(i + 1);
3072 // handle the case that name is equal to target, except of "http://"
3073 else if (target.substr(i + 3) == name && type == "http:")
3076 begin_command_inset(os, "href", "href");
3078 os << "name \"" << name << "\"\n";
3079 os << "target \"" << target << "\"\n";
3080 if (type == "mailto:" || type == "file:")
3081 os << "type \"" << type << "\"\n";
3083 skip_spaces_braces(p);
3086 else if (t.cs() == "lyxline") {
3087 // swallow size argument (it is not used anyway)
3089 if (!context.atParagraphStart()) {
3090 // so our line is in the middle of a paragraph
3091 // we need to add a new line, lest this line
3092 // follow the other content on that line and
3093 // run off the side of the page
3094 // FIXME: This may create an empty paragraph,
3095 // but without that it would not be
3096 // possible to set noindent below.
3097 // Fortunately LaTeX does not care
3098 // about the empty paragraph.
3099 context.new_paragraph(os);
3101 if (preamble.indentParagraphs()) {
3102 // we need to unindent, lest the line be too long
3103 context.add_par_extra_stuff("\\noindent\n");
3105 context.check_layout(os);
3106 begin_command_inset(os, "line", "rule");
3107 os << "offset \"0.5ex\"\n"
3108 "width \"100line%\"\n"
3113 else if (t.cs() == "rule") {
3114 string const offset = (p.hasOpt() ? p.getArg('[', ']') : string());
3115 string const width = p.getArg('{', '}');
3116 string const thickness = p.getArg('{', '}');
3117 context.check_layout(os);
3118 begin_command_inset(os, "line", "rule");
3119 if (!offset.empty())
3120 os << "offset \"" << translate_len(offset) << "\"\n";
3121 os << "width \"" << translate_len(width) << "\"\n"
3122 "height \"" << translate_len(thickness) << "\"\n";
3126 else if (is_known(t.cs(), known_phrases) ||
3127 (t.cs() == "protect" &&
3128 p.next_token().cat() == catEscape &&
3129 is_known(p.next_token().cs(), known_phrases))) {
3130 // LyX sometimes puts a \protect in front, so we have to ignore it
3131 // FIXME: This needs to be changed when bug 4752 is fixed.
3133 t.cs() == "protect" ? p.get_token().cs() : t.cs(),
3135 context.check_layout(os);
3136 os << known_coded_phrases[where - known_phrases];
3137 skip_spaces_braces(p);
3140 else if ((where = is_known(t.cs(), known_ref_commands))) {
3141 string const opt = p.getOpt();
3143 context.check_layout(os);
3144 begin_command_inset(os, "ref",
3145 known_coded_ref_commands[where - known_ref_commands]);
3146 os << "reference \""
3147 << convert_command_inset_arg(p.verbatim_item())
3150 if (t.cs() == "vref" || t.cs() == "vpageref")
3151 preamble.registerAutomaticallyLoadedPackage("varioref");
3154 // LyX does not support optional arguments of ref commands
3155 handle_ert(os, t.asInput() + '[' + opt + "]{" +
3156 p.verbatim_item() + "}", context);
3160 else if (use_natbib &&
3161 is_known(t.cs(), known_natbib_commands) &&
3162 ((t.cs() != "citefullauthor" &&
3163 t.cs() != "citeyear" &&
3164 t.cs() != "citeyearpar") ||
3165 p.next_token().asInput() != "*")) {
3166 context.check_layout(os);
3167 string command = t.cs();
3168 if (p.next_token().asInput() == "*") {
3172 if (command == "citefullauthor")
3173 // alternative name for "\\citeauthor*"
3174 command = "citeauthor*";
3176 // text before the citation
3178 // text after the citation
3180 get_cite_arguments(p, true, before, after);
3182 if (command == "cite") {
3183 // \cite without optional argument means
3184 // \citet, \cite with at least one optional
3185 // argument means \citep.
3186 if (before.empty() && after.empty())
3191 if (before.empty() && after == "[]")
3192 // avoid \citet[]{a}
3194 else if (before == "[]" && after == "[]") {
3195 // avoid \citet[][]{a}
3199 // remove the brackets around after and before
3200 if (!after.empty()) {
3202 after.erase(after.length() - 1, 1);
3203 after = convert_command_inset_arg(after);
3205 if (!before.empty()) {
3207 before.erase(before.length() - 1, 1);
3208 before = convert_command_inset_arg(before);
3210 begin_command_inset(os, "citation", command);
3211 os << "after " << '"' << after << '"' << "\n";
3212 os << "before " << '"' << before << '"' << "\n";
3214 << convert_command_inset_arg(p.verbatim_item())
3219 else if (use_jurabib &&
3220 is_known(t.cs(), known_jurabib_commands) &&
3221 (t.cs() == "cite" || p.next_token().asInput() != "*")) {
3222 context.check_layout(os);
3223 string command = t.cs();
3224 if (p.next_token().asInput() == "*") {
3228 char argumentOrder = '\0';
3229 vector<string> const options =
3230 preamble.getPackageOptions("jurabib");
3231 if (find(options.begin(), options.end(),
3232 "natbiborder") != options.end())
3233 argumentOrder = 'n';
3234 else if (find(options.begin(), options.end(),
3235 "jurabiborder") != options.end())
3236 argumentOrder = 'j';
3238 // text before the citation
3240 // text after the citation
3242 get_cite_arguments(p, argumentOrder != 'j', before, after);
3244 string const citation = p.verbatim_item();
3245 if (!before.empty() && argumentOrder == '\0') {
3246 cerr << "Warning: Assuming argument order "
3247 "of jurabib version 0.6 for\n'"
3248 << command << before << after << '{'
3249 << citation << "}'.\n"
3250 "Add 'jurabiborder' to the jurabib "
3251 "package options if you used an\n"
3252 "earlier jurabib version." << endl;
3254 if (!after.empty()) {
3256 after.erase(after.length() - 1, 1);
3258 if (!before.empty()) {
3260 before.erase(before.length() - 1, 1);
3262 begin_command_inset(os, "citation", command);
3263 os << "after " << '"' << after << '"' << "\n";
3264 os << "before " << '"' << before << '"' << "\n";
3265 os << "key " << '"' << citation << '"' << "\n";
3269 else if (t.cs() == "cite"
3270 || t.cs() == "nocite") {
3271 context.check_layout(os);
3272 string after = convert_command_inset_arg(p.getArg('[', ']'));
3273 string key = convert_command_inset_arg(p.verbatim_item());
3274 // store the case that it is "\nocite{*}" to use it later for
3277 begin_command_inset(os, "citation", t.cs());
3278 os << "after " << '"' << after << '"' << "\n";
3279 os << "key " << '"' << key << '"' << "\n";
3281 } else if (t.cs() == "nocite")
3285 else if (t.cs() == "index" ||
3286 (t.cs() == "sindex" && preamble.use_indices() == "true")) {
3287 context.check_layout(os);
3288 string const arg = (t.cs() == "sindex" && p.hasOpt()) ?
3289 p.getArg('[', ']') : "";
3290 string const kind = arg.empty() ? "idx" : arg;
3291 begin_inset(os, "Index ");
3292 os << kind << "\nstatus collapsed\n";
3293 parse_text_in_inset(p, os, FLAG_ITEM, false, context, "Index");
3296 preamble.registerAutomaticallyLoadedPackage("splitidx");
3299 else if (t.cs() == "nomenclature") {
3300 context.check_layout(os);
3301 begin_command_inset(os, "nomenclature", "nomenclature");
3302 string prefix = convert_command_inset_arg(p.getArg('[', ']'));
3303 if (!prefix.empty())
3304 os << "prefix " << '"' << prefix << '"' << "\n";
3305 os << "symbol " << '"'
3306 << convert_command_inset_arg(p.verbatim_item());
3307 os << "\"\ndescription \""
3308 << convert_command_inset_arg(p.verbatim_item())
3311 preamble.registerAutomaticallyLoadedPackage("nomencl");
3314 else if (t.cs() == "label") {
3315 context.check_layout(os);
3316 begin_command_inset(os, "label", "label");
3318 << convert_command_inset_arg(p.verbatim_item())
3323 else if (t.cs() == "printindex") {
3324 context.check_layout(os);
3325 begin_command_inset(os, "index_print", "printindex");
3326 os << "type \"idx\"\n";
3328 skip_spaces_braces(p);
3329 preamble.registerAutomaticallyLoadedPackage("makeidx");
3330 if (preamble.use_indices() == "true")
3331 preamble.registerAutomaticallyLoadedPackage("splitidx");
3334 else if (t.cs() == "printnomenclature") {
3336 string width_type = "";
3337 context.check_layout(os);
3338 begin_command_inset(os, "nomencl_print", "printnomenclature");
3339 // case of a custom width
3341 width = p.getArg('[', ']');
3342 width = translate_len(width);
3343 width_type = "custom";
3345 // case of no custom width
3346 // the case of no custom width but the width set
3347 // via \settowidth{\nomlabelwidth}{***} cannot be supported
3348 // because the user could have set anything, not only the width
3349 // of the longest label (which would be width_type = "auto")
3350 string label = convert_command_inset_arg(p.getArg('{', '}'));
3351 if (label.empty() && width_type.empty())
3352 width_type = "none";
3353 os << "set_width \"" << width_type << "\"\n";
3354 if (width_type == "custom")
3355 os << "width \"" << width << '\"';
3357 skip_spaces_braces(p);
3358 preamble.registerAutomaticallyLoadedPackage("nomencl");
3361 else if ((t.cs() == "textsuperscript" || t.cs() == "textsubscript")) {
3362 context.check_layout(os);
3363 begin_inset(os, "script ");
3364 os << t.cs().substr(4) << '\n';
3365 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
3367 if (t.cs() == "textsubscript")
3368 preamble.registerAutomaticallyLoadedPackage("subscript");
3371 else if ((where = is_known(t.cs(), known_quotes))) {
3372 context.check_layout(os);
3373 begin_inset(os, "Quotes ");
3374 os << known_coded_quotes[where - known_quotes];
3376 // LyX adds {} after the quote, so we have to eat
3377 // spaces here if there are any before a possible
3379 eat_whitespace(p, os, context, false);
3383 else if ((where = is_known(t.cs(), known_sizes)) &&
3384 context.new_layout_allowed) {
3385 context.check_layout(os);
3386 TeXFont const oldFont = context.font;
3387 context.font.size = known_coded_sizes[where - known_sizes];
3388 output_font_change(os, oldFont, context.font);
3389 eat_whitespace(p, os, context, false);
3392 else if ((where = is_known(t.cs(), known_font_families)) &&
3393 context.new_layout_allowed) {
3394 context.check_layout(os);
3395 TeXFont const oldFont = context.font;
3396 context.font.family =
3397 known_coded_font_families[where - known_font_families];
3398 output_font_change(os, oldFont, context.font);
3399 eat_whitespace(p, os, context, false);
3402 else if ((where = is_known(t.cs(), known_font_series)) &&
3403 context.new_layout_allowed) {
3404 context.check_layout(os);
3405 TeXFont const oldFont = context.font;
3406 context.font.series =
3407 known_coded_font_series[where - known_font_series];
3408 output_font_change(os, oldFont, context.font);
3409 eat_whitespace(p, os, context, false);
3412 else if ((where = is_known(t.cs(), known_font_shapes)) &&
3413 context.new_layout_allowed) {
3414 context.check_layout(os);
3415 TeXFont const oldFont = context.font;
3416 context.font.shape =
3417 known_coded_font_shapes[where - known_font_shapes];
3418 output_font_change(os, oldFont, context.font);
3419 eat_whitespace(p, os, context, false);
3421 else if ((where = is_known(t.cs(), known_old_font_families)) &&
3422 context.new_layout_allowed) {
3423 context.check_layout(os);
3424 TeXFont const oldFont = context.font;
3425 context.font.init();
3426 context.font.size = oldFont.size;
3427 context.font.family =
3428 known_coded_font_families[where - known_old_font_families];
3429 output_font_change(os, oldFont, context.font);
3430 eat_whitespace(p, os, context, false);
3433 else if ((where = is_known(t.cs(), known_old_font_series)) &&
3434 context.new_layout_allowed) {
3435 context.check_layout(os);
3436 TeXFont const oldFont = context.font;
3437 context.font.init();
3438 context.font.size = oldFont.size;
3439 context.font.series =
3440 known_coded_font_series[where - known_old_font_series];
3441 output_font_change(os, oldFont, context.font);
3442 eat_whitespace(p, os, context, false);
3445 else if ((where = is_known(t.cs(), known_old_font_shapes)) &&
3446 context.new_layout_allowed) {
3447 context.check_layout(os);
3448 TeXFont const oldFont = context.font;
3449 context.font.init();
3450 context.font.size = oldFont.size;
3451 context.font.shape =
3452 known_coded_font_shapes[where - known_old_font_shapes];
3453 output_font_change(os, oldFont, context.font);
3454 eat_whitespace(p, os, context, false);
3457 else if (t.cs() == "selectlanguage") {
3458 context.check_layout(os);
3459 // save the language for the case that a
3460 // \foreignlanguage is used
3461 context.font.language = babel2lyx(p.verbatim_item());
3462 os << "\n\\lang " << context.font.language << "\n";
3465 else if (t.cs() == "foreignlanguage") {
3466 string const lang = babel2lyx(p.verbatim_item());
3467 parse_text_attributes(p, os, FLAG_ITEM, outer,
3469 context.font.language, lang);
3472 else if (is_known(t.cs().substr(4, string::npos), polyglossia_languages)) {
3473 // scheme is \textLANGUAGE{text} where LANGUAGE is in polyglossia_languages[]
3474 string const lang = polyglossia2lyx(t.cs().substr(4, string::npos));
3475 parse_text_attributes(p, os, FLAG_ITEM, outer,
3477 context.font.language, lang);
3480 else if (t.cs() == "inputencoding") {
3481 // nothing to write here
3482 string const enc = subst(p.verbatim_item(), "\n", " ");
3486 else if ((where = is_known(t.cs(), known_special_chars))) {
3487 context.check_layout(os);
3488 os << "\\SpecialChar \\"
3489 << known_coded_special_chars[where - known_special_chars]
3491 skip_spaces_braces(p);
3494 else if (t.cs() == "nobreakdash" && p.next_token().asInput() == "-") {
3495 context.check_layout(os);
3496 os << "\\SpecialChar \\nobreakdash-\n";
3500 else if (t.cs() == "textquotedbl") {
3501 context.check_layout(os);
3506 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
3507 context.check_layout(os);
3508 os << "\\SpecialChar \\@.\n";
3512 else if (t.cs() == "-") {
3513 context.check_layout(os);
3514 os << "\\SpecialChar \\-\n";
3517 else if (t.cs() == "textasciitilde") {
3518 context.check_layout(os);
3520 skip_spaces_braces(p);
3523 else if (t.cs() == "textasciicircum") {
3524 context.check_layout(os);
3526 skip_spaces_braces(p);
3529 else if (t.cs() == "textbackslash") {
3530 context.check_layout(os);
3531 os << "\n\\backslash\n";
3532 skip_spaces_braces(p);
3535 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
3536 || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
3538 context.check_layout(os);
3542 else if (t.cs() == "char") {
3543 context.check_layout(os);
3544 if (p.next_token().character() == '`') {
3546 if (p.next_token().cs() == "\"") {
3551 handle_ert(os, "\\char`", context);
3554 handle_ert(os, "\\char", context);
3558 else if (t.cs() == "verb") {
3559 context.check_layout(os);
3560 char const delimiter = p.next_token().character();
3561 string const arg = p.getArg(delimiter, delimiter);
3563 oss << "\\verb" << delimiter << arg << delimiter;
3564 handle_ert(os, oss.str(), context);
3567 // Problem: \= creates a tabstop inside the tabbing environment
3568 // and else an accent. In the latter case we really would want
3569 // \={o} instead of \= o.
3570 else if (t.cs() == "=" && (flags & FLAG_TABBING))
3571 handle_ert(os, t.asInput(), context);
3573 // accents (see Table 6 in Comprehensive LaTeX Symbol List)
3574 else if (t.cs().size() == 1
3575 && contains("\"'.=^`bcdHkrtuv~", t.cs())) {
3576 context.check_layout(os);
3577 // try to see whether the string is in unicodesymbols
3580 string command = t.asInput() + "{"
3581 + trimSpaceAndEol(p.verbatim_item())
3584 docstring s = encodings.fromLaTeXCommand(from_utf8(command),
3585 Encodings::TEXT_CMD | Encodings::MATH_CMD,
3586 termination, rem, &req);
3589 cerr << "When parsing " << command
3590 << ", result is " << to_utf8(s)
3591 << "+" << to_utf8(rem) << endl;
3593 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
3594 preamble.registerAutomaticallyLoadedPackage(*it);
3596 // we did not find a non-ert version
3597 handle_ert(os, command, context);
3600 else if (t.cs() == "\\") {
3601 context.check_layout(os);
3603 handle_ert(os, "\\\\" + p.getOpt(), context);
3604 else if (p.next_token().asInput() == "*") {
3606 // getOpt() eats the following space if there
3607 // is no optional argument, but that is OK
3608 // here since it has no effect in the output.
3609 handle_ert(os, "\\\\*" + p.getOpt(), context);
3612 begin_inset(os, "Newline newline");
3617 else if (t.cs() == "newline" ||
3618 (t.cs() == "linebreak" && !p.hasOpt())) {
3619 context.check_layout(os);
3620 begin_inset(os, "Newline ");
3623 skip_spaces_braces(p);
3626 else if (t.cs() == "input" || t.cs() == "include"
3627 || t.cs() == "verbatiminput") {
3628 string name = t.cs();
3629 if (t.cs() == "verbatiminput"
3630 && p.next_token().asInput() == "*")
3631 name += p.get_token().asInput();
3632 context.check_layout(os);
3633 string filename(normalize_filename(p.getArg('{', '}')));
3634 string const path = getMasterFilePath();
3635 // We want to preserve relative / absolute filenames,
3636 // therefore path is only used for testing
3637 if ((t.cs() == "include" || t.cs() == "input") &&
3638 !makeAbsPath(filename, path).exists()) {
3639 // The file extension is probably missing.
3640 // Now try to find it out.
3641 string const tex_name =
3642 find_file(filename, path,
3643 known_tex_extensions);
3644 if (!tex_name.empty())
3645 filename = tex_name;
3647 bool external = false;
3649 if (makeAbsPath(filename, path).exists()) {
3650 string const abstexname =
3651 makeAbsPath(filename, path).absFileName();
3652 string const abslyxname =
3653 changeExtension(abstexname, ".lyx");
3654 string const absfigname =
3655 changeExtension(abstexname, ".fig");
3656 fix_relative_filename(filename);
3657 string const lyxname =
3658 changeExtension(filename, ".lyx");
3660 external = FileName(absfigname).exists();
3661 if (t.cs() == "input") {
3662 string const ext = getExtension(abstexname);
3664 // Combined PS/LaTeX:
3665 // x.eps, x.pstex_t (old xfig)
3666 // x.pstex, x.pstex_t (new xfig, e.g. 3.2.5)
3667 FileName const absepsname(
3668 changeExtension(abstexname, ".eps"));
3669 FileName const abspstexname(
3670 changeExtension(abstexname, ".pstex"));
3671 bool const xfigeps =
3672 (absepsname.exists() ||
3673 abspstexname.exists()) &&
3676 // Combined PDF/LaTeX:
3677 // x.pdf, x.pdftex_t (old xfig)
3678 // x.pdf, x.pdf_t (new xfig, e.g. 3.2.5)
3679 FileName const abspdfname(
3680 changeExtension(abstexname, ".pdf"));
3681 bool const xfigpdf =
3682 abspdfname.exists() &&
3683 (ext == "pdftex_t" || ext == "pdf_t");
3687 // Combined PS/PDF/LaTeX:
3688 // x_pspdftex.eps, x_pspdftex.pdf, x.pspdftex
3689 string const absbase2(
3690 removeExtension(abstexname) + "_pspdftex");
3691 FileName const abseps2name(
3692 addExtension(absbase2, ".eps"));
3693 FileName const abspdf2name(
3694 addExtension(absbase2, ".pdf"));
3695 bool const xfigboth =
3696 abspdf2name.exists() &&
3697 abseps2name.exists() && ext == "pspdftex";
3699 xfig = xfigpdf || xfigeps || xfigboth;
3700 external = external && xfig;
3703 outname = changeExtension(filename, ".fig");
3705 // Don't try to convert, the result
3706 // would be full of ERT.
3708 } else if (t.cs() != "verbatiminput" &&
3709 tex2lyx(abstexname, FileName(abslyxname),
3716 cerr << "Warning: Could not find included file '"
3717 << filename << "'." << endl;
3721 begin_inset(os, "External\n");
3722 os << "\ttemplate XFig\n"
3723 << "\tfilename " << outname << '\n';
3724 registerExternalTemplatePackages("XFig");
3726 begin_command_inset(os, "include", name);
3727 os << "preview false\n"
3728 "filename \"" << outname << "\"\n";
3729 if (t.cs() == "verbatiminput")
3730 preamble.registerAutomaticallyLoadedPackage("verbatim");
3735 else if (t.cs() == "bibliographystyle") {
3736 // store new bibliographystyle
3737 bibliographystyle = p.verbatim_item();
3738 // If any other command than \bibliography and
3739 // \nocite{*} follows, we need to output the style
3740 // (because it might be used by that command).
3741 // Otherwise, it will automatically be output by LyX.
3744 for (Token t2 = p.get_token(); p.good(); t2 = p.get_token()) {
3745 if (t2.cat() == catBegin)
3747 if (t2.cat() != catEscape)
3749 if (t2.cs() == "nocite") {
3750 if (p.getArg('{', '}') == "*")
3752 } else if (t2.cs() == "bibliography")
3759 "\\bibliographystyle{" + bibliographystyle + '}',
3764 else if (t.cs() == "bibliography") {
3765 context.check_layout(os);
3766 begin_command_inset(os, "bibtex", "bibtex");
3767 if (!btprint.empty()) {
3768 os << "btprint " << '"' << "btPrintAll" << '"' << "\n";
3769 // clear the string because the next BibTeX inset can be without the
3770 // \nocite{*} option
3773 os << "bibfiles " << '"' << p.verbatim_item() << '"' << "\n";
3774 // Do we have a bibliographystyle set?
3775 if (!bibliographystyle.empty())
3776 os << "options " << '"' << bibliographystyle << '"' << "\n";
3780 else if (t.cs() == "parbox") {
3781 // Test whether this is an outer box of a shaded box
3783 // swallow arguments
3784 while (p.hasOpt()) {
3786 p.skip_spaces(true);
3789 p.skip_spaces(true);
3791 if (p.next_token().cat() == catBegin)
3793 p.skip_spaces(true);
3794 Token to = p.get_token();
3795 bool shaded = false;
3796 if (to.asInput() == "\\begin") {
3797 p.skip_spaces(true);
3798 if (p.getArg('{', '}') == "shaded")
3803 parse_outer_box(p, os, FLAG_ITEM, outer,
3804 context, "parbox", "shaded");
3806 parse_box(p, os, 0, FLAG_ITEM, outer, context,
3810 else if (t.cs() == "ovalbox" || t.cs() == "Ovalbox" ||
3811 t.cs() == "shadowbox" || t.cs() == "doublebox")
3812 parse_outer_box(p, os, FLAG_ITEM, outer, context, t.cs(), "");
3814 else if (t.cs() == "framebox") {
3815 if (p.next_token().character() == '(') {
3816 //the syntax is: \framebox(x,y)[position]{content}
3817 string arg = t.asInput();
3818 arg += p.getFullParentheseArg();
3819 arg += p.getFullOpt();
3820 eat_whitespace(p, os, context, false);
3821 handle_ert(os, arg + '{', context);
3822 eat_whitespace(p, os, context, false);
3823 parse_text(p, os, FLAG_ITEM, outer, context);
3824 handle_ert(os, "}", context);
3826 string special = p.getFullOpt();
3827 special += p.getOpt();
3828 parse_outer_box(p, os, FLAG_ITEM, outer,
3829 context, t.cs(), special);
3833 //\makebox() is part of the picture environment and different from \makebox{}
3834 //\makebox{} will be parsed by parse_box
3835 else if (t.cs() == "makebox") {
3836 if (p.next_token().character() == '(') {
3837 //the syntax is: \makebox(x,y)[position]{content}
3838 string arg = t.asInput();
3839 arg += p.getFullParentheseArg();
3840 arg += p.getFullOpt();
3841 eat_whitespace(p, os, context, false);
3842 handle_ert(os, arg + '{', context);
3843 eat_whitespace(p, os, context, false);
3844 parse_text(p, os, FLAG_ITEM, outer, context);
3845 handle_ert(os, "}", context);
3847 //the syntax is: \makebox[width][position]{content}
3848 parse_box(p, os, 0, FLAG_ITEM, outer, context,
3852 else if (t.cs() == "smallskip" ||
3853 t.cs() == "medskip" ||
3854 t.cs() == "bigskip" ||
3855 t.cs() == "vfill") {
3856 context.check_layout(os);
3857 begin_inset(os, "VSpace ");
3860 skip_spaces_braces(p);
3863 else if ((where = is_known(t.cs(), known_spaces))) {
3864 context.check_layout(os);
3865 begin_inset(os, "space ");
3866 os << '\\' << known_coded_spaces[where - known_spaces]
3869 // LaTeX swallows whitespace after all spaces except
3870 // "\\,". We have to do that here, too, because LyX
3871 // adds "{}" which would make the spaces significant.
3873 eat_whitespace(p, os, context, false);
3874 // LyX adds "{}" after all spaces except "\\ " and
3875 // "\\,", so we have to remove "{}".
3876 // "\\,{}" is equivalent to "\\," in LaTeX, so we
3877 // remove the braces after "\\,", too.
3882 else if (t.cs() == "newpage" ||
3883 (t.cs() == "pagebreak" && !p.hasOpt()) ||
3884 t.cs() == "clearpage" ||
3885 t.cs() == "cleardoublepage") {
3886 context.check_layout(os);
3887 begin_inset(os, "Newpage ");
3890 skip_spaces_braces(p);
3893 else if (t.cs() == "DeclareRobustCommand" ||
3894 t.cs() == "DeclareRobustCommandx" ||
3895 t.cs() == "newcommand" ||
3896 t.cs() == "newcommandx" ||
3897 t.cs() == "providecommand" ||
3898 t.cs() == "providecommandx" ||
3899 t.cs() == "renewcommand" ||
3900 t.cs() == "renewcommandx") {
3901 // DeclareRobustCommand, DeclareRobustCommandx,
3902 // providecommand and providecommandx could be handled
3903 // by parse_command(), but we need to call
3904 // add_known_command() here.
3905 string name = t.asInput();
3906 if (p.next_token().asInput() == "*") {
3907 // Starred form. Eat '*'
3911 string const command = p.verbatim_item();
3912 string const opt1 = p.getFullOpt();
3913 string const opt2 = p.getFullOpt();
3914 add_known_command(command, opt1, !opt2.empty());
3915 string const ert = name + '{' + command + '}' +
3917 '{' + p.verbatim_item() + '}';
3919 if (t.cs() == "DeclareRobustCommand" ||
3920 t.cs() == "DeclareRobustCommandx" ||
3921 t.cs() == "providecommand" ||
3922 t.cs() == "providecommandx" ||
3923 name[name.length()-1] == '*')
3924 handle_ert(os, ert, context);
3926 context.check_layout(os);
3927 begin_inset(os, "FormulaMacro");
3933 else if (t.cs() == "let" && p.next_token().asInput() != "*") {
3934 // let could be handled by parse_command(),
3935 // but we need to call add_known_command() here.
3936 string ert = t.asInput();
3939 if (p.next_token().cat() == catBegin) {
3940 name = p.verbatim_item();
3941 ert += '{' + name + '}';
3943 name = p.verbatim_item();
3948 if (p.next_token().cat() == catBegin) {
3949 command = p.verbatim_item();
3950 ert += '{' + command + '}';
3952 command = p.verbatim_item();
3955 // If command is known, make name known too, to parse
3956 // its arguments correctly. For this reason we also
3957 // have commands in syntax.default that are hardcoded.
3958 CommandMap::iterator it = known_commands.find(command);
3959 if (it != known_commands.end())
3960 known_commands[t.asInput()] = it->second;
3961 handle_ert(os, ert, context);
3964 else if (t.cs() == "hspace" || t.cs() == "vspace") {
3965 bool starred = false;
3966 if (p.next_token().asInput() == "*") {
3970 string name = t.asInput();
3971 string const length = p.verbatim_item();
3974 bool valid = splitLatexLength(length, valstring, unit);
3975 bool known_hspace = false;
3976 bool known_vspace = false;
3977 bool known_unit = false;
3980 istringstream iss(valstring);
3983 if (t.cs()[0] == 'h') {
3984 if (unit == "\\fill") {
3989 known_hspace = true;
3992 if (unit == "\\smallskipamount") {
3994 known_vspace = true;
3995 } else if (unit == "\\medskipamount") {
3997 known_vspace = true;
3998 } else if (unit == "\\bigskipamount") {
4000 known_vspace = true;
4001 } else if (unit == "\\fill") {
4003 known_vspace = true;
4007 if (!known_hspace && !known_vspace) {
4008 switch (unitFromString(unit)) {
4029 if (t.cs()[0] == 'h' && (known_unit || known_hspace)) {
4030 // Literal horizontal length or known variable
4031 context.check_layout(os);
4032 begin_inset(os, "space ");
4040 if (known_unit && !known_hspace)
4042 << translate_len(length);
4044 } else if (known_unit || known_vspace) {
4045 // Literal vertical length or known variable
4046 context.check_layout(os);
4047 begin_inset(os, "VSpace ");
4055 // LyX can't handle other length variables in Inset VSpace/space
4060 handle_ert(os, name + '{' + unit + '}', context);
4061 else if (value == -1.0)
4062 handle_ert(os, name + "{-" + unit + '}', context);
4064 handle_ert(os, name + '{' + valstring + unit + '}', context);
4066 handle_ert(os, name + '{' + length + '}', context);
4070 // The single '=' is meant here.
4071 else if ((newinsetlayout = findInsetLayout(context.textclass, t.cs(), true))) {
4073 context.check_layout(os);
4074 begin_inset(os, "Flex ");
4075 os << to_utf8(newinsetlayout->name()) << '\n'
4076 << "status collapsed\n";
4077 parse_text_in_inset(p, os, FLAG_ITEM, false, context, newinsetlayout);
4081 else if (t.cs() == "includepdf") {
4083 string const arg = p.getArg('[', ']');
4084 map<string, string> opts;
4085 vector<string> keys;
4086 split_map(arg, opts, keys);
4087 string name = normalize_filename(p.verbatim_item());
4088 string const path = getMasterFilePath();
4089 // We want to preserve relative / absolute filenames,
4090 // therefore path is only used for testing
4091 if (!makeAbsPath(name, path).exists()) {
4092 // The file extension is probably missing.
4093 // Now try to find it out.
4094 char const * const pdfpages_format[] = {"pdf", 0};
4095 string const pdftex_name =
4096 find_file(name, path, pdfpages_format);
4097 if (!pdftex_name.empty()) {
4102 if (makeAbsPath(name, path).exists())
4103 fix_relative_filename(name);
4105 cerr << "Warning: Could not find file '"
4106 << name << "'." << endl;
4108 context.check_layout(os);
4109 begin_inset(os, "External\n\ttemplate ");
4110 os << "PDFPages\n\tfilename "
4112 // parse the options
4113 if (opts.find("pages") != opts.end())
4114 os << "\textra LaTeX \"pages="
4115 << opts["pages"] << "\"\n";
4116 if (opts.find("angle") != opts.end())
4117 os << "\trotateAngle "
4118 << opts["angle"] << '\n';
4119 if (opts.find("origin") != opts.end()) {
4121 string const opt = opts["origin"];
4122 if (opt == "tl") ss << "topleft";
4123 if (opt == "bl") ss << "bottomleft";
4124 if (opt == "Bl") ss << "baselineleft";
4125 if (opt == "c") ss << "center";
4126 if (opt == "tc") ss << "topcenter";
4127 if (opt == "bc") ss << "bottomcenter";
4128 if (opt == "Bc") ss << "baselinecenter";
4129 if (opt == "tr") ss << "topright";
4130 if (opt == "br") ss << "bottomright";
4131 if (opt == "Br") ss << "baselineright";
4132 if (!ss.str().empty())
4133 os << "\trotateOrigin " << ss.str() << '\n';
4135 cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
4137 if (opts.find("width") != opts.end())
4139 << translate_len(opts["width"]) << '\n';
4140 if (opts.find("height") != opts.end())
4142 << translate_len(opts["height"]) << '\n';
4143 if (opts.find("keepaspectratio") != opts.end())
4144 os << "\tkeepAspectRatio\n";
4146 context.check_layout(os);
4147 registerExternalTemplatePackages("PDFPages");
4150 else if (t.cs() == "loadgame") {
4152 string name = normalize_filename(p.verbatim_item());
4153 string const path = getMasterFilePath();
4154 // We want to preserve relative / absolute filenames,
4155 // therefore path is only used for testing
4156 if (!makeAbsPath(name, path).exists()) {
4157 // The file extension is probably missing.
4158 // Now try to find it out.
4159 char const * const lyxskak_format[] = {"fen", 0};
4160 string const lyxskak_name =
4161 find_file(name, path, lyxskak_format);
4162 if (!lyxskak_name.empty())
4163 name = lyxskak_name;
4165 if (makeAbsPath(name, path).exists())
4166 fix_relative_filename(name);
4168 cerr << "Warning: Could not find file '"
4169 << name << "'." << endl;
4170 context.check_layout(os);
4171 begin_inset(os, "External\n\ttemplate ");
4172 os << "ChessDiagram\n\tfilename "
4175 context.check_layout(os);
4176 // after a \loadgame follows a \showboard
4177 if (p.get_token().asInput() == "showboard")
4179 registerExternalTemplatePackages("ChessDiagram");
4183 // try to see whether the string is in unicodesymbols
4184 // Only use text mode commands, since we are in text mode here,
4185 // and math commands may be invalid (bug 6797)
4189 docstring s = encodings.fromLaTeXCommand(from_utf8(t.asInput()),
4190 Encodings::TEXT_CMD, termination, rem, &req);
4193 cerr << "When parsing " << t.cs()
4194 << ", result is " << to_utf8(s)
4195 << "+" << to_utf8(rem) << endl;
4196 context.check_layout(os);
4199 skip_spaces_braces(p);
4200 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
4201 preamble.registerAutomaticallyLoadedPackage(*it);
4203 //cerr << "#: " << t << " mode: " << mode << endl;
4204 // heuristic: read up to next non-nested space
4206 string s = t.asInput();
4207 string z = p.verbatim_item();
4208 while (p.good() && z != " " && z.size()) {
4209 //cerr << "read: " << z << endl;
4211 z = p.verbatim_item();
4213 cerr << "found ERT: " << s << endl;
4214 handle_ert(os, s + ' ', context);
4217 string name = t.asInput();
4218 if (p.next_token().asInput() == "*") {
4219 // Starred commands like \vspace*{}
4220 p.get_token(); // Eat '*'
4223 if (!parse_command(name, p, os, outer, context))
4224 handle_ert(os, name, context);
4228 if (flags & FLAG_LEAVE) {
4229 flags &= ~FLAG_LEAVE;