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 known_languages with .lyx names
137 * please keep this in sync with known_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 parent_context.check_layout(os);
1256 parent_context.font.language = polyglossia2lyx(name);
1257 os << "\n\\lang " << parent_context.font.language << "\n";
1261 else if (unstarred_name == "tabular" || name == "longtable") {
1262 eat_whitespace(p, os, parent_context, false);
1263 string width = "0pt";
1264 if (name == "tabular*") {
1265 width = lyx::translate_len(p.getArg('{', '}'));
1266 eat_whitespace(p, os, parent_context, false);
1268 parent_context.check_layout(os);
1269 begin_inset(os, "Tabular ");
1270 handle_tabular(p, os, name, width, parent_context);
1275 else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
1276 eat_whitespace(p, os, parent_context, false);
1277 string const opt = p.hasOpt() ? p.getArg('[', ']') : string();
1278 eat_whitespace(p, os, parent_context, false);
1279 parent_context.check_layout(os);
1280 begin_inset(os, "Float " + unstarred_name + "\n");
1281 // store the float type for subfloats
1282 // subfloats only work with figures and tables
1283 if (unstarred_name == "figure")
1284 float_type = unstarred_name;
1285 else if (unstarred_name == "table")
1286 float_type = unstarred_name;
1290 os << "placement " << opt << '\n';
1291 if (contains(opt, "H"))
1292 preamble.registerAutomaticallyLoadedPackage("float");
1294 Floating const & fl = parent_context.textclass.floats()
1295 .getType(unstarred_name);
1296 if (!fl.floattype().empty() && fl.usesFloatPkg())
1297 preamble.registerAutomaticallyLoadedPackage("float");
1300 os << "wide " << convert<string>(is_starred)
1301 << "\nsideways false"
1302 << "\nstatus open\n\n";
1303 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1305 // We don't need really a new paragraph, but
1306 // we must make sure that the next item gets a \begin_layout.
1307 parent_context.new_paragraph(os);
1309 // the float is parsed thus delete the type
1313 else if (unstarred_name == "sidewaysfigure"
1314 || unstarred_name == "sidewaystable") {
1315 eat_whitespace(p, os, parent_context, false);
1316 parent_context.check_layout(os);
1317 if (unstarred_name == "sidewaysfigure")
1318 begin_inset(os, "Float figure\n");
1320 begin_inset(os, "Float table\n");
1321 os << "wide " << convert<string>(is_starred)
1322 << "\nsideways true"
1323 << "\nstatus open\n\n";
1324 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1326 // We don't need really a new paragraph, but
1327 // we must make sure that the next item gets a \begin_layout.
1328 parent_context.new_paragraph(os);
1330 preamble.registerAutomaticallyLoadedPackage("rotfloat");
1333 else if (name == "wrapfigure" || name == "wraptable") {
1334 // syntax is \begin{wrapfigure}[lines]{placement}[overhang]{width}
1335 eat_whitespace(p, os, parent_context, false);
1336 parent_context.check_layout(os);
1339 string overhang = "0col%";
1342 lines = p.getArg('[', ']');
1343 string const placement = p.getArg('{', '}');
1345 overhang = p.getArg('[', ']');
1346 string const width = p.getArg('{', '}');
1348 if (name == "wrapfigure")
1349 begin_inset(os, "Wrap figure\n");
1351 begin_inset(os, "Wrap table\n");
1352 os << "lines " << lines
1353 << "\nplacement " << placement
1354 << "\noverhang " << lyx::translate_len(overhang)
1355 << "\nwidth " << lyx::translate_len(width)
1356 << "\nstatus open\n\n";
1357 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1359 // We don't need really a new paragraph, but
1360 // we must make sure that the next item gets a \begin_layout.
1361 parent_context.new_paragraph(os);
1363 preamble.registerAutomaticallyLoadedPackage("wrapfig");
1366 else if (name == "minipage") {
1367 eat_whitespace(p, os, parent_context, false);
1368 // Test whether this is an outer box of a shaded box
1370 // swallow arguments
1371 while (p.hasOpt()) {
1373 p.skip_spaces(true);
1376 p.skip_spaces(true);
1377 Token t = p.get_token();
1378 bool shaded = false;
1379 if (t.asInput() == "\\begin") {
1380 p.skip_spaces(true);
1381 if (p.getArg('{', '}') == "shaded")
1386 parse_outer_box(p, os, FLAG_END, outer,
1387 parent_context, name, "shaded");
1389 parse_box(p, os, 0, FLAG_END, outer, parent_context,
1394 else if (name == "comment") {
1395 eat_whitespace(p, os, parent_context, false);
1396 parent_context.check_layout(os);
1397 begin_inset(os, "Note Comment\n");
1398 os << "status open\n";
1399 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1402 skip_braces(p); // eat {} that might by set by LyX behind comments
1403 preamble.registerAutomaticallyLoadedPackage("verbatim");
1406 else if (name == "verbatim") {
1407 os << "\n\\end_layout\n\n\\begin_layout Verbatim\n";
1408 string const s = p.plainEnvironment("verbatim");
1409 string::const_iterator it2 = s.begin();
1410 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
1412 os << "\\backslash ";
1413 else if (*it == '\n') {
1415 // avoid adding an empty paragraph at the end
1416 // FIXME: if there are 2 consecutive spaces at the end ignore it
1417 // because LyX will re-add a \n
1418 // This hack must be removed once bug 8049 is fixed!
1419 if ((it + 1 != et) && (it + 2 != et || *it2 != '\n'))
1420 os << "\n\\end_layout\n\\begin_layout Verbatim\n";
1424 os << "\n\\end_layout\n\n";
1426 // reset to Standard layout
1427 os << "\n\\begin_layout Standard\n";
1430 else if (name == "lyxgreyedout") {
1431 eat_whitespace(p, os, parent_context, false);
1432 parent_context.check_layout(os);
1433 begin_inset(os, "Note Greyedout\n");
1434 os << "status open\n";
1435 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
1438 if (!preamble.notefontcolor().empty())
1439 preamble.registerAutomaticallyLoadedPackage("color");
1442 else if (name == "framed" || name == "shaded") {
1443 eat_whitespace(p, os, parent_context, false);
1444 parse_outer_box(p, os, FLAG_END, outer, parent_context, name, "");
1448 else if (name == "lstlisting") {
1449 eat_whitespace(p, os, parent_context, false);
1450 // FIXME handle the automatic color package loading
1451 // uwestoehr asks: In what case color is loaded?
1452 parse_listings(p, os, parent_context, false);
1456 else if (!parent_context.new_layout_allowed)
1457 parse_unknown_environment(p, name, os, FLAG_END, outer,
1460 // Alignment and spacing settings
1461 // FIXME (bug xxxx): These settings can span multiple paragraphs and
1462 // therefore are totally broken!
1463 // Note that \centering, raggedright, and raggedleft cannot be handled, as
1464 // they are commands not environments. They are furthermore switches that
1465 // can be ended by another switches, but also by commands like \footnote or
1466 // \parbox. So the only safe way is to leave them untouched.
1467 else if (name == "center" || name == "centering" ||
1468 name == "flushleft" || name == "flushright" ||
1469 name == "singlespace" || name == "onehalfspace" ||
1470 name == "doublespace" || name == "spacing") {
1471 eat_whitespace(p, os, parent_context, false);
1472 // We must begin a new paragraph if not already done
1473 if (! parent_context.atParagraphStart()) {
1474 parent_context.check_end_layout(os);
1475 parent_context.new_paragraph(os);
1477 if (name == "flushleft")
1478 parent_context.add_extra_stuff("\\align left\n");
1479 else if (name == "flushright")
1480 parent_context.add_extra_stuff("\\align right\n");
1481 else if (name == "center" || name == "centering")
1482 parent_context.add_extra_stuff("\\align center\n");
1483 else if (name == "singlespace")
1484 parent_context.add_extra_stuff("\\paragraph_spacing single\n");
1485 else if (name == "onehalfspace") {
1486 parent_context.add_extra_stuff("\\paragraph_spacing onehalf\n");
1487 preamble.registerAutomaticallyLoadedPackage("setspace");
1488 } else if (name == "doublespace") {
1489 parent_context.add_extra_stuff("\\paragraph_spacing double\n");
1490 preamble.registerAutomaticallyLoadedPackage("setspace");
1491 } else if (name == "spacing") {
1492 parent_context.add_extra_stuff("\\paragraph_spacing other " + p.verbatim_item() + "\n");
1493 preamble.registerAutomaticallyLoadedPackage("setspace");
1495 parse_text(p, os, FLAG_END, outer, parent_context);
1496 // Just in case the environment is empty
1497 parent_context.extra_stuff.erase();
1498 // We must begin a new paragraph to reset the alignment
1499 parent_context.new_paragraph(os);
1503 // The single '=' is meant here.
1504 else if ((newlayout = findLayout(parent_context.textclass, name, false))) {
1505 eat_whitespace(p, os, parent_context, false);
1506 Context context(true, parent_context.textclass, newlayout,
1507 parent_context.layout, parent_context.font);
1508 if (parent_context.deeper_paragraph) {
1509 // We are beginning a nested environment after a
1510 // deeper paragraph inside the outer list environment.
1511 // Therefore we don't need to output a "begin deeper".
1512 context.need_end_deeper = true;
1514 parent_context.check_end_layout(os);
1515 if (last_env == name) {
1516 // we need to output a separator since LyX would export
1517 // the two environments as one otherwise (bug 5716)
1518 docstring const sep = from_ascii("--Separator--");
1519 TeX2LyXDocClass const & textclass(parent_context.textclass);
1520 if (textclass.hasLayout(sep)) {
1521 Context newcontext(parent_context);
1522 newcontext.layout = &(textclass[sep]);
1523 newcontext.check_layout(os);
1524 newcontext.check_end_layout(os);
1526 parent_context.check_layout(os);
1527 begin_inset(os, "Note Note\n");
1528 os << "status closed\n";
1529 Context newcontext(true, textclass,
1530 &(textclass.defaultLayout()));
1531 newcontext.check_layout(os);
1532 newcontext.check_end_layout(os);
1534 parent_context.check_end_layout(os);
1537 switch (context.layout->latextype) {
1538 case LATEX_LIST_ENVIRONMENT:
1539 context.add_par_extra_stuff("\\labelwidthstring "
1540 + p.verbatim_item() + '\n');
1543 case LATEX_BIB_ENVIRONMENT:
1544 p.verbatim_item(); // swallow next arg
1550 context.check_deeper(os);
1551 // handle known optional and required arguments
1552 // layouts require all optional arguments before the required ones
1553 // Unfortunately LyX can't handle arguments of list arguments (bug 7468):
1554 // It is impossible to place anything after the environment name,
1555 // but before the first \\item.
1556 if (context.layout->latextype == LATEX_ENVIRONMENT) {
1557 bool need_layout = true;
1558 unsigned int optargs = 0;
1559 while (optargs < context.layout->optargs) {
1560 eat_whitespace(p, os, context, false);
1561 if (p.next_token().cat() == catEscape ||
1562 p.next_token().character() != '[')
1564 p.get_token(); // eat '['
1566 context.check_layout(os);
1567 need_layout = false;
1569 begin_inset(os, "Argument\n");
1570 os << "status collapsed\n\n";
1571 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
1573 eat_whitespace(p, os, context, false);
1576 unsigned int reqargs = 0;
1577 while (reqargs < context.layout->reqargs) {
1578 eat_whitespace(p, os, context, false);
1579 if (p.next_token().cat() != catBegin)
1581 p.get_token(); // eat '{'
1583 context.check_layout(os);
1584 need_layout = false;
1586 begin_inset(os, "Argument\n");
1587 os << "status collapsed\n\n";
1588 parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context);
1590 eat_whitespace(p, os, context, false);
1594 parse_text(p, os, FLAG_END, outer, context);
1595 context.check_end_layout(os);
1596 if (parent_context.deeper_paragraph) {
1597 // We must suppress the "end deeper" because we
1598 // suppressed the "begin deeper" above.
1599 context.need_end_deeper = false;
1601 context.check_end_deeper(os);
1602 parent_context.new_paragraph(os);
1604 if (!preamble.titleLayoutFound())
1605 preamble.titleLayoutFound(newlayout->intitle);
1606 set<string> const & req = newlayout->requires();
1607 set<string>::const_iterator it = req.begin();
1608 set<string>::const_iterator en = req.end();
1609 for (; it != en; ++it)
1610 preamble.registerAutomaticallyLoadedPackage(*it);
1613 // The single '=' is meant here.
1614 else if ((newinsetlayout = findInsetLayout(parent_context.textclass, name, false))) {
1615 eat_whitespace(p, os, parent_context, false);
1616 parent_context.check_layout(os);
1617 begin_inset(os, "Flex ");
1618 os << to_utf8(newinsetlayout->name()) << '\n'
1619 << "status collapsed\n";
1620 parse_text_in_inset(p, os, FLAG_END, false, parent_context, newinsetlayout);
1624 else if (name == "appendix") {
1625 // This is no good latex style, but it works and is used in some documents...
1626 eat_whitespace(p, os, parent_context, false);
1627 parent_context.check_end_layout(os);
1628 Context context(true, parent_context.textclass, parent_context.layout,
1629 parent_context.layout, parent_context.font);
1630 context.check_layout(os);
1631 os << "\\start_of_appendix\n";
1632 parse_text(p, os, FLAG_END, outer, context);
1633 context.check_end_layout(os);
1637 else if (known_environments.find(name) != known_environments.end()) {
1638 vector<ArgumentType> arguments = known_environments[name];
1639 // The last "argument" denotes wether we may translate the
1640 // environment contents to LyX
1641 // The default required if no argument is given makes us
1642 // compatible with the reLyXre environment.
1643 ArgumentType contents = arguments.empty() ?
1646 if (!arguments.empty())
1647 arguments.pop_back();
1648 // See comment in parse_unknown_environment()
1649 bool const specialfont =
1650 (parent_context.font != parent_context.normalfont);
1651 bool const new_layout_allowed =
1652 parent_context.new_layout_allowed;
1654 parent_context.new_layout_allowed = false;
1655 parse_arguments("\\begin{" + name + "}", arguments, p, os,
1656 outer, parent_context);
1657 if (contents == verbatim)
1658 handle_ert(os, p.verbatimEnvironment(name),
1661 parse_text_snippet(p, os, FLAG_END, outer,
1663 handle_ert(os, "\\end{" + name + "}", parent_context);
1665 parent_context.new_layout_allowed = new_layout_allowed;
1669 parse_unknown_environment(p, name, os, FLAG_END, outer,
1673 active_environments.pop_back();
1677 /// parses a comment and outputs it to \p os.
1678 void parse_comment(Parser & p, ostream & os, Token const & t, Context & context)
1680 LASSERT(t.cat() == catComment, return);
1681 if (!t.cs().empty()) {
1682 context.check_layout(os);
1683 handle_comment(os, '%' + t.cs(), context);
1684 if (p.next_token().cat() == catNewline) {
1685 // A newline after a comment line starts a new
1687 if (context.new_layout_allowed) {
1688 if(!context.atParagraphStart())
1689 // Only start a new paragraph if not already
1690 // done (we might get called recursively)
1691 context.new_paragraph(os);
1693 handle_ert(os, "\n", context);
1694 eat_whitespace(p, os, context, true);
1697 // "%\n" combination
1704 * Reads spaces and comments until the first non-space, non-comment token.
1705 * New paragraphs (double newlines or \\par) are handled like simple spaces
1706 * if \p eatParagraph is true.
1707 * Spaces are skipped, but comments are written to \p os.
1709 void eat_whitespace(Parser & p, ostream & os, Context & context,
1713 Token const & t = p.get_token();
1714 if (t.cat() == catComment)
1715 parse_comment(p, os, t, context);
1716 else if ((! eatParagraph && p.isParagraph()) ||
1717 (t.cat() != catSpace && t.cat() != catNewline)) {
1726 * Set a font attribute, parse text and reset the font attribute.
1727 * \param attribute Attribute name (e.g. \\family, \\shape etc.)
1728 * \param currentvalue Current value of the attribute. Is set to the new
1729 * value during parsing.
1730 * \param newvalue New value of the attribute
1732 void parse_text_attributes(Parser & p, ostream & os, unsigned flags, bool outer,
1733 Context & context, string const & attribute,
1734 string & currentvalue, string const & newvalue)
1736 context.check_layout(os);
1737 string const oldvalue = currentvalue;
1738 currentvalue = newvalue;
1739 os << '\n' << attribute << ' ' << newvalue << "\n";
1740 parse_text_snippet(p, os, flags, outer, context);
1741 context.check_layout(os);
1742 os << '\n' << attribute << ' ' << oldvalue << "\n";
1743 currentvalue = oldvalue;
1747 /// get the arguments of a natbib or jurabib citation command
1748 void get_cite_arguments(Parser & p, bool natbibOrder,
1749 string & before, string & after)
1751 // We need to distinguish "" and "[]", so we can't use p.getOpt().
1753 // text before the citation
1755 // text after the citation
1756 after = p.getFullOpt();
1758 if (!after.empty()) {
1759 before = p.getFullOpt();
1760 if (natbibOrder && !before.empty())
1761 swap(before, after);
1766 /// Convert filenames with TeX macros and/or quotes to something LyX
1768 string const normalize_filename(string const & name)
1770 Parser p(trim(name, "\""));
1773 Token const & t = p.get_token();
1774 if (t.cat() != catEscape)
1776 else if (t.cs() == "lyxdot") {
1777 // This is used by LyX for simple dots in relative
1781 } else if (t.cs() == "space") {
1791 /// Convert \p name from TeX convention (relative to master file) to LyX
1792 /// convention (relative to .lyx file) if it is relative
1793 void fix_relative_filename(string & name)
1795 if (FileName::isAbsolute(name))
1798 name = to_utf8(makeRelPath(from_utf8(makeAbsPath(name, getMasterFilePath()).absFileName()),
1799 from_utf8(getParentFilePath())));
1803 /// Parse a NoWeb Scrap section. The initial "<<" is already parsed.
1804 void parse_noweb(Parser & p, ostream & os, Context & context)
1806 // assemble the rest of the keyword
1810 Token const & t = p.get_token();
1811 if (t.asInput() == ">" && p.next_token().asInput() == ">") {
1814 scrap = (p.good() && p.next_token().asInput() == "=");
1816 name += p.get_token().asInput();
1819 name += t.asInput();
1822 if (!scrap || !context.new_layout_allowed ||
1823 !context.textclass.hasLayout(from_ascii("Scrap"))) {
1824 cerr << "Warning: Could not interpret '" << name
1825 << "'. Ignoring it." << endl;
1829 // We use new_paragraph instead of check_end_layout because the stuff
1830 // following the noweb chunk needs to start with a \begin_layout.
1831 // This may create a new paragraph even if there was none in the
1832 // noweb file, but the alternative is an invalid LyX file. Since
1833 // noweb code chunks are implemented with a layout style in LyX they
1834 // always must be in an own paragraph.
1835 context.new_paragraph(os);
1836 Context newcontext(true, context.textclass,
1837 &context.textclass[from_ascii("Scrap")]);
1838 newcontext.check_layout(os);
1841 Token const & t = p.get_token();
1842 // We abuse the parser a bit, because this is no TeX syntax
1844 if (t.cat() == catEscape)
1845 os << subst(t.asInput(), "\\", "\n\\backslash\n");
1848 Context tmp(false, context.textclass,
1849 &context.textclass[from_ascii("Scrap")]);
1850 tmp.need_end_layout = true;
1851 tmp.check_layout(oss);
1852 os << subst(t.asInput(), "\n", oss.str());
1854 // The scrap chunk is ended by an @ at the beginning of a line.
1855 // After the @ the line may contain a comment and/or
1856 // whitespace, but nothing else.
1857 if (t.asInput() == "@" && p.prev_token().cat() == catNewline &&
1858 (p.next_token().cat() == catSpace ||
1859 p.next_token().cat() == catNewline ||
1860 p.next_token().cat() == catComment)) {
1861 while (p.good() && p.next_token().cat() == catSpace)
1862 os << p.get_token().asInput();
1863 if (p.next_token().cat() == catComment)
1864 // The comment includes a final '\n'
1865 os << p.get_token().asInput();
1867 if (p.next_token().cat() == catNewline)
1874 newcontext.check_end_layout(os);
1878 /// detects \\def, \\long\\def and \\global\\long\\def with ws and comments
1879 bool is_macro(Parser & p)
1881 Token first = p.curr_token();
1882 if (first.cat() != catEscape || !p.good())
1884 if (first.cs() == "def")
1886 if (first.cs() != "global" && first.cs() != "long")
1888 Token second = p.get_token();
1890 while (p.good() && !p.isParagraph() && (second.cat() == catSpace ||
1891 second.cat() == catNewline || second.cat() == catComment)) {
1892 second = p.get_token();
1895 bool secondvalid = second.cat() == catEscape;
1897 bool thirdvalid = false;
1898 if (p.good() && first.cs() == "global" && secondvalid &&
1899 second.cs() == "long") {
1900 third = p.get_token();
1902 while (p.good() && !p.isParagraph() &&
1903 (third.cat() == catSpace ||
1904 third.cat() == catNewline ||
1905 third.cat() == catComment)) {
1906 third = p.get_token();
1909 thirdvalid = third.cat() == catEscape;
1911 for (int i = 0; i < pos; ++i)
1916 return (first.cs() == "global" || first.cs() == "long") &&
1917 second.cs() == "def";
1918 return first.cs() == "global" && second.cs() == "long" &&
1919 third.cs() == "def";
1923 /// Parse a macro definition (assumes that is_macro() returned true)
1924 void parse_macro(Parser & p, ostream & os, Context & context)
1926 context.check_layout(os);
1927 Token first = p.curr_token();
1930 string command = first.asInput();
1931 if (first.cs() != "def") {
1933 eat_whitespace(p, os, context, false);
1934 second = p.curr_token();
1935 command += second.asInput();
1936 if (second.cs() != "def") {
1938 eat_whitespace(p, os, context, false);
1939 third = p.curr_token();
1940 command += third.asInput();
1943 eat_whitespace(p, os, context, false);
1944 string const name = p.get_token().cs();
1945 eat_whitespace(p, os, context, false);
1951 while (p.next_token().cat() != catBegin) {
1952 if (p.next_token().cat() == catParameter) {
1957 // followed by number?
1958 if (p.next_token().cat() == catOther) {
1959 char c = p.getChar();
1961 // number = current arity + 1?
1962 if (c == arity + '0' + 1)
1967 paramtext += p.get_token().cs();
1969 paramtext += p.get_token().cs();
1974 // only output simple (i.e. compatible) macro as FormulaMacros
1975 string ert = '\\' + name + ' ' + paramtext + '{' + p.verbatim_item() + '}';
1977 context.check_layout(os);
1978 begin_inset(os, "FormulaMacro");
1979 os << "\n\\def" << ert;
1982 handle_ert(os, command + ert, context);
1986 void registerExternalTemplatePackages(string const & name)
1988 external::TemplateManager const & etm = external::TemplateManager::get();
1989 external::Template const * const et = etm.getTemplateByName(name);
1992 external::Template::Formats::const_iterator cit = et->formats.end();
1994 cit = et->formats.find("PDFLaTeX");
1995 if (cit == et->formats.end())
1996 // If the template has not specified a PDFLaTeX output,
1997 // we try the LaTeX format.
1998 cit = et->formats.find("LaTeX");
1999 if (cit == et->formats.end())
2001 vector<string>::const_iterator qit = cit->second.requirements.begin();
2002 vector<string>::const_iterator qend = cit->second.requirements.end();
2003 for (; qit != qend; ++qit)
2004 preamble.registerAutomaticallyLoadedPackage(*qit);
2007 } // anonymous namespace
2010 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
2013 Layout const * newlayout = 0;
2014 InsetLayout const * newinsetlayout = 0;
2015 char const * const * where = 0;
2016 // Store the latest bibliographystyle and nocite{*} option
2017 // (needed for bibtex inset)
2019 string bibliographystyle = "default";
2020 bool const use_natbib = preamble.isPackageUsed("natbib");
2021 bool const use_jurabib = preamble.isPackageUsed("jurabib");
2024 Token const & t = p.get_token();
2027 debugToken(cerr, t, flags);
2030 if (flags & FLAG_ITEM) {
2031 if (t.cat() == catSpace)
2034 flags &= ~FLAG_ITEM;
2035 if (t.cat() == catBegin) {
2036 // skip the brace and collect everything to the next matching
2038 flags |= FLAG_BRACE_LAST;
2042 // handle only this single token, leave the loop if done
2043 flags |= FLAG_LEAVE;
2046 if (t.cat() != catEscape && t.character() == ']' &&
2047 (flags & FLAG_BRACK_LAST))
2049 if (t.cat() == catEnd && (flags & FLAG_BRACE_LAST))
2052 // If there is anything between \end{env} and \begin{env} we
2053 // don't need to output a separator.
2054 if (t.cat() != catSpace && t.cat() != catNewline &&
2055 t.asInput() != "\\begin")
2061 if (t.cat() == catMath) {
2062 // we are inside some text mode thingy, so opening new math is allowed
2063 context.check_layout(os);
2064 begin_inset(os, "Formula ");
2065 Token const & n = p.get_token();
2066 bool const display(n.cat() == catMath && outer);
2068 // TeX's $$...$$ syntax for displayed math
2070 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
2072 p.get_token(); // skip the second '$' token
2074 // simple $...$ stuff
2077 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
2082 // Prevent the conversion of a line break to a
2083 // space (bug 7668). This does not change the
2084 // output, but looks ugly in LyX.
2085 eat_whitespace(p, os, context, false);
2089 else if (t.cat() == catSuper || t.cat() == catSub)
2090 cerr << "catcode " << t << " illegal in text mode\n";
2092 // Basic support for english quotes. This should be
2093 // extended to other quotes, but is not so easy (a
2094 // left english quote is the same as a right german
2096 else if (t.asInput() == "`" && p.next_token().asInput() == "`") {
2097 context.check_layout(os);
2098 begin_inset(os, "Quotes ");
2104 else if (t.asInput() == "'" && p.next_token().asInput() == "'") {
2105 context.check_layout(os);
2106 begin_inset(os, "Quotes ");
2113 else if (t.asInput() == ">" && p.next_token().asInput() == ">") {
2114 context.check_layout(os);
2115 begin_inset(os, "Quotes ");
2122 else if (t.asInput() == "<" && p.next_token().asInput() == "<") {
2123 context.check_layout(os);
2124 begin_inset(os, "Quotes ");
2131 else if (t.asInput() == "<"
2132 && p.next_token().asInput() == "<" && noweb_mode) {
2134 parse_noweb(p, os, context);
2137 else if (t.cat() == catSpace || (t.cat() == catNewline && ! p.isParagraph()))
2138 check_space(p, os, context);
2140 else if (t.character() == '[' && noweb_mode &&
2141 p.next_token().character() == '[') {
2142 // These can contain underscores
2144 string const s = p.getFullOpt() + ']';
2145 if (p.next_token().character() == ']')
2148 cerr << "Warning: Inserting missing ']' in '"
2149 << s << "'." << endl;
2150 handle_ert(os, s, context);
2153 else if (t.cat() == catLetter) {
2154 context.check_layout(os);
2155 // Workaround for bug 4752.
2156 // FIXME: This whole code block needs to be removed
2157 // when the bug is fixed and tex2lyx produces
2158 // the updated file format.
2159 // The replacement algorithm in LyX is so stupid that
2160 // it even translates a phrase if it is part of a word.
2161 bool handled = false;
2162 for (int const * l = known_phrase_lengths; *l; ++l) {
2163 string phrase = t.cs();
2164 for (int i = 1; i < *l && p.next_token().isAlnumASCII(); ++i)
2165 phrase += p.get_token().cs();
2166 if (is_known(phrase, known_coded_phrases)) {
2167 handle_ert(os, phrase, context);
2171 for (size_t i = 1; i < phrase.length(); ++i)
2179 else if (t.cat() == catOther ||
2180 t.cat() == catAlign ||
2181 t.cat() == catParameter) {
2182 // This translates "&" to "\\&" which may be wrong...
2183 context.check_layout(os);
2187 else if (p.isParagraph()) {
2188 if (context.new_layout_allowed)
2189 context.new_paragraph(os);
2191 handle_ert(os, "\\par ", context);
2192 eat_whitespace(p, os, context, true);
2195 else if (t.cat() == catActive) {
2196 context.check_layout(os);
2197 if (t.character() == '~') {
2198 if (context.layout->free_spacing)
2201 begin_inset(os, "space ~\n");
2208 else if (t.cat() == catBegin) {
2209 Token const next = p.next_token();
2210 Token const end = p.next_next_token();
2211 if (next.cat() == catEnd) {
2213 Token const prev = p.prev_token();
2215 if (p.next_token().character() == '`' ||
2216 (prev.character() == '-' &&
2217 p.next_token().character() == '-'))
2218 ; // ignore it in {}`` or -{}-
2220 handle_ert(os, "{}", context);
2221 } else if (next.cat() == catEscape &&
2222 is_known(next.cs(), known_quotes) &&
2223 end.cat() == catEnd) {
2224 // Something like {\textquoteright} (e.g.
2225 // from writer2latex). LyX writes
2226 // \textquoteright{}, so we may skip the
2227 // braces here for better readability.
2228 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2231 context.check_layout(os);
2232 // special handling of font attribute changes
2233 Token const prev = p.prev_token();
2234 TeXFont const oldFont = context.font;
2235 if (next.character() == '[' ||
2236 next.character() == ']' ||
2237 next.character() == '*') {
2239 if (p.next_token().cat() == catEnd) {
2244 handle_ert(os, "{", context);
2245 parse_text_snippet(p, os,
2248 handle_ert(os, "}", context);
2250 } else if (! context.new_layout_allowed) {
2251 handle_ert(os, "{", context);
2252 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2254 handle_ert(os, "}", context);
2255 } else if (is_known(next.cs(), known_sizes)) {
2256 // next will change the size, so we must
2258 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2260 if (!context.atParagraphStart())
2262 << context.font.size << "\n";
2263 } else if (is_known(next.cs(), known_font_families)) {
2264 // next will change the font family, so we
2265 // must reset it here
2266 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2268 if (!context.atParagraphStart())
2270 << context.font.family << "\n";
2271 } else if (is_known(next.cs(), known_font_series)) {
2272 // next will change the font series, so we
2273 // must reset it here
2274 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2276 if (!context.atParagraphStart())
2278 << context.font.series << "\n";
2279 } else if (is_known(next.cs(), known_font_shapes)) {
2280 // next will change the font shape, so we
2281 // must reset it here
2282 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2284 if (!context.atParagraphStart())
2286 << context.font.shape << "\n";
2287 } else if (is_known(next.cs(), known_old_font_families) ||
2288 is_known(next.cs(), known_old_font_series) ||
2289 is_known(next.cs(), known_old_font_shapes)) {
2290 // next will change the font family, series
2291 // and shape, so we must reset it here
2292 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2294 if (!context.atParagraphStart())
2296 << context.font.family
2298 << context.font.series
2300 << context.font.shape << "\n";
2302 handle_ert(os, "{", context);
2303 parse_text_snippet(p, os, FLAG_BRACE_LAST,
2305 handle_ert(os, "}", context);
2310 else if (t.cat() == catEnd) {
2311 if (flags & FLAG_BRACE_LAST) {
2314 cerr << "stray '}' in text\n";
2315 handle_ert(os, "}", context);
2318 else if (t.cat() == catComment)
2319 parse_comment(p, os, t, context);
2322 // control sequences
2325 else if (t.cs() == "(") {
2326 context.check_layout(os);
2327 begin_inset(os, "Formula");
2329 parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
2334 else if (t.cs() == "[") {
2335 context.check_layout(os);
2336 begin_inset(os, "Formula");
2338 parse_math(p, os, FLAG_EQUATION, MATH_MODE);
2341 // Prevent the conversion of a line break to a space
2342 // (bug 7668). This does not change the output, but
2343 // looks ugly in LyX.
2344 eat_whitespace(p, os, context, false);
2347 else if (t.cs() == "begin")
2348 parse_environment(p, os, outer, last_env,
2351 else if (t.cs() == "end") {
2352 if (flags & FLAG_END) {
2353 // eat environment name
2354 string const name = p.getArg('{', '}');
2355 if (name != active_environment())
2356 cerr << "\\end{" + name + "} does not match \\begin{"
2357 + active_environment() + "}\n";
2360 p.error("found 'end' unexpectedly");
2363 else if (t.cs() == "item") {
2365 bool const optarg = p.hasOpt();
2367 // FIXME: This swallows comments, but we cannot use
2368 // eat_whitespace() since we must not output
2369 // anything before the item.
2370 p.skip_spaces(true);
2371 s = p.verbatimOption();
2373 p.skip_spaces(false);
2375 context.check_layout(os);
2376 if (context.has_item) {
2377 // An item in an unknown list-like environment
2378 // FIXME: Do this in check_layout()!
2379 context.has_item = false;
2381 handle_ert(os, "\\item", context);
2383 handle_ert(os, "\\item ", context);
2386 if (context.layout->labeltype != LABEL_MANUAL) {
2387 // LyX does not support \item[\mybullet]
2388 // in itemize environments
2390 os << parse_text_snippet(p2,
2391 FLAG_BRACK_LAST, outer, context);
2392 } else if (!s.empty()) {
2393 // LyX adds braces around the argument,
2394 // so we need to remove them here.
2395 if (s.size() > 2 && s[0] == '{' &&
2396 s[s.size()-1] == '}')
2397 s = s.substr(1, s.size()-2);
2398 // If the argument contains a space we
2399 // must put it into ERT: Otherwise LyX
2400 // would misinterpret the space as
2401 // item delimiter (bug 7663)
2402 if (contains(s, ' ')) {
2403 handle_ert(os, s, context);
2406 os << parse_text_snippet(p2,
2410 // The space is needed to separate the
2411 // item from the rest of the sentence.
2413 eat_whitespace(p, os, context, false);
2418 else if (t.cs() == "bibitem") {
2420 context.check_layout(os);
2421 eat_whitespace(p, os, context, false);
2422 string label = convert_command_inset_arg(p.verbatimOption());
2423 string key = convert_command_inset_arg(p.verbatim_item());
2424 if (contains(label, '\\') || contains(key, '\\')) {
2425 // LyX can't handle LaTeX commands in labels or keys
2426 handle_ert(os, t.asInput() + '[' + label +
2427 "]{" + p.verbatim_item() + '}',
2430 begin_command_inset(os, "bibitem", "bibitem");
2431 os << "label \"" << label << "\"\n"
2432 "key \"" << key << "\"\n";
2437 else if (is_macro(p)) {
2438 // catch the case of \def\inputGnumericTable
2440 if (t.cs() == "def") {
2441 Token second = p.next_token();
2442 if (second.cs() == "inputGnumericTable") {
2446 Token third = p.get_token();
2448 if (third.cs() == "input") {
2452 string name = normalize_filename(p.verbatim_item());
2453 string const path = getMasterFilePath();
2454 // We want to preserve relative / absolute filenames,
2455 // therefore path is only used for testing
2456 // The file extension is in every case ".tex".
2457 // So we need to remove this extension and check for
2458 // the original one.
2459 name = removeExtension(name);
2460 if (!makeAbsPath(name, path).exists()) {
2461 char const * const Gnumeric_formats[] = {"gnumeric",
2463 string const Gnumeric_name =
2464 find_file(name, path, Gnumeric_formats);
2465 if (!Gnumeric_name.empty())
2466 name = Gnumeric_name;
2468 if (makeAbsPath(name, path).exists())
2469 fix_relative_filename(name);
2471 cerr << "Warning: Could not find file '"
2472 << name << "'." << endl;
2473 context.check_layout(os);
2474 begin_inset(os, "External\n\ttemplate ");
2475 os << "GnumericSpreadsheet\n\tfilename "
2478 context.check_layout(os);
2480 // register the packages that are automatically reloaded
2481 // by the Gnumeric template
2482 registerExternalTemplatePackages("GnumericSpreadsheet");
2487 parse_macro(p, os, context);
2490 else if (t.cs() == "noindent") {
2492 context.add_par_extra_stuff("\\noindent\n");
2495 else if (t.cs() == "appendix") {
2496 context.add_par_extra_stuff("\\start_of_appendix\n");
2497 // We need to start a new paragraph. Otherwise the
2498 // appendix in 'bla\appendix\chapter{' would start
2500 context.new_paragraph(os);
2501 // We need to make sure that the paragraph is
2502 // generated even if it is empty. Otherwise the
2503 // appendix in '\par\appendix\par\chapter{' would
2505 context.check_layout(os);
2506 // FIXME: This is a hack to prevent paragraph
2507 // deletion if it is empty. Handle this better!
2509 "%dummy comment inserted by tex2lyx to "
2510 "ensure that this paragraph is not empty",
2512 // Both measures above may generate an additional
2513 // empty paragraph, but that does not hurt, because
2514 // whitespace does not matter here.
2515 eat_whitespace(p, os, context, true);
2518 // Must catch empty dates before findLayout is called below
2519 else if (t.cs() == "date") {
2520 eat_whitespace(p, os, context, false);
2522 string const date = p.verbatim_item();
2525 preamble.suppressDate(true);
2528 preamble.suppressDate(false);
2529 if (context.new_layout_allowed &&
2530 (newlayout = findLayout(context.textclass,
2533 output_command_layout(os, p, outer,
2534 context, newlayout);
2535 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2536 if (!preamble.titleLayoutFound())
2537 preamble.titleLayoutFound(newlayout->intitle);
2538 set<string> const & req = newlayout->requires();
2539 set<string>::const_iterator it = req.begin();
2540 set<string>::const_iterator en = req.end();
2541 for (; it != en; ++it)
2542 preamble.registerAutomaticallyLoadedPackage(*it);
2545 "\\date{" + p.verbatim_item() + '}',
2550 // Starred section headings
2551 // Must attempt to parse "Section*" before "Section".
2552 else if ((p.next_token().asInput() == "*") &&
2553 context.new_layout_allowed &&
2554 (newlayout = findLayout(context.textclass, t.cs() + '*', true))) {
2557 output_command_layout(os, p, outer, context, newlayout);
2559 if (!preamble.titleLayoutFound())
2560 preamble.titleLayoutFound(newlayout->intitle);
2561 set<string> const & req = newlayout->requires();
2562 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
2563 preamble.registerAutomaticallyLoadedPackage(*it);
2566 // Section headings and the like
2567 else if (context.new_layout_allowed &&
2568 (newlayout = findLayout(context.textclass, t.cs(), true))) {
2570 output_command_layout(os, p, outer, context, newlayout);
2572 if (!preamble.titleLayoutFound())
2573 preamble.titleLayoutFound(newlayout->intitle);
2574 set<string> const & req = newlayout->requires();
2575 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
2576 preamble.registerAutomaticallyLoadedPackage(*it);
2579 else if (t.cs() == "caption") {
2581 context.check_layout(os);
2583 begin_inset(os, "Caption\n");
2584 Context newcontext(true, context.textclass);
2585 newcontext.font = context.font;
2586 newcontext.check_layout(os);
2587 if (p.next_token().cat() != catEscape &&
2588 p.next_token().character() == '[') {
2589 p.get_token(); // eat '['
2590 begin_inset(os, "Argument\n");
2591 os << "status collapsed\n";
2592 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
2594 eat_whitespace(p, os, context, false);
2596 parse_text(p, os, FLAG_ITEM, outer, context);
2597 context.check_end_layout(os);
2598 // We don't need really a new paragraph, but
2599 // we must make sure that the next item gets a \begin_layout.
2600 context.new_paragraph(os);
2603 newcontext.check_end_layout(os);
2606 else if (t.cs() == "subfloat") {
2607 // the syntax is \subfloat[caption]{content}
2608 // if it is a table of figure depends on the surrounding float
2609 bool has_caption = false;
2611 // do nothing if there is no outer float
2612 if (!float_type.empty()) {
2613 context.check_layout(os);
2615 begin_inset(os, "Float " + float_type + "\n");
2617 << "\nsideways false"
2618 << "\nstatus collapsed\n\n";
2621 if (p.next_token().cat() != catEscape &&
2622 p.next_token().character() == '[') {
2623 p.get_token(); // eat '['
2624 caption = parse_text_snippet(p, FLAG_BRACK_LAST, outer, context);
2628 parse_text_in_inset(p, os, FLAG_ITEM, outer, context);
2629 // the caption comes always as the last
2631 // we must make sure that the caption gets a \begin_layout
2632 os << "\n\\begin_layout Plain Layout";
2634 begin_inset(os, "Caption\n");
2635 Context newcontext(true, context.textclass);
2636 newcontext.font = context.font;
2637 newcontext.check_layout(os);
2638 os << caption << "\n";
2639 newcontext.check_end_layout(os);
2640 // We don't need really a new paragraph, but
2641 // we must make sure that the next item gets a \begin_layout.
2642 //newcontext.new_paragraph(os);
2646 // We don't need really a new paragraph, but
2647 // we must make sure that the next item gets a \begin_layout.
2649 context.new_paragraph(os);
2652 context.check_end_layout(os);
2653 // close the layout we opened
2655 os << "\n\\end_layout\n";
2657 // if the float type is not supported or there is no surrounding float
2660 string opt_arg = convert_command_inset_arg(p.getArg('[', ']'));
2661 handle_ert(os, t.asInput() + '[' + opt_arg +
2662 "]{" + p.verbatim_item() + '}', context);
2664 handle_ert(os, t.asInput() + "{" + p.verbatim_item() + '}', context);
2668 else if (t.cs() == "includegraphics") {
2669 bool const clip = p.next_token().asInput() == "*";
2672 string const arg = p.getArg('[', ']');
2673 map<string, string> opts;
2674 vector<string> keys;
2675 split_map(arg, opts, keys);
2677 opts["clip"] = string();
2678 string name = normalize_filename(p.verbatim_item());
2680 string const path = getMasterFilePath();
2681 // We want to preserve relative / absolute filenames,
2682 // therefore path is only used for testing
2683 if (!makeAbsPath(name, path).exists()) {
2684 // The file extension is probably missing.
2685 // Now try to find it out.
2686 string const dvips_name =
2687 find_file(name, path,
2688 known_dvips_graphics_formats);
2689 string const pdftex_name =
2690 find_file(name, path,
2691 known_pdftex_graphics_formats);
2692 if (!dvips_name.empty()) {
2693 if (!pdftex_name.empty()) {
2694 cerr << "This file contains the "
2696 "\"\\includegraphics{"
2698 "However, files\n\""
2699 << dvips_name << "\" and\n\""
2700 << pdftex_name << "\"\n"
2701 "both exist, so I had to make a "
2702 "choice and took the first one.\n"
2703 "Please move the unwanted one "
2704 "someplace else and try again\n"
2705 "if my choice was wrong."
2709 } else if (!pdftex_name.empty()) {
2715 if (makeAbsPath(name, path).exists())
2716 fix_relative_filename(name);
2718 cerr << "Warning: Could not find graphics file '"
2719 << name << "'." << endl;
2721 context.check_layout(os);
2722 begin_inset(os, "Graphics ");
2723 os << "\n\tfilename " << name << '\n';
2724 if (opts.find("width") != opts.end())
2726 << translate_len(opts["width"]) << '\n';
2727 if (opts.find("height") != opts.end())
2729 << translate_len(opts["height"]) << '\n';
2730 if (opts.find("scale") != opts.end()) {
2731 istringstream iss(opts["scale"]);
2735 os << "\tscale " << val << '\n';
2737 if (opts.find("angle") != opts.end()) {
2738 os << "\trotateAngle "
2739 << opts["angle"] << '\n';
2740 vector<string>::const_iterator a =
2741 find(keys.begin(), keys.end(), "angle");
2742 vector<string>::const_iterator s =
2743 find(keys.begin(), keys.end(), "width");
2744 if (s == keys.end())
2745 s = find(keys.begin(), keys.end(), "height");
2746 if (s == keys.end())
2747 s = find(keys.begin(), keys.end(), "scale");
2748 if (s != keys.end() && distance(s, a) > 0)
2749 os << "\tscaleBeforeRotation\n";
2751 if (opts.find("origin") != opts.end()) {
2753 string const opt = opts["origin"];
2754 if (opt.find('l') != string::npos) ss << "left";
2755 if (opt.find('r') != string::npos) ss << "right";
2756 if (opt.find('c') != string::npos) ss << "center";
2757 if (opt.find('t') != string::npos) ss << "Top";
2758 if (opt.find('b') != string::npos) ss << "Bottom";
2759 if (opt.find('B') != string::npos) ss << "Baseline";
2760 if (!ss.str().empty())
2761 os << "\trotateOrigin " << ss.str() << '\n';
2763 cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
2765 if (opts.find("keepaspectratio") != opts.end())
2766 os << "\tkeepAspectRatio\n";
2767 if (opts.find("clip") != opts.end())
2769 if (opts.find("draft") != opts.end())
2771 if (opts.find("bb") != opts.end())
2772 os << "\tBoundingBox "
2773 << opts["bb"] << '\n';
2774 int numberOfbbOptions = 0;
2775 if (opts.find("bbllx") != opts.end())
2776 numberOfbbOptions++;
2777 if (opts.find("bblly") != opts.end())
2778 numberOfbbOptions++;
2779 if (opts.find("bburx") != opts.end())
2780 numberOfbbOptions++;
2781 if (opts.find("bbury") != opts.end())
2782 numberOfbbOptions++;
2783 if (numberOfbbOptions == 4)
2784 os << "\tBoundingBox "
2785 << opts["bbllx"] << " " << opts["bblly"] << " "
2786 << opts["bburx"] << " " << opts["bbury"] << '\n';
2787 else if (numberOfbbOptions > 0)
2788 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
2789 numberOfbbOptions = 0;
2790 if (opts.find("natwidth") != opts.end())
2791 numberOfbbOptions++;
2792 if (opts.find("natheight") != opts.end())
2793 numberOfbbOptions++;
2794 if (numberOfbbOptions == 2)
2795 os << "\tBoundingBox 0bp 0bp "
2796 << opts["natwidth"] << " " << opts["natheight"] << '\n';
2797 else if (numberOfbbOptions > 0)
2798 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
2799 ostringstream special;
2800 if (opts.find("hiresbb") != opts.end())
2801 special << "hiresbb,";
2802 if (opts.find("trim") != opts.end())
2804 if (opts.find("viewport") != opts.end())
2805 special << "viewport=" << opts["viewport"] << ',';
2806 if (opts.find("totalheight") != opts.end())
2807 special << "totalheight=" << opts["totalheight"] << ',';
2808 if (opts.find("type") != opts.end())
2809 special << "type=" << opts["type"] << ',';
2810 if (opts.find("ext") != opts.end())
2811 special << "ext=" << opts["ext"] << ',';
2812 if (opts.find("read") != opts.end())
2813 special << "read=" << opts["read"] << ',';
2814 if (opts.find("command") != opts.end())
2815 special << "command=" << opts["command"] << ',';
2816 string s_special = special.str();
2817 if (!s_special.empty()) {
2818 // We had special arguments. Remove the trailing ','.
2819 os << "\tspecial " << s_special.substr(0, s_special.size() - 1) << '\n';
2821 // TODO: Handle the unknown settings better.
2822 // Warn about invalid options.
2823 // Check whether some option was given twice.
2825 preamble.registerAutomaticallyLoadedPackage("graphicx");
2828 else if (t.cs() == "footnote" ||
2829 (t.cs() == "thanks" && context.layout->intitle)) {
2831 context.check_layout(os);
2832 begin_inset(os, "Foot\n");
2833 os << "status collapsed\n\n";
2834 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
2838 else if (t.cs() == "marginpar") {
2840 context.check_layout(os);
2841 begin_inset(os, "Marginal\n");
2842 os << "status collapsed\n\n";
2843 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
2847 else if (t.cs() == "lstinline") {
2849 parse_listings(p, os, context, true);
2852 else if (t.cs() == "ensuremath") {
2854 context.check_layout(os);
2855 string const s = p.verbatim_item();
2856 //FIXME: this never triggers in UTF8
2857 if (s == "\xb1" || s == "\xb3" || s == "\xb2" || s == "\xb5")
2860 handle_ert(os, "\\ensuremath{" + s + "}",
2864 else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
2865 if (preamble.titleLayoutFound()) {
2867 skip_spaces_braces(p);
2869 handle_ert(os, t.asInput(), context);
2872 else if (t.cs() == "tableofcontents" || t.cs() == "lstlistoflistings") {
2873 context.check_layout(os);
2874 begin_command_inset(os, "toc", t.cs());
2876 skip_spaces_braces(p);
2877 if (t.cs() == "lstlistoflistings")
2878 preamble.registerAutomaticallyLoadedPackage("listings");
2881 else if (t.cs() == "listoffigures") {
2882 context.check_layout(os);
2883 begin_inset(os, "FloatList figure\n");
2885 skip_spaces_braces(p);
2888 else if (t.cs() == "listoftables") {
2889 context.check_layout(os);
2890 begin_inset(os, "FloatList table\n");
2892 skip_spaces_braces(p);
2895 else if (t.cs() == "listof") {
2896 p.skip_spaces(true);
2897 string const name = p.get_token().cs();
2898 if (context.textclass.floats().typeExist(name)) {
2899 context.check_layout(os);
2900 begin_inset(os, "FloatList ");
2903 p.get_token(); // swallow second arg
2905 handle_ert(os, "\\listof{" + name + "}", context);
2908 else if ((where = is_known(t.cs(), known_text_font_families)))
2909 parse_text_attributes(p, os, FLAG_ITEM, outer,
2910 context, "\\family", context.font.family,
2911 known_coded_font_families[where - known_text_font_families]);
2913 else if ((where = is_known(t.cs(), known_text_font_series)))
2914 parse_text_attributes(p, os, FLAG_ITEM, outer,
2915 context, "\\series", context.font.series,
2916 known_coded_font_series[where - known_text_font_series]);
2918 else if ((where = is_known(t.cs(), known_text_font_shapes)))
2919 parse_text_attributes(p, os, FLAG_ITEM, outer,
2920 context, "\\shape", context.font.shape,
2921 known_coded_font_shapes[where - known_text_font_shapes]);
2923 else if (t.cs() == "textnormal" || t.cs() == "normalfont") {
2924 context.check_layout(os);
2925 TeXFont oldFont = context.font;
2926 context.font.init();
2927 context.font.size = oldFont.size;
2928 os << "\n\\family " << context.font.family << "\n";
2929 os << "\n\\series " << context.font.series << "\n";
2930 os << "\n\\shape " << context.font.shape << "\n";
2931 if (t.cs() == "textnormal") {
2932 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2933 output_font_change(os, context.font, oldFont);
2934 context.font = oldFont;
2936 eat_whitespace(p, os, context, false);
2939 else if (t.cs() == "textcolor") {
2940 // scheme is \textcolor{color name}{text}
2941 string const color = p.verbatim_item();
2942 // we only support the predefined colors of the color package
2943 if (color == "black" || color == "blue" || color == "cyan"
2944 || color == "green" || color == "magenta" || color == "red"
2945 || color == "white" || color == "yellow") {
2946 context.check_layout(os);
2947 os << "\n\\color " << color << "\n";
2948 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2949 context.check_layout(os);
2950 os << "\n\\color inherit\n";
2951 preamble.registerAutomaticallyLoadedPackage("color");
2953 // for custom defined colors
2954 handle_ert(os, t.asInput() + "{" + color + "}", context);
2957 else if (t.cs() == "underbar" || t.cs() == "uline") {
2958 // \underbar is not 100% correct (LyX outputs \uline
2959 // of ulem.sty). The difference is that \ulem allows
2960 // line breaks, and \underbar does not.
2961 // Do NOT handle \underline.
2962 // \underbar cuts through y, g, q, p etc.,
2963 // \underline does not.
2964 context.check_layout(os);
2965 os << "\n\\bar under\n";
2966 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2967 context.check_layout(os);
2968 os << "\n\\bar default\n";
2969 preamble.registerAutomaticallyLoadedPackage("ulem");
2972 else if (t.cs() == "sout") {
2973 context.check_layout(os);
2974 os << "\n\\strikeout on\n";
2975 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2976 context.check_layout(os);
2977 os << "\n\\strikeout default\n";
2978 preamble.registerAutomaticallyLoadedPackage("ulem");
2981 else if (t.cs() == "uuline" || t.cs() == "uwave" ||
2982 t.cs() == "emph" || t.cs() == "noun") {
2983 context.check_layout(os);
2984 os << "\n\\" << t.cs() << " on\n";
2985 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
2986 context.check_layout(os);
2987 os << "\n\\" << t.cs() << " default\n";
2988 if (t.cs() == "uuline" || t.cs() == "uwave")
2989 preamble.registerAutomaticallyLoadedPackage("ulem");
2992 else if (t.cs() == "lyxadded" || t.cs() == "lyxdeleted") {
2993 context.check_layout(os);
2994 string name = p.getArg('{', '}');
2995 string localtime = p.getArg('{', '}');
2996 preamble.registerAuthor(name);
2997 Author const & author = preamble.getAuthor(name);
2998 // from_ctime() will fail if LyX decides to output the
2999 // time in the text language. It might also use a wrong
3000 // time zone (if the original LyX document was exported
3001 // with a different time zone).
3002 time_t ptime = from_ctime(localtime);
3003 if (ptime == static_cast<time_t>(-1)) {
3004 cerr << "Warning: Could not parse time `" << localtime
3005 << "´ for change tracking, using current time instead.\n";
3006 ptime = current_time();
3008 if (t.cs() == "lyxadded")
3009 os << "\n\\change_inserted ";
3011 os << "\n\\change_deleted ";
3012 os << author.bufferId() << ' ' << ptime << '\n';
3013 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
3014 bool dvipost = LaTeXPackages::isAvailable("dvipost");
3015 bool xcolorulem = LaTeXPackages::isAvailable("ulem") &&
3016 LaTeXPackages::isAvailable("xcolor");
3017 // No need to test for luatex, since luatex comes in
3018 // two flavours (dvi and pdf), like latex, and those
3019 // are detected by pdflatex.
3020 if (pdflatex || xetex) {
3022 preamble.registerAutomaticallyLoadedPackage("ulem");
3023 preamble.registerAutomaticallyLoadedPackage("xcolor");
3024 preamble.registerAutomaticallyLoadedPackage("pdfcolmk");
3028 preamble.registerAutomaticallyLoadedPackage("dvipost");
3029 } else if (xcolorulem) {
3030 preamble.registerAutomaticallyLoadedPackage("ulem");
3031 preamble.registerAutomaticallyLoadedPackage("xcolor");
3036 else if (t.cs() == "phantom" || t.cs() == "hphantom" ||
3037 t.cs() == "vphantom") {
3038 context.check_layout(os);
3039 if (t.cs() == "phantom")
3040 begin_inset(os, "Phantom Phantom\n");
3041 if (t.cs() == "hphantom")
3042 begin_inset(os, "Phantom HPhantom\n");
3043 if (t.cs() == "vphantom")
3044 begin_inset(os, "Phantom VPhantom\n");
3045 os << "status open\n";
3046 parse_text_in_inset(p, os, FLAG_ITEM, outer, context,
3051 else if (t.cs() == "href") {
3052 context.check_layout(os);
3053 string target = p.getArg('{', '}');
3054 string name = p.getArg('{', '}');
3056 size_t i = target.find(':');
3057 if (i != string::npos) {
3058 type = target.substr(0, i + 1);
3059 if (type == "mailto:" || type == "file:")
3060 target = target.substr(i + 1);
3061 // handle the case that name is equal to target, except of "http://"
3062 else if (target.substr(i + 3) == name && type == "http:")
3065 begin_command_inset(os, "href", "href");
3067 os << "name \"" << name << "\"\n";
3068 os << "target \"" << target << "\"\n";
3069 if (type == "mailto:" || type == "file:")
3070 os << "type \"" << type << "\"\n";
3072 skip_spaces_braces(p);
3075 else if (t.cs() == "lyxline") {
3076 // swallow size argument (it is not used anyway)
3078 if (!context.atParagraphStart()) {
3079 // so our line is in the middle of a paragraph
3080 // we need to add a new line, lest this line
3081 // follow the other content on that line and
3082 // run off the side of the page
3083 // FIXME: This may create an empty paragraph,
3084 // but without that it would not be
3085 // possible to set noindent below.
3086 // Fortunately LaTeX does not care
3087 // about the empty paragraph.
3088 context.new_paragraph(os);
3090 if (preamble.indentParagraphs()) {
3091 // we need to unindent, lest the line be too long
3092 context.add_par_extra_stuff("\\noindent\n");
3094 context.check_layout(os);
3095 begin_command_inset(os, "line", "rule");
3096 os << "offset \"0.5ex\"\n"
3097 "width \"100line%\"\n"
3102 else if (t.cs() == "rule") {
3103 string const offset = (p.hasOpt() ? p.getArg('[', ']') : string());
3104 string const width = p.getArg('{', '}');
3105 string const thickness = p.getArg('{', '}');
3106 context.check_layout(os);
3107 begin_command_inset(os, "line", "rule");
3108 if (!offset.empty())
3109 os << "offset \"" << translate_len(offset) << "\"\n";
3110 os << "width \"" << translate_len(width) << "\"\n"
3111 "height \"" << translate_len(thickness) << "\"\n";
3115 else if (is_known(t.cs(), known_phrases) ||
3116 (t.cs() == "protect" &&
3117 p.next_token().cat() == catEscape &&
3118 is_known(p.next_token().cs(), known_phrases))) {
3119 // LyX sometimes puts a \protect in front, so we have to ignore it
3120 // FIXME: This needs to be changed when bug 4752 is fixed.
3122 t.cs() == "protect" ? p.get_token().cs() : t.cs(),
3124 context.check_layout(os);
3125 os << known_coded_phrases[where - known_phrases];
3126 skip_spaces_braces(p);
3129 else if ((where = is_known(t.cs(), known_ref_commands))) {
3130 string const opt = p.getOpt();
3132 context.check_layout(os);
3133 begin_command_inset(os, "ref",
3134 known_coded_ref_commands[where - known_ref_commands]);
3135 os << "reference \""
3136 << convert_command_inset_arg(p.verbatim_item())
3139 if (t.cs() == "vref" || t.cs() == "vpageref")
3140 preamble.registerAutomaticallyLoadedPackage("varioref");
3143 // LyX does not support optional arguments of ref commands
3144 handle_ert(os, t.asInput() + '[' + opt + "]{" +
3145 p.verbatim_item() + "}", context);
3149 else if (use_natbib &&
3150 is_known(t.cs(), known_natbib_commands) &&
3151 ((t.cs() != "citefullauthor" &&
3152 t.cs() != "citeyear" &&
3153 t.cs() != "citeyearpar") ||
3154 p.next_token().asInput() != "*")) {
3155 context.check_layout(os);
3156 string command = t.cs();
3157 if (p.next_token().asInput() == "*") {
3161 if (command == "citefullauthor")
3162 // alternative name for "\\citeauthor*"
3163 command = "citeauthor*";
3165 // text before the citation
3167 // text after the citation
3169 get_cite_arguments(p, true, before, after);
3171 if (command == "cite") {
3172 // \cite without optional argument means
3173 // \citet, \cite with at least one optional
3174 // argument means \citep.
3175 if (before.empty() && after.empty())
3180 if (before.empty() && after == "[]")
3181 // avoid \citet[]{a}
3183 else if (before == "[]" && after == "[]") {
3184 // avoid \citet[][]{a}
3188 // remove the brackets around after and before
3189 if (!after.empty()) {
3191 after.erase(after.length() - 1, 1);
3192 after = convert_command_inset_arg(after);
3194 if (!before.empty()) {
3196 before.erase(before.length() - 1, 1);
3197 before = convert_command_inset_arg(before);
3199 begin_command_inset(os, "citation", command);
3200 os << "after " << '"' << after << '"' << "\n";
3201 os << "before " << '"' << before << '"' << "\n";
3203 << convert_command_inset_arg(p.verbatim_item())
3208 else if (use_jurabib &&
3209 is_known(t.cs(), known_jurabib_commands) &&
3210 (t.cs() == "cite" || p.next_token().asInput() != "*")) {
3211 context.check_layout(os);
3212 string command = t.cs();
3213 if (p.next_token().asInput() == "*") {
3217 char argumentOrder = '\0';
3218 vector<string> const options =
3219 preamble.getPackageOptions("jurabib");
3220 if (find(options.begin(), options.end(),
3221 "natbiborder") != options.end())
3222 argumentOrder = 'n';
3223 else if (find(options.begin(), options.end(),
3224 "jurabiborder") != options.end())
3225 argumentOrder = 'j';
3227 // text before the citation
3229 // text after the citation
3231 get_cite_arguments(p, argumentOrder != 'j', before, after);
3233 string const citation = p.verbatim_item();
3234 if (!before.empty() && argumentOrder == '\0') {
3235 cerr << "Warning: Assuming argument order "
3236 "of jurabib version 0.6 for\n'"
3237 << command << before << after << '{'
3238 << citation << "}'.\n"
3239 "Add 'jurabiborder' to the jurabib "
3240 "package options if you used an\n"
3241 "earlier jurabib version." << endl;
3243 if (!after.empty()) {
3245 after.erase(after.length() - 1, 1);
3247 if (!before.empty()) {
3249 before.erase(before.length() - 1, 1);
3251 begin_command_inset(os, "citation", command);
3252 os << "after " << '"' << after << '"' << "\n";
3253 os << "before " << '"' << before << '"' << "\n";
3254 os << "key " << '"' << citation << '"' << "\n";
3258 else if (t.cs() == "cite"
3259 || t.cs() == "nocite") {
3260 context.check_layout(os);
3261 string after = convert_command_inset_arg(p.getArg('[', ']'));
3262 string key = convert_command_inset_arg(p.verbatim_item());
3263 // store the case that it is "\nocite{*}" to use it later for
3266 begin_command_inset(os, "citation", t.cs());
3267 os << "after " << '"' << after << '"' << "\n";
3268 os << "key " << '"' << key << '"' << "\n";
3270 } else if (t.cs() == "nocite")
3274 else if (t.cs() == "index" ||
3275 (t.cs() == "sindex" && preamble.use_indices() == "true")) {
3276 context.check_layout(os);
3277 string const arg = (t.cs() == "sindex" && p.hasOpt()) ?
3278 p.getArg('[', ']') : "";
3279 string const kind = arg.empty() ? "idx" : arg;
3280 begin_inset(os, "Index ");
3281 os << kind << "\nstatus collapsed\n";
3282 parse_text_in_inset(p, os, FLAG_ITEM, false, context, "Index");
3285 preamble.registerAutomaticallyLoadedPackage("splitidx");
3288 else if (t.cs() == "nomenclature") {
3289 context.check_layout(os);
3290 begin_command_inset(os, "nomenclature", "nomenclature");
3291 string prefix = convert_command_inset_arg(p.getArg('[', ']'));
3292 if (!prefix.empty())
3293 os << "prefix " << '"' << prefix << '"' << "\n";
3294 os << "symbol " << '"'
3295 << convert_command_inset_arg(p.verbatim_item());
3296 os << "\"\ndescription \""
3297 << convert_command_inset_arg(p.verbatim_item())
3300 preamble.registerAutomaticallyLoadedPackage("nomencl");
3303 else if (t.cs() == "label") {
3304 context.check_layout(os);
3305 begin_command_inset(os, "label", "label");
3307 << convert_command_inset_arg(p.verbatim_item())
3312 else if (t.cs() == "printindex") {
3313 context.check_layout(os);
3314 begin_command_inset(os, "index_print", "printindex");
3315 os << "type \"idx\"\n";
3317 skip_spaces_braces(p);
3318 preamble.registerAutomaticallyLoadedPackage("makeidx");
3319 if (preamble.use_indices() == "true")
3320 preamble.registerAutomaticallyLoadedPackage("splitidx");
3323 else if (t.cs() == "printnomenclature") {
3325 string width_type = "";
3326 context.check_layout(os);
3327 begin_command_inset(os, "nomencl_print", "printnomenclature");
3328 // case of a custom width
3330 width = p.getArg('[', ']');
3331 width = translate_len(width);
3332 width_type = "custom";
3334 // case of no custom width
3335 // the case of no custom width but the width set
3336 // via \settowidth{\nomlabelwidth}{***} cannot be supported
3337 // because the user could have set anything, not only the width
3338 // of the longest label (which would be width_type = "auto")
3339 string label = convert_command_inset_arg(p.getArg('{', '}'));
3340 if (label.empty() && width_type.empty())
3341 width_type = "none";
3342 os << "set_width \"" << width_type << "\"\n";
3343 if (width_type == "custom")
3344 os << "width \"" << width << '\"';
3346 skip_spaces_braces(p);
3347 preamble.registerAutomaticallyLoadedPackage("nomencl");
3350 else if ((t.cs() == "textsuperscript" || t.cs() == "textsubscript")) {
3351 context.check_layout(os);
3352 begin_inset(os, "script ");
3353 os << t.cs().substr(4) << '\n';
3354 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
3356 if (t.cs() == "textsubscript")
3357 preamble.registerAutomaticallyLoadedPackage("subscript");
3360 else if ((where = is_known(t.cs(), known_quotes))) {
3361 context.check_layout(os);
3362 begin_inset(os, "Quotes ");
3363 os << known_coded_quotes[where - known_quotes];
3365 // LyX adds {} after the quote, so we have to eat
3366 // spaces here if there are any before a possible
3368 eat_whitespace(p, os, context, false);
3372 else if ((where = is_known(t.cs(), known_sizes)) &&
3373 context.new_layout_allowed) {
3374 context.check_layout(os);
3375 TeXFont const oldFont = context.font;
3376 context.font.size = known_coded_sizes[where - known_sizes];
3377 output_font_change(os, oldFont, context.font);
3378 eat_whitespace(p, os, context, false);
3381 else if ((where = is_known(t.cs(), known_font_families)) &&
3382 context.new_layout_allowed) {
3383 context.check_layout(os);
3384 TeXFont const oldFont = context.font;
3385 context.font.family =
3386 known_coded_font_families[where - known_font_families];
3387 output_font_change(os, oldFont, context.font);
3388 eat_whitespace(p, os, context, false);
3391 else if ((where = is_known(t.cs(), known_font_series)) &&
3392 context.new_layout_allowed) {
3393 context.check_layout(os);
3394 TeXFont const oldFont = context.font;
3395 context.font.series =
3396 known_coded_font_series[where - known_font_series];
3397 output_font_change(os, oldFont, context.font);
3398 eat_whitespace(p, os, context, false);
3401 else if ((where = is_known(t.cs(), known_font_shapes)) &&
3402 context.new_layout_allowed) {
3403 context.check_layout(os);
3404 TeXFont const oldFont = context.font;
3405 context.font.shape =
3406 known_coded_font_shapes[where - known_font_shapes];
3407 output_font_change(os, oldFont, context.font);
3408 eat_whitespace(p, os, context, false);
3410 else if ((where = is_known(t.cs(), known_old_font_families)) &&
3411 context.new_layout_allowed) {
3412 context.check_layout(os);
3413 TeXFont const oldFont = context.font;
3414 context.font.init();
3415 context.font.size = oldFont.size;
3416 context.font.family =
3417 known_coded_font_families[where - known_old_font_families];
3418 output_font_change(os, oldFont, context.font);
3419 eat_whitespace(p, os, context, false);
3422 else if ((where = is_known(t.cs(), known_old_font_series)) &&
3423 context.new_layout_allowed) {
3424 context.check_layout(os);
3425 TeXFont const oldFont = context.font;
3426 context.font.init();
3427 context.font.size = oldFont.size;
3428 context.font.series =
3429 known_coded_font_series[where - known_old_font_series];
3430 output_font_change(os, oldFont, context.font);
3431 eat_whitespace(p, os, context, false);
3434 else if ((where = is_known(t.cs(), known_old_font_shapes)) &&
3435 context.new_layout_allowed) {
3436 context.check_layout(os);
3437 TeXFont const oldFont = context.font;
3438 context.font.init();
3439 context.font.size = oldFont.size;
3440 context.font.shape =
3441 known_coded_font_shapes[where - known_old_font_shapes];
3442 output_font_change(os, oldFont, context.font);
3443 eat_whitespace(p, os, context, false);
3446 else if (t.cs() == "selectlanguage") {
3447 context.check_layout(os);
3448 // save the language for the case that a
3449 // \foreignlanguage is used
3450 context.font.language = babel2lyx(p.verbatim_item());
3451 os << "\n\\lang " << context.font.language << "\n";
3454 else if (t.cs() == "foreignlanguage") {
3455 string const lang = babel2lyx(p.verbatim_item());
3456 parse_text_attributes(p, os, FLAG_ITEM, outer,
3458 context.font.language, lang);
3461 else if (t.cs() == "inputencoding") {
3462 // nothing to write here
3463 string const enc = subst(p.verbatim_item(), "\n", " ");
3467 else if ((where = is_known(t.cs(), known_special_chars))) {
3468 context.check_layout(os);
3469 os << "\\SpecialChar \\"
3470 << known_coded_special_chars[where - known_special_chars]
3472 skip_spaces_braces(p);
3475 else if (t.cs() == "nobreakdash" && p.next_token().asInput() == "-") {
3476 context.check_layout(os);
3477 os << "\\SpecialChar \\nobreakdash-\n";
3481 else if (t.cs() == "textquotedbl") {
3482 context.check_layout(os);
3487 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
3488 context.check_layout(os);
3489 os << "\\SpecialChar \\@.\n";
3493 else if (t.cs() == "-") {
3494 context.check_layout(os);
3495 os << "\\SpecialChar \\-\n";
3498 else if (t.cs() == "textasciitilde") {
3499 context.check_layout(os);
3501 skip_spaces_braces(p);
3504 else if (t.cs() == "textasciicircum") {
3505 context.check_layout(os);
3507 skip_spaces_braces(p);
3510 else if (t.cs() == "textbackslash") {
3511 context.check_layout(os);
3512 os << "\n\\backslash\n";
3513 skip_spaces_braces(p);
3516 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
3517 || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
3519 context.check_layout(os);
3523 else if (t.cs() == "char") {
3524 context.check_layout(os);
3525 if (p.next_token().character() == '`') {
3527 if (p.next_token().cs() == "\"") {
3532 handle_ert(os, "\\char`", context);
3535 handle_ert(os, "\\char", context);
3539 else if (t.cs() == "verb") {
3540 context.check_layout(os);
3541 char const delimiter = p.next_token().character();
3542 string const arg = p.getArg(delimiter, delimiter);
3544 oss << "\\verb" << delimiter << arg << delimiter;
3545 handle_ert(os, oss.str(), context);
3548 // Problem: \= creates a tabstop inside the tabbing environment
3549 // and else an accent. In the latter case we really would want
3550 // \={o} instead of \= o.
3551 else if (t.cs() == "=" && (flags & FLAG_TABBING))
3552 handle_ert(os, t.asInput(), context);
3554 // accents (see Table 6 in Comprehensive LaTeX Symbol List)
3555 else if (t.cs().size() == 1
3556 && contains("\"'.=^`bcdHkrtuv~", t.cs())) {
3557 context.check_layout(os);
3558 // try to see whether the string is in unicodesymbols
3561 string command = t.asInput() + "{"
3562 + trimSpaceAndEol(p.verbatim_item())
3565 docstring s = encodings.fromLaTeXCommand(from_utf8(command),
3566 Encodings::TEXT_CMD | Encodings::MATH_CMD,
3567 termination, rem, &req);
3570 cerr << "When parsing " << command
3571 << ", result is " << to_utf8(s)
3572 << "+" << to_utf8(rem) << endl;
3574 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
3575 preamble.registerAutomaticallyLoadedPackage(*it);
3577 // we did not find a non-ert version
3578 handle_ert(os, command, context);
3581 else if (t.cs() == "\\") {
3582 context.check_layout(os);
3584 handle_ert(os, "\\\\" + p.getOpt(), context);
3585 else if (p.next_token().asInput() == "*") {
3587 // getOpt() eats the following space if there
3588 // is no optional argument, but that is OK
3589 // here since it has no effect in the output.
3590 handle_ert(os, "\\\\*" + p.getOpt(), context);
3593 begin_inset(os, "Newline newline");
3598 else if (t.cs() == "newline" ||
3599 (t.cs() == "linebreak" && !p.hasOpt())) {
3600 context.check_layout(os);
3601 begin_inset(os, "Newline ");
3604 skip_spaces_braces(p);
3607 else if (t.cs() == "input" || t.cs() == "include"
3608 || t.cs() == "verbatiminput") {
3609 string name = t.cs();
3610 if (t.cs() == "verbatiminput"
3611 && p.next_token().asInput() == "*")
3612 name += p.get_token().asInput();
3613 context.check_layout(os);
3614 string filename(normalize_filename(p.getArg('{', '}')));
3615 string const path = getMasterFilePath();
3616 // We want to preserve relative / absolute filenames,
3617 // therefore path is only used for testing
3618 if ((t.cs() == "include" || t.cs() == "input") &&
3619 !makeAbsPath(filename, path).exists()) {
3620 // The file extension is probably missing.
3621 // Now try to find it out.
3622 string const tex_name =
3623 find_file(filename, path,
3624 known_tex_extensions);
3625 if (!tex_name.empty())
3626 filename = tex_name;
3628 bool external = false;
3630 if (makeAbsPath(filename, path).exists()) {
3631 string const abstexname =
3632 makeAbsPath(filename, path).absFileName();
3633 string const abslyxname =
3634 changeExtension(abstexname, ".lyx");
3635 string const absfigname =
3636 changeExtension(abstexname, ".fig");
3637 fix_relative_filename(filename);
3638 string const lyxname =
3639 changeExtension(filename, ".lyx");
3641 external = FileName(absfigname).exists();
3642 if (t.cs() == "input") {
3643 string const ext = getExtension(abstexname);
3645 // Combined PS/LaTeX:
3646 // x.eps, x.pstex_t (old xfig)
3647 // x.pstex, x.pstex_t (new xfig, e.g. 3.2.5)
3648 FileName const absepsname(
3649 changeExtension(abstexname, ".eps"));
3650 FileName const abspstexname(
3651 changeExtension(abstexname, ".pstex"));
3652 bool const xfigeps =
3653 (absepsname.exists() ||
3654 abspstexname.exists()) &&
3657 // Combined PDF/LaTeX:
3658 // x.pdf, x.pdftex_t (old xfig)
3659 // x.pdf, x.pdf_t (new xfig, e.g. 3.2.5)
3660 FileName const abspdfname(
3661 changeExtension(abstexname, ".pdf"));
3662 bool const xfigpdf =
3663 abspdfname.exists() &&
3664 (ext == "pdftex_t" || ext == "pdf_t");
3668 // Combined PS/PDF/LaTeX:
3669 // x_pspdftex.eps, x_pspdftex.pdf, x.pspdftex
3670 string const absbase2(
3671 removeExtension(abstexname) + "_pspdftex");
3672 FileName const abseps2name(
3673 addExtension(absbase2, ".eps"));
3674 FileName const abspdf2name(
3675 addExtension(absbase2, ".pdf"));
3676 bool const xfigboth =
3677 abspdf2name.exists() &&
3678 abseps2name.exists() && ext == "pspdftex";
3680 xfig = xfigpdf || xfigeps || xfigboth;
3681 external = external && xfig;
3684 outname = changeExtension(filename, ".fig");
3686 // Don't try to convert, the result
3687 // would be full of ERT.
3689 } else if (t.cs() != "verbatiminput" &&
3690 tex2lyx(abstexname, FileName(abslyxname),
3697 cerr << "Warning: Could not find included file '"
3698 << filename << "'." << endl;
3702 begin_inset(os, "External\n");
3703 os << "\ttemplate XFig\n"
3704 << "\tfilename " << outname << '\n';
3705 registerExternalTemplatePackages("XFig");
3707 begin_command_inset(os, "include", name);
3708 os << "preview false\n"
3709 "filename \"" << outname << "\"\n";
3710 if (t.cs() == "verbatiminput")
3711 preamble.registerAutomaticallyLoadedPackage("verbatim");
3716 else if (t.cs() == "bibliographystyle") {
3717 // store new bibliographystyle
3718 bibliographystyle = p.verbatim_item();
3719 // If any other command than \bibliography and
3720 // \nocite{*} follows, we need to output the style
3721 // (because it might be used by that command).
3722 // Otherwise, it will automatically be output by LyX.
3725 for (Token t2 = p.get_token(); p.good(); t2 = p.get_token()) {
3726 if (t2.cat() == catBegin)
3728 if (t2.cat() != catEscape)
3730 if (t2.cs() == "nocite") {
3731 if (p.getArg('{', '}') == "*")
3733 } else if (t2.cs() == "bibliography")
3740 "\\bibliographystyle{" + bibliographystyle + '}',
3745 else if (t.cs() == "bibliography") {
3746 context.check_layout(os);
3747 begin_command_inset(os, "bibtex", "bibtex");
3748 if (!btprint.empty()) {
3749 os << "btprint " << '"' << "btPrintAll" << '"' << "\n";
3750 // clear the string because the next BibTeX inset can be without the
3751 // \nocite{*} option
3754 os << "bibfiles " << '"' << p.verbatim_item() << '"' << "\n";
3755 // Do we have a bibliographystyle set?
3756 if (!bibliographystyle.empty())
3757 os << "options " << '"' << bibliographystyle << '"' << "\n";
3761 else if (t.cs() == "parbox") {
3762 // Test whether this is an outer box of a shaded box
3764 // swallow arguments
3765 while (p.hasOpt()) {
3767 p.skip_spaces(true);
3770 p.skip_spaces(true);
3772 if (p.next_token().cat() == catBegin)
3774 p.skip_spaces(true);
3775 Token to = p.get_token();
3776 bool shaded = false;
3777 if (to.asInput() == "\\begin") {
3778 p.skip_spaces(true);
3779 if (p.getArg('{', '}') == "shaded")
3784 parse_outer_box(p, os, FLAG_ITEM, outer,
3785 context, "parbox", "shaded");
3787 parse_box(p, os, 0, FLAG_ITEM, outer, context,
3791 else if (t.cs() == "ovalbox" || t.cs() == "Ovalbox" ||
3792 t.cs() == "shadowbox" || t.cs() == "doublebox")
3793 parse_outer_box(p, os, FLAG_ITEM, outer, context, t.cs(), "");
3795 else if (t.cs() == "framebox") {
3796 if (p.next_token().character() == '(') {
3797 //the syntax is: \framebox(x,y)[position]{content}
3798 string arg = t.asInput();
3799 arg += p.getFullParentheseArg();
3800 arg += p.getFullOpt();
3801 eat_whitespace(p, os, context, false);
3802 handle_ert(os, arg + '{', context);
3803 eat_whitespace(p, os, context, false);
3804 parse_text(p, os, FLAG_ITEM, outer, context);
3805 handle_ert(os, "}", context);
3807 string special = p.getFullOpt();
3808 special += p.getOpt();
3809 parse_outer_box(p, os, FLAG_ITEM, outer,
3810 context, t.cs(), special);
3814 //\makebox() is part of the picture environment and different from \makebox{}
3815 //\makebox{} will be parsed by parse_box
3816 else if (t.cs() == "makebox") {
3817 if (p.next_token().character() == '(') {
3818 //the syntax is: \makebox(x,y)[position]{content}
3819 string arg = t.asInput();
3820 arg += p.getFullParentheseArg();
3821 arg += p.getFullOpt();
3822 eat_whitespace(p, os, context, false);
3823 handle_ert(os, arg + '{', context);
3824 eat_whitespace(p, os, context, false);
3825 parse_text(p, os, FLAG_ITEM, outer, context);
3826 handle_ert(os, "}", context);
3828 //the syntax is: \makebox[width][position]{content}
3829 parse_box(p, os, 0, FLAG_ITEM, outer, context,
3833 else if (t.cs() == "smallskip" ||
3834 t.cs() == "medskip" ||
3835 t.cs() == "bigskip" ||
3836 t.cs() == "vfill") {
3837 context.check_layout(os);
3838 begin_inset(os, "VSpace ");
3841 skip_spaces_braces(p);
3844 else if ((where = is_known(t.cs(), known_spaces))) {
3845 context.check_layout(os);
3846 begin_inset(os, "space ");
3847 os << '\\' << known_coded_spaces[where - known_spaces]
3850 // LaTeX swallows whitespace after all spaces except
3851 // "\\,". We have to do that here, too, because LyX
3852 // adds "{}" which would make the spaces significant.
3854 eat_whitespace(p, os, context, false);
3855 // LyX adds "{}" after all spaces except "\\ " and
3856 // "\\,", so we have to remove "{}".
3857 // "\\,{}" is equivalent to "\\," in LaTeX, so we
3858 // remove the braces after "\\,", too.
3863 else if (t.cs() == "newpage" ||
3864 (t.cs() == "pagebreak" && !p.hasOpt()) ||
3865 t.cs() == "clearpage" ||
3866 t.cs() == "cleardoublepage") {
3867 context.check_layout(os);
3868 begin_inset(os, "Newpage ");
3871 skip_spaces_braces(p);
3874 else if (t.cs() == "DeclareRobustCommand" ||
3875 t.cs() == "DeclareRobustCommandx" ||
3876 t.cs() == "newcommand" ||
3877 t.cs() == "newcommandx" ||
3878 t.cs() == "providecommand" ||
3879 t.cs() == "providecommandx" ||
3880 t.cs() == "renewcommand" ||
3881 t.cs() == "renewcommandx") {
3882 // DeclareRobustCommand, DeclareRobustCommandx,
3883 // providecommand and providecommandx could be handled
3884 // by parse_command(), but we need to call
3885 // add_known_command() here.
3886 string name = t.asInput();
3887 if (p.next_token().asInput() == "*") {
3888 // Starred form. Eat '*'
3892 string const command = p.verbatim_item();
3893 string const opt1 = p.getFullOpt();
3894 string const opt2 = p.getFullOpt();
3895 add_known_command(command, opt1, !opt2.empty());
3896 string const ert = name + '{' + command + '}' +
3898 '{' + p.verbatim_item() + '}';
3900 if (t.cs() == "DeclareRobustCommand" ||
3901 t.cs() == "DeclareRobustCommandx" ||
3902 t.cs() == "providecommand" ||
3903 t.cs() == "providecommandx" ||
3904 name[name.length()-1] == '*')
3905 handle_ert(os, ert, context);
3907 context.check_layout(os);
3908 begin_inset(os, "FormulaMacro");
3914 else if (t.cs() == "let" && p.next_token().asInput() != "*") {
3915 // let could be handled by parse_command(),
3916 // but we need to call add_known_command() here.
3917 string ert = t.asInput();
3920 if (p.next_token().cat() == catBegin) {
3921 name = p.verbatim_item();
3922 ert += '{' + name + '}';
3924 name = p.verbatim_item();
3929 if (p.next_token().cat() == catBegin) {
3930 command = p.verbatim_item();
3931 ert += '{' + command + '}';
3933 command = p.verbatim_item();
3936 // If command is known, make name known too, to parse
3937 // its arguments correctly. For this reason we also
3938 // have commands in syntax.default that are hardcoded.
3939 CommandMap::iterator it = known_commands.find(command);
3940 if (it != known_commands.end())
3941 known_commands[t.asInput()] = it->second;
3942 handle_ert(os, ert, context);
3945 else if (t.cs() == "hspace" || t.cs() == "vspace") {
3946 bool starred = false;
3947 if (p.next_token().asInput() == "*") {
3951 string name = t.asInput();
3952 string const length = p.verbatim_item();
3955 bool valid = splitLatexLength(length, valstring, unit);
3956 bool known_hspace = false;
3957 bool known_vspace = false;
3958 bool known_unit = false;
3961 istringstream iss(valstring);
3964 if (t.cs()[0] == 'h') {
3965 if (unit == "\\fill") {
3970 known_hspace = true;
3973 if (unit == "\\smallskipamount") {
3975 known_vspace = true;
3976 } else if (unit == "\\medskipamount") {
3978 known_vspace = true;
3979 } else if (unit == "\\bigskipamount") {
3981 known_vspace = true;
3982 } else if (unit == "\\fill") {
3984 known_vspace = true;
3988 if (!known_hspace && !known_vspace) {
3989 switch (unitFromString(unit)) {
4010 if (t.cs()[0] == 'h' && (known_unit || known_hspace)) {
4011 // Literal horizontal length or known variable
4012 context.check_layout(os);
4013 begin_inset(os, "space ");
4021 if (known_unit && !known_hspace)
4023 << translate_len(length);
4025 } else if (known_unit || known_vspace) {
4026 // Literal vertical length or known variable
4027 context.check_layout(os);
4028 begin_inset(os, "VSpace ");
4036 // LyX can't handle other length variables in Inset VSpace/space
4041 handle_ert(os, name + '{' + unit + '}', context);
4042 else if (value == -1.0)
4043 handle_ert(os, name + "{-" + unit + '}', context);
4045 handle_ert(os, name + '{' + valstring + unit + '}', context);
4047 handle_ert(os, name + '{' + length + '}', context);
4051 // The single '=' is meant here.
4052 else if ((newinsetlayout = findInsetLayout(context.textclass, t.cs(), true))) {
4054 context.check_layout(os);
4055 begin_inset(os, "Flex ");
4056 os << to_utf8(newinsetlayout->name()) << '\n'
4057 << "status collapsed\n";
4058 parse_text_in_inset(p, os, FLAG_ITEM, false, context, newinsetlayout);
4062 else if (t.cs() == "includepdf") {
4064 string const arg = p.getArg('[', ']');
4065 map<string, string> opts;
4066 vector<string> keys;
4067 split_map(arg, opts, keys);
4068 string name = normalize_filename(p.verbatim_item());
4069 string const path = getMasterFilePath();
4070 // We want to preserve relative / absolute filenames,
4071 // therefore path is only used for testing
4072 if (!makeAbsPath(name, path).exists()) {
4073 // The file extension is probably missing.
4074 // Now try to find it out.
4075 char const * const pdfpages_format[] = {"pdf", 0};
4076 string const pdftex_name =
4077 find_file(name, path, pdfpages_format);
4078 if (!pdftex_name.empty()) {
4083 if (makeAbsPath(name, path).exists())
4084 fix_relative_filename(name);
4086 cerr << "Warning: Could not find file '"
4087 << name << "'." << endl;
4089 context.check_layout(os);
4090 begin_inset(os, "External\n\ttemplate ");
4091 os << "PDFPages\n\tfilename "
4093 // parse the options
4094 if (opts.find("pages") != opts.end())
4095 os << "\textra LaTeX \"pages="
4096 << opts["pages"] << "\"\n";
4097 if (opts.find("angle") != opts.end())
4098 os << "\trotateAngle "
4099 << opts["angle"] << '\n';
4100 if (opts.find("origin") != opts.end()) {
4102 string const opt = opts["origin"];
4103 if (opt == "tl") ss << "topleft";
4104 if (opt == "bl") ss << "bottomleft";
4105 if (opt == "Bl") ss << "baselineleft";
4106 if (opt == "c") ss << "center";
4107 if (opt == "tc") ss << "topcenter";
4108 if (opt == "bc") ss << "bottomcenter";
4109 if (opt == "Bc") ss << "baselinecenter";
4110 if (opt == "tr") ss << "topright";
4111 if (opt == "br") ss << "bottomright";
4112 if (opt == "Br") ss << "baselineright";
4113 if (!ss.str().empty())
4114 os << "\trotateOrigin " << ss.str() << '\n';
4116 cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
4118 if (opts.find("width") != opts.end())
4120 << translate_len(opts["width"]) << '\n';
4121 if (opts.find("height") != opts.end())
4123 << translate_len(opts["height"]) << '\n';
4124 if (opts.find("keepaspectratio") != opts.end())
4125 os << "\tkeepAspectRatio\n";
4127 context.check_layout(os);
4128 registerExternalTemplatePackages("PDFPages");
4131 else if (t.cs() == "loadgame") {
4133 string name = normalize_filename(p.verbatim_item());
4134 string const path = getMasterFilePath();
4135 // We want to preserve relative / absolute filenames,
4136 // therefore path is only used for testing
4137 if (!makeAbsPath(name, path).exists()) {
4138 // The file extension is probably missing.
4139 // Now try to find it out.
4140 char const * const lyxskak_format[] = {"fen", 0};
4141 string const lyxskak_name =
4142 find_file(name, path, lyxskak_format);
4143 if (!lyxskak_name.empty())
4144 name = lyxskak_name;
4146 if (makeAbsPath(name, path).exists())
4147 fix_relative_filename(name);
4149 cerr << "Warning: Could not find file '"
4150 << name << "'." << endl;
4151 context.check_layout(os);
4152 begin_inset(os, "External\n\ttemplate ");
4153 os << "ChessDiagram\n\tfilename "
4156 context.check_layout(os);
4157 // after a \loadgame follows a \showboard
4158 if (p.get_token().asInput() == "showboard")
4160 registerExternalTemplatePackages("ChessDiagram");
4164 // try to see whether the string is in unicodesymbols
4165 // Only use text mode commands, since we are in text mode here,
4166 // and math commands may be invalid (bug 6797)
4170 docstring s = encodings.fromLaTeXCommand(from_utf8(t.asInput()),
4171 Encodings::TEXT_CMD, termination, rem, &req);
4174 cerr << "When parsing " << t.cs()
4175 << ", result is " << to_utf8(s)
4176 << "+" << to_utf8(rem) << endl;
4177 context.check_layout(os);
4180 skip_spaces_braces(p);
4181 for (set<string>::const_iterator it = req.begin(); it != req.end(); ++it)
4182 preamble.registerAutomaticallyLoadedPackage(*it);
4184 //cerr << "#: " << t << " mode: " << mode << endl;
4185 // heuristic: read up to next non-nested space
4187 string s = t.asInput();
4188 string z = p.verbatim_item();
4189 while (p.good() && z != " " && z.size()) {
4190 //cerr << "read: " << z << endl;
4192 z = p.verbatim_item();
4194 cerr << "found ERT: " << s << endl;
4195 handle_ert(os, s + ' ', context);
4198 string name = t.asInput();
4199 if (p.next_token().asInput() == "*") {
4200 // Starred commands like \vspace*{}
4201 p.get_token(); // Eat '*'
4204 if (!parse_command(name, p, os, outer, context))
4205 handle_ert(os, name, context);
4209 if (flags & FLAG_LEAVE) {
4210 flags &= ~FLAG_LEAVE;