X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ftex2lyx%2Ftext.cpp;h=f9201cd1a26f73f307979729869a3f243d5d0683;hb=a2b21e3cd4bbfd42e59161143eba6e7681aaa93f;hp=cda081bcc92de9879d5afde8554ac0d2bc54fe80;hpb=a30972a39c10647ffcb7eab5382f250331db7a8b;p=lyx.git diff --git a/src/tex2lyx/text.cpp b/src/tex2lyx/text.cpp index cda081bcc9..f9201cd1a2 100644 --- a/src/tex2lyx/text.cpp +++ b/src/tex2lyx/text.cpp @@ -47,14 +47,15 @@ namespace lyx { namespace { -void output_arguments(ostream &, Parser &, bool, bool, bool, Context &, +void output_arguments(ostream &, Parser &, bool, bool, string, Context &, Layout::LaTeXArgMap const &); } void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer, - Context const & context, InsetLayout const * layout) + Context const & context, InsetLayout const * layout, + string const rdelim) { bool const forcePlainLayout = layout ? layout->forcePlainLayout() : false; @@ -64,11 +65,18 @@ void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer, else newcontext.font = context.font; if (layout) - output_arguments(os, p, outer, false, false, newcontext, + output_arguments(os, p, outer, false, string(), newcontext, layout->latexargs()); - parse_text(p, os, flags, outer, newcontext); + // If we have a latex param, we eat it here. + if (!context.latexparam.empty()) { + ostringstream oss; + Context dummy(true, context.textclass); + parse_text(p, oss, FLAG_RDELIM, outer, dummy, + string(1, context.latexparam.back())); + } + parse_text(p, os, flags, outer, newcontext, rdelim); if (layout) - output_arguments(os, p, outer, false, true, newcontext, + output_arguments(os, p, outer, false, "post", newcontext, layout->postcommandargs()); newcontext.check_end_layout(os); } @@ -77,14 +85,15 @@ void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer, namespace { void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer, - Context const & context, string const & name) + Context const & context, string const & name, + string const rdelim = string()) { InsetLayout const * layout = 0; DocumentClass::InsetLayouts::const_iterator it = context.textclass.insetLayouts().find(from_ascii(name)); if (it != context.textclass.insetLayouts().end()) layout = &(it->second); - parse_text_in_inset(p, os, flags, outer, context, layout); + parse_text_in_inset(p, os, flags, outer, context, layout, rdelim); } /// parses a paragraph snippet, useful for example for \\emph{...} @@ -201,13 +210,14 @@ bool need_commentbib = false; char const * const known_quotes[] = { "dq", "guillemotleft", "flqq", "og", "guillemotright", "frqq", "fg", "glq", "glqq", "textquoteleft", "grq", "grqq", "quotedblbase", "textquotedblleft", "quotesinglbase", "textquoteright", "flq", -"guilsinglleft", "frq", "guilsinglright", 0}; +"guilsinglleft", "frq", "guilsinglright", "textquotedblright", "textquotesingle", +"textquotedbl", 0}; /// the same as known_quotes with .lyx names -char const * const known_coded_quotes[] = { "prd", "ard", "ard", "ard", -"ald", "ald", "ald", "gls", "gld", "els", "els", "grd", -"gld", "grd", "gls", "ers", "fls", -"fls", "frs", "frs", 0}; +char const * const known_coded_quotes[] = { "qrd", "ard", "ard", "ard", +"ald", "ald", "ald", "gls", "gld", "els", "els", "eld", +"gld", "eld", "gls", "ers", "ars", +"ars", "als", "als", "erd", "qrs", "qrd", 0}; /// LaTeX names for font sizes char const * const known_sizes[] = { "tiny", "scriptsize", "footnotesize", @@ -343,6 +353,10 @@ bool minted_float_has_caption = false; // The caption for non-floating minted listings string minted_nonfloat_caption = ""; +// Characters that have to be escaped by \\ in LaTeX +char const * const known_escaped_chars[] = { + "&", "_", "$", "%", "#", "^", "{", "}", 0}; + /// splits "x=z, y=b" into a map and an ordered keyword vector void split_map(string const & s, map & res, vector & keys) @@ -442,6 +456,107 @@ bool translate_len(string const & length, string & valstring, string & unit) return true; } + +/// If we have ambiguous quotation marks, make a smart guess +/// based on main quote style +string guessQuoteStyle(string in, bool const opening) +{ + string res = in; + if (prefixIs(in, "qr")) {// straight quote + if (!opening) + res = subst(res, "r", "l"); + } else if (in == "eld") {// `` + if (preamble.quotesStyle() == "german") + res = "grd"; + else if (preamble.quotesStyle() == "british") + res = "bls"; + else if (preamble.quotesStyle() == "french") + res = "fls"; + else if (preamble.quotesStyle() == "russian") + res = "rrs"; + } else if (in == "erd") {// '' + if (preamble.quotesStyle() == "polish") + res = "prd"; + else if (preamble.quotesStyle() == "british") + res = "brs"; + else if (preamble.quotesStyle() == "french") + res = "frs"; + else if (preamble.quotesStyle() == "swedish") + res = opening ? "sld" : "srd"; + } else if (in == "els") {// ` + if (preamble.quotesStyle() == "german") + res = "grs"; + else if (preamble.quotesStyle() == "british") + res = "bld"; + } else if (in == "ers") {// ' + if (preamble.quotesStyle() == "polish") + res = "prs"; + else if (preamble.quotesStyle() == "british") + res = "brd"; + else if (preamble.quotesStyle() == "swedish") + res = opening ? "sls" : "srs"; + } else if (in == "ard") {// >> + if (preamble.quotesStyle() == "swiss") + res = "cld"; + else if (preamble.quotesStyle() == "french") + res = "fld"; + else if (preamble.quotesStyle() == "russian") + res = "rld"; + } else if (in == "ald") {// << + if (preamble.quotesStyle() == "swiss") + res = "crd"; + else if (preamble.quotesStyle() == "french") + res = "frd"; + else if (preamble.quotesStyle() == "russian") + res = "rrd"; + } else if (in == "ars") {// > + if (preamble.quotesStyle() == "swiss") + res = "cls"; + } else if (in == "als") {// < + if (preamble.quotesStyle() == "swiss") + res = "crs"; + } else if (in == "gld") {// ,, + if (preamble.quotesStyle() == "polish") + res = "pld"; + else if (preamble.quotesStyle() == "russian") + res = "rls"; + } else if (in == "gls") {// , + if (preamble.quotesStyle() == "polish") + res = "pls"; + } + return res; +} + + +string const fromPolyglossiaEnvironment(string const s) +{ + // Since \arabic is taken by the LaTeX kernel, + // the Arabic polyglossia environment is upcased + if (s == "Arabic") + return "arabic"; + else + return s; +} + + +string uncapitalize(string const s) +{ + docstring in = from_ascii(s); + char_type t = lowercase(s[0]); + in[0] = t; + return to_ascii(in); +} + + +bool isCapitalized(string const s) +{ + docstring in = from_ascii(s); + char_type t = uppercase(s[0]); + in[0] = t; + return to_ascii(in) == s; +} + + } // namespace @@ -482,22 +597,6 @@ void translate_box_len(string const & length, string & value, string & unit, str } -/*! - * Find a file with basename \p name in path \p path and an extension - * in \p extensions. - */ -string find_file(string const & name, string const & path, - char const * const * extensions) -{ - for (char const * const * what = extensions; *what; ++what) { - string const trial = addExtension(name, *what); - if (makeAbsPath(trial, path).exists()) - return trial; - } - return string(); -} - - void begin_inset(ostream & os, string const & name) { os << "\n\\begin_inset " << name; @@ -534,8 +633,9 @@ bool skip_braces(Parser & p) /// replace LaTeX commands in \p s from the unicodesymbols file with their /// unicode points -docstring convert_unicodesymbols(docstring s) +pair convert_unicodesymbols(docstring s) { + bool res = true; odocstringstream os; for (size_t i = 0; i < s.size();) { if (s[i] != '\\') { @@ -556,23 +656,41 @@ docstring convert_unicodesymbols(docstring s) s = rem; if (s.empty() || s[0] != '\\') i = 0; - else + else { + res = false; + for (auto const & c : known_escaped_chars) + if (c != 0 && prefixIs(s, from_ascii("\\") + c)) + res = true; i = 1; + } } - return os.str(); + return make_pair(res, os.str()); } /// try to convert \p s to a valid InsetCommand argument -string convert_command_inset_arg(string s) +/// return whether this succeeded. If not, these command insets +/// get the "literate" flag. +pair convert_latexed_command_inset_arg(string s) { - if (isAscii(s)) + bool success = false; + if (isAscii(s)) { // since we don't know the input encoding we can't use from_utf8 - s = to_utf8(convert_unicodesymbols(from_ascii(s))); + pair res = convert_unicodesymbols(from_ascii(s)); + success = res.first; + s = to_utf8(res.second); + } // LyX cannot handle newlines in a latex command - return subst(s, "\n", " "); + return make_pair(success, subst(s, "\n", " ")); } +/// try to convert \p s to a valid InsetCommand argument +/// without trying to recode macros. +string convert_literate_command_inset_arg(string s) +{ + // LyX cannot handle newlines in a latex command + return subst(s, "\n", " "); +} void output_ert(ostream & os, string const & s, Context & context) { @@ -615,29 +733,33 @@ void output_comment(Parser & p, ostream & os, string const & s, } -Layout const * findLayout(TextClass const & textclass, string const & name, bool command) +Layout const * findLayout(TextClass const & textclass, string const & name, bool command, + string const & latexparam = string()) { - Layout const * layout = findLayoutWithoutModule(textclass, name, command); + Layout const * layout = findLayoutWithoutModule(textclass, name, command, latexparam); if (layout) return layout; if (checkModule(name, command)) - return findLayoutWithoutModule(textclass, name, command); + return findLayoutWithoutModule(textclass, name, command, latexparam); return layout; } -InsetLayout const * findInsetLayout(TextClass const & textclass, string const & name, bool command) +InsetLayout const * findInsetLayout(TextClass const & textclass, string const & name, bool command, + string const & latexparam = string()) { - InsetLayout const * insetlayout = findInsetLayoutWithoutModule(textclass, name, command); + InsetLayout const * insetlayout = + findInsetLayoutWithoutModule(textclass, name, command, latexparam); if (insetlayout) return insetlayout; if (checkModule(name, command)) - return findInsetLayoutWithoutModule(textclass, name, command); + return findInsetLayoutWithoutModule(textclass, name, command, latexparam); return insetlayout; } -void eat_whitespace(Parser &, ostream &, Context &, bool); +void eat_whitespace(Parser &, ostream &, Context &, bool eatParagraph, + bool eatNewline = true); /*! @@ -667,14 +789,16 @@ void skip_spaces_braces(Parser & p, bool keepws = false) } -void output_arguments(ostream & os, Parser & p, bool outer, bool need_layout, bool post, +void output_arguments(ostream & os, Parser & p, bool outer, bool need_layout, string const prefix, Context & context, Layout::LaTeXArgMap const & latexargs) { - if (need_layout) { - context.check_layout(os); - need_layout = false; - } else - need_layout = true; + if (context.layout->latextype != LATEX_ITEM_ENVIRONMENT || !prefix.empty()) { + if (need_layout) { + context.check_layout(os); + need_layout = false; + } else + need_layout = true; + } int i = 0; Layout::LaTeXArgMap::const_iterator lait = latexargs.begin(); Layout::LaTeXArgMap::const_iterator const laend = latexargs.end(); @@ -684,31 +808,50 @@ void output_arguments(ostream & os, Parser & p, bool outer, bool need_layout, bo if (lait->second.mandatory) { if (p.next_token().cat() != catBegin) break; - p.get_token(); // eat '{' + string ldelim = to_utf8(lait->second.ldelim); + string rdelim = to_utf8(lait->second.rdelim); + if (ldelim.empty()) + ldelim = "{"; + if (rdelim.empty()) + rdelim = "}"; + p.get_token(); // eat ldelim + if (ldelim.size() > 1) + p.get_token(); // eat ldelim if (need_layout) { context.check_layout(os); need_layout = false; } begin_inset(os, "Argument "); - if (post) - os << "post:"; + if (!prefix.empty()) + os << prefix << ':'; os << i << "\nstatus collapsed\n\n"; - parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context); + parse_text_in_inset(p, os, FLAG_RDELIM, outer, context, 0, rdelim); end_inset(os); } else { - if (p.next_token().cat() == catEscape || - p.next_token().character() != '[') + string ldelim = to_utf8(lait->second.ldelim); + string rdelim = to_utf8(lait->second.rdelim); + if (ldelim.empty()) + ldelim = "["; + if (rdelim.empty()) + rdelim = "]"; + string tok = p.next_token().asInput(); + // we only support delimiters with max 2 chars for now. + if (ldelim.size() > 1) + tok += p.next_next_token().asInput(); + if (p.next_token().cat() == catEscape || tok != ldelim) continue; - p.get_token(); // eat '[' + p.get_token(); // eat ldelim + if (ldelim.size() > 1) + p.get_token(); // eat ldelim if (need_layout) { context.check_layout(os); need_layout = false; } begin_inset(os, "Argument "); - if (post) - os << "post:"; + if (!prefix.empty()) + os << prefix << ':'; os << i << "\nstatus collapsed\n\n"; - parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context); + parse_text_in_inset(p, os, FLAG_RDELIM, outer, context, 0, rdelim); end_inset(os); } eat_whitespace(p, os, context, false); @@ -739,10 +882,17 @@ void output_command_layout(ostream & os, Parser & p, bool outer, context.need_end_deeper = true; } context.check_deeper(os); - output_arguments(os, p, outer, true, false, context, + output_arguments(os, p, outer, true, string(), context, context.layout->latexargs()); + // If we have a latex param, we eat it here. + if (!parent_context.latexparam.empty()) { + ostringstream oss; + Context dummy(true, parent_context.textclass); + parse_text(p, oss, FLAG_RDELIM, outer, dummy, + string(1, parent_context.latexparam.back())); + } parse_text(p, os, FLAG_ITEM, outer, context); - output_arguments(os, p, outer, false, true, context, + output_arguments(os, p, outer, false, "post", context, context.layout->postcommandargs()); context.check_end_layout(os); if (parent_context.deeper_paragraph) { @@ -1363,7 +1513,7 @@ void parse_listings(Parser & p, ostream & os, Context & parent_context, os << "inline true\n"; else os << "inline false\n"; - os << "status collapsed\n"; + os << "status open\n"; Context context(true, parent_context.textclass); context.layout = &parent_context.textclass.plainLayout(); if (use_minted && prefixIs(minted_nonfloat_caption, "[t]")) { @@ -1445,6 +1595,14 @@ void parse_unknown_environment(Parser & p, string const & name, ostream & os, if (specialfont) parent_context.new_layout_allowed = false; output_ert_inset(os, "\\begin{" + name + "}", parent_context); + // Try to handle options: Look if we have optional arguments, + // and if so, put the brackets in ERT. + while (p.hasOpt()) { + p.get_token(); // eat '[' + output_ert_inset(os, "[", parent_context); + os << parse_text_snippet(p, FLAG_BRACK_LAST, outer, parent_context); + output_ert_inset(os, "]", parent_context); + } parse_text_snippet(p, os, flags, outer, parent_context); output_ert_inset(os, "\\end{" + name + "}", parent_context); if (specialfont) @@ -1462,615 +1620,821 @@ void parse_environment(Parser & p, ostream & os, bool outer, string const unstarred_name = rtrim(name, "*"); active_environments.push_back(name); - if (is_math_env(name)) { - parent_context.check_layout(os); - begin_inset(os, "Formula "); - os << "\\begin{" << name << "}"; - parse_math(p, os, FLAG_END, MATH_MODE); - os << "\\end{" << name << "}"; - end_inset(os); - if (is_display_math_env(name)) { - // Prevent the conversion of a line break to a space - // (bug 7668). This does not change the output, but - // looks ugly in LyX. - eat_whitespace(p, os, parent_context, false); + // We use this loop and break out after a condition is met + // rather than a huge else-if-chain. + while (true) { + if (is_math_env(name)) { + parent_context.check_layout(os); + begin_inset(os, "Formula "); + os << "\\begin{" << name << "}"; + parse_math(p, os, FLAG_END, MATH_MODE); + os << "\\end{" << name << "}"; + end_inset(os); + if (is_display_math_env(name)) { + // Prevent the conversion of a line break to a space + // (bug 7668). This does not change the output, but + // looks ugly in LyX. + eat_whitespace(p, os, parent_context, false); + } + break; } - } - else if (is_known(name, preamble.polyglossia_languages)) { - // We must begin a new paragraph if not already done - if (! parent_context.atParagraphStart()) { - parent_context.check_end_layout(os); + // We need to use fromPolyglossiaEnvironment die to Arabic > arabic + if (is_known(fromPolyglossiaEnvironment(name), preamble.polyglossia_languages)) { + // We must begin a new paragraph if not already done + if (! parent_context.atParagraphStart()) { + parent_context.check_end_layout(os); + parent_context.new_paragraph(os); + } + // save the language in the context so that it is + // handled by parse_text + parent_context.font.language = + preamble.polyglossia2lyx(fromPolyglossiaEnvironment(name)); + parse_text(p, os, FLAG_END, outer, parent_context); + // Just in case the environment is empty + parent_context.extra_stuff.erase(); + // We must begin a new paragraph to reset the language parent_context.new_paragraph(os); + p.skip_spaces(); + break; } - // save the language in the context so that it is - // handled by parse_text - parent_context.font.language = preamble.polyglossia2lyx(name); - parse_text(p, os, FLAG_END, outer, parent_context); - // Just in case the environment is empty - parent_context.extra_stuff.erase(); - // We must begin a new paragraph to reset the language - parent_context.new_paragraph(os); - p.skip_spaces(); - } - else if (unstarred_name == "tabular" || name == "longtable") { - eat_whitespace(p, os, parent_context, false); - string width = "0pt"; - if (name == "tabular*") { - width = lyx::translate_len(p.getArg('{', '}')); + if (unstarred_name == "tabular" || name == "longtable" + || name == "tabularx" || name == "xltabular") { eat_whitespace(p, os, parent_context, false); + string width = "0pt"; + string halign; + if ((name == "longtable" || name == "xltabular") && p.hasOpt()) { + string const opt = p.getArg('[', ']'); + if (opt == "c") + halign = "center"; + else if (opt == "l") + halign = "left"; + else if (opt == "r") + halign = "right"; + } + if (name == "tabular*" || name == "tabularx" || name == "xltabular") { + width = lyx::translate_len(p.getArg('{', '}')); + eat_whitespace(p, os, parent_context, false); + } + parent_context.check_layout(os); + begin_inset(os, "Tabular "); + handle_tabular(p, os, name, width, halign, parent_context); + end_inset(os); + p.skip_spaces(); + break; } - parent_context.check_layout(os); - begin_inset(os, "Tabular "); - handle_tabular(p, os, name, width, parent_context); - end_inset(os); - p.skip_spaces(); - } - else if (parent_context.textclass.floats().typeExist(unstarred_name)) { - eat_whitespace(p, os, parent_context, false); - string const opt = p.hasOpt() ? p.getArg('[', ']') : string(); - eat_whitespace(p, os, parent_context, false); - parent_context.check_layout(os); - begin_inset(os, "Float " + unstarred_name + "\n"); - // store the float type for subfloats - // subfloats only work with figures and tables - if (unstarred_name == "figure") - float_type = unstarred_name; - else if (unstarred_name == "table") - float_type = unstarred_name; - else - float_type = ""; - if (!opt.empty()) - os << "placement " << opt << '\n'; - if (contains(opt, "H")) - preamble.registerAutomaticallyLoadedPackage("float"); - else { - Floating const & fl = parent_context.textclass.floats() - .getType(unstarred_name); - if (!fl.floattype().empty() && fl.usesFloatPkg()) + if (parent_context.textclass.floats().typeExist(unstarred_name)) { + eat_whitespace(p, os, parent_context, false); + string const opt = p.hasOpt() ? p.getArg('[', ']') : string(); + eat_whitespace(p, os, parent_context, false); + parent_context.check_layout(os); + begin_inset(os, "Float " + unstarred_name + "\n"); + // store the float type for subfloats + // subfloats only work with figures and tables + if (unstarred_name == "figure") + float_type = unstarred_name; + else if (unstarred_name == "table") + float_type = unstarred_name; + else + float_type = ""; + if (!opt.empty()) + os << "placement " << opt << '\n'; + if (contains(opt, "H")) preamble.registerAutomaticallyLoadedPackage("float"); - } - - os << "wide " << convert(is_starred) - << "\nsideways false" - << "\nstatus open\n\n"; - parse_text_in_inset(p, os, FLAG_END, outer, parent_context); - end_inset(os); - // We don't need really a new paragraph, but - // we must make sure that the next item gets a \begin_layout. - parent_context.new_paragraph(os); - p.skip_spaces(); - // the float is parsed thus delete the type - float_type = ""; - } - - else if (unstarred_name == "sidewaysfigure" - || unstarred_name == "sidewaystable" - || unstarred_name == "sidewaysalgorithm") { - string const opt = p.hasOpt() ? p.getArg('[', ']') : string(); - eat_whitespace(p, os, parent_context, false); - parent_context.check_layout(os); - if (unstarred_name == "sidewaysfigure") - begin_inset(os, "Float figure\n"); - else if (unstarred_name == "sidewaystable") - begin_inset(os, "Float table\n"); - else if (unstarred_name == "sidewaysalgorithm") - begin_inset(os, "Float algorithm\n"); - if (!opt.empty()) - os << "placement " << opt << '\n'; - if (contains(opt, "H")) - preamble.registerAutomaticallyLoadedPackage("float"); - os << "wide " << convert(is_starred) - << "\nsideways true" - << "\nstatus open\n\n"; - parse_text_in_inset(p, os, FLAG_END, outer, parent_context); - end_inset(os); - // We don't need really a new paragraph, but - // we must make sure that the next item gets a \begin_layout. - parent_context.new_paragraph(os); - p.skip_spaces(); - preamble.registerAutomaticallyLoadedPackage("rotfloat"); - } - - else if (name == "wrapfigure" || name == "wraptable") { - // syntax is \begin{wrapfigure}[lines]{placement}[overhang]{width} - eat_whitespace(p, os, parent_context, false); - parent_context.check_layout(os); - // default values - string lines = "0"; - string overhang = "0col%"; - // parse - if (p.hasOpt()) - lines = p.getArg('[', ']'); - string const placement = p.getArg('{', '}'); - if (p.hasOpt()) - overhang = p.getArg('[', ']'); - string const width = p.getArg('{', '}'); - // write - if (name == "wrapfigure") - begin_inset(os, "Wrap figure\n"); - else - begin_inset(os, "Wrap table\n"); - os << "lines " << lines - << "\nplacement " << placement - << "\noverhang " << lyx::translate_len(overhang) - << "\nwidth " << lyx::translate_len(width) - << "\nstatus open\n\n"; - parse_text_in_inset(p, os, FLAG_END, outer, parent_context); - end_inset(os); - // We don't need really a new paragraph, but - // we must make sure that the next item gets a \begin_layout. - parent_context.new_paragraph(os); - p.skip_spaces(); - preamble.registerAutomaticallyLoadedPackage("wrapfig"); - } + else { + Floating const & fl = parent_context.textclass.floats() + .getType(unstarred_name); + if (!fl.floattype().empty() && fl.usesFloatPkg()) + preamble.registerAutomaticallyLoadedPackage("float"); + } - else if (name == "minipage") { - eat_whitespace(p, os, parent_context, false); - // Test whether this is an outer box of a shaded box - p.pushPosition(); - // swallow arguments - while (p.hasOpt()) { - p.getArg('[', ']'); - p.skip_spaces(true); - } - p.getArg('{', '}'); - p.skip_spaces(true); - Token t = p.get_token(); - bool shaded = false; - if (t.asInput() == "\\begin") { - p.skip_spaces(true); - if (p.getArg('{', '}') == "shaded") - shaded = true; + os << "wide " << convert(is_starred) + << "\nsideways false" + << "\nstatus open\n\n"; + parse_text_in_inset(p, os, FLAG_END, outer, parent_context); + end_inset(os); + // We don't need really a new paragraph, but + // we must make sure that the next item gets a \begin_layout. + parent_context.new_paragraph(os); + p.skip_spaces(); + // the float is parsed thus delete the type + float_type = ""; + break; } - p.popPosition(); - if (shaded) - parse_outer_box(p, os, FLAG_END, outer, - parent_context, name, "shaded"); - else - parse_box(p, os, 0, FLAG_END, outer, parent_context, - "", "", name, "", ""); - p.skip_spaces(); - } - - else if (name == "comment") { - eat_whitespace(p, os, parent_context, false); - parent_context.check_layout(os); - begin_inset(os, "Note Comment\n"); - os << "status open\n"; - parse_text_in_inset(p, os, FLAG_END, outer, parent_context); - end_inset(os); - p.skip_spaces(); - skip_braces(p); // eat {} that might by set by LyX behind comments - preamble.registerAutomaticallyLoadedPackage("verbatim"); - } - - else if (unstarred_name == "verbatim") { - // FIXME: this should go in the generic code that - // handles environments defined in layout file that - // have "PassThru 1". However, the code over there is - // already too complicated for my taste. - string const ascii_name = - (name == "verbatim*") ? "Verbatim*" : "Verbatim"; - parent_context.new_paragraph(os); - Context context(true, parent_context.textclass, - &parent_context.textclass[from_ascii(ascii_name)]); - string s = p.verbatimEnvironment(name); - output_ert(os, s, context); - p.skip_spaces(); - } - - else if (name == "IPA") { - eat_whitespace(p, os, parent_context, false); - parent_context.check_layout(os); - begin_inset(os, "IPA\n"); - parse_text_in_inset(p, os, FLAG_END, outer, parent_context); - end_inset(os); - p.skip_spaces(); - preamble.registerAutomaticallyLoadedPackage("tipa"); - preamble.registerAutomaticallyLoadedPackage("tipx"); - } - else if (name == "CJK") { - // the scheme is \begin{CJK}{encoding}{mapping}text\end{CJK} - // It is impossible to decide if a CJK environment was in its own paragraph or within - // a line. We therefore always assume a paragraph since the latter is a rare case. - eat_whitespace(p, os, parent_context, false); - parent_context.check_end_layout(os); - // store the encoding to be able to reset it - string const encoding_old = p.getEncoding(); - string const encoding = p.getArg('{', '}'); - // FIXME: For some reason JIS does not work. Although the text - // in tests/CJK.tex is identical with the SJIS version if you - // convert both snippets using the recode command line utility, - // the resulting .lyx file contains some extra characters if - // you set buggy_encoding to false for JIS. - bool const buggy_encoding = encoding == "JIS"; - if (!buggy_encoding) - p.setEncoding(encoding, Encoding::CJK); - else { - // FIXME: This will read garbage, since the data is not encoded in utf8. - p.setEncoding("UTF-8"); - } - // LyX only supports the same mapping for all CJK - // environments, so we might need to output everything as ERT - string const mapping = trim(p.getArg('{', '}')); - char const * const * const where = - is_known(encoding, supported_CJK_encodings); - if (!buggy_encoding && !preamble.fontCJKSet()) - preamble.fontCJK(mapping); - bool knownMapping = mapping == preamble.fontCJK(); - if (buggy_encoding || !knownMapping || !where) { + if (unstarred_name == "sidewaysfigure" + || unstarred_name == "sidewaystable" + || unstarred_name == "sidewaysalgorithm") { + string const opt = p.hasOpt() ? p.getArg('[', ']') : string(); + eat_whitespace(p, os, parent_context, false); parent_context.check_layout(os); - output_ert_inset(os, "\\begin{" + name + "}{" + encoding + "}{" + mapping + "}", - parent_context); - // we must parse the content as verbatim because e.g. JIS can contain - // normally invalid characters - // FIXME: This works only for the most simple cases. - // Since TeX control characters are not parsed, - // things like comments are completely wrong. - string const s = p.plainEnvironment("CJK"); - for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) { - if (*it == '\\') - output_ert_inset(os, "\\", parent_context); - else if (*it == '$') - output_ert_inset(os, "$", parent_context); - else if (*it == '\n' && it + 1 != et && s.begin() + 1 != it) - os << "\n "; - else - os << *it; - } - output_ert_inset(os, "\\end{" + name + "}", - parent_context); - } else { - string const lang = - supported_CJK_languages[where - supported_CJK_encodings]; - // store the language because we must reset it at the end - string const lang_old = parent_context.font.language; - parent_context.font.language = lang; + if (unstarred_name == "sidewaysfigure") + begin_inset(os, "Float figure\n"); + else if (unstarred_name == "sidewaystable") + begin_inset(os, "Float table\n"); + else if (unstarred_name == "sidewaysalgorithm") + begin_inset(os, "Float algorithm\n"); + if (!opt.empty()) + os << "placement " << opt << '\n'; + if (contains(opt, "H")) + preamble.registerAutomaticallyLoadedPackage("float"); + os << "wide " << convert(is_starred) + << "\nsideways true" + << "\nstatus open\n\n"; parse_text_in_inset(p, os, FLAG_END, outer, parent_context); - parent_context.font.language = lang_old; + end_inset(os); + // We don't need really a new paragraph, but + // we must make sure that the next item gets a \begin_layout. parent_context.new_paragraph(os); + p.skip_spaces(); + preamble.registerAutomaticallyLoadedPackage("rotfloat"); + break; } - p.setEncoding(encoding_old); - p.skip_spaces(); - } - - else if (name == "lyxgreyedout") { - eat_whitespace(p, os, parent_context, false); - parent_context.check_layout(os); - begin_inset(os, "Note Greyedout\n"); - os << "status open\n"; - parse_text_in_inset(p, os, FLAG_END, outer, parent_context); - end_inset(os); - p.skip_spaces(); - if (!preamble.notefontcolor().empty()) - preamble.registerAutomaticallyLoadedPackage("color"); - } - - else if (name == "btSect") { - eat_whitespace(p, os, parent_context, false); - parent_context.check_layout(os); - begin_command_inset(os, "bibtex", "bibtex"); - string bibstyle = "plain"; - if (p.hasOpt()) { - bibstyle = p.getArg('[', ']'); - p.skip_spaces(true); - } - string const bibfile = p.getArg('{', '}'); - eat_whitespace(p, os, parent_context, false); - Token t = p.get_token(); - if (t.asInput() == "\\btPrintCited") { - p.skip_spaces(true); - os << "btprint " << '"' << "btPrintCited" << '"' << "\n"; - } - if (t.asInput() == "\\btPrintNotCited") { - p.skip_spaces(true); - os << "btprint " << '"' << "btPrintNotCited" << '"' << "\n"; - } - if (t.asInput() == "\\btPrintAll") { - p.skip_spaces(true); - os << "btprint " << '"' << "btPrintAll" << '"' << "\n"; - } - os << "bibfiles " << '"' << bibfile << "\"\n" - << "options " << '"' << bibstyle << "\"\n"; - parse_text_in_inset(p, os, FLAG_END, outer, parent_context); - end_inset(os); - p.skip_spaces(); - } - - else if (name == "framed" || name == "shaded") { - eat_whitespace(p, os, parent_context, false); - parse_outer_box(p, os, FLAG_END, outer, parent_context, name, ""); - p.skip_spaces(); - preamble.registerAutomaticallyLoadedPackage("framed"); - } - else if (name == "listing") { - minted_float = "float"; - eat_whitespace(p, os, parent_context, false); - string const opt = p.hasOpt() ? p.getArg('[', ']') : string(); - if (!opt.empty()) - minted_float += "=" + opt; - // If something precedes \begin{minted}, we output it at the end - // as a caption, in order to keep it inside the listings inset. - eat_whitespace(p, os, parent_context, true); - p.pushPosition(); - Token const & t = p.get_token(); - p.skip_spaces(true); - string const envname = p.next_token().cat() == catBegin - ? p.getArg('{', '}') : string(); - bool prologue = t.asInput() != "\\begin" || envname != "minted"; - p.popPosition(); - minted_float_has_caption = false; - string content = parse_text_snippet(p, FLAG_END, outer, - parent_context); - size_t i = content.find("\\begin_inset listings"); - bool minted_env = i != string::npos; - string caption; - if (prologue) { - caption = content.substr(0, i); - content.erase(0, i); - } - parent_context.check_layout(os); - if (minted_env && minted_float_has_caption) { - eat_whitespace(p, os, parent_context, true); - os << content << "\n"; - if (!caption.empty()) - os << caption << "\n"; - os << "\n\\end_layout\n"; // close inner layout - end_inset(os); // close caption inset - os << "\n\\end_layout\n"; // close outer layout - } else if (!caption.empty()) { - if (!minted_env) { - begin_inset(os, "listings\n"); - os << "lstparams " << '"' << minted_float << '"' << '\n'; - os << "inline false\n"; - os << "status collapsed\n"; - } - os << "\n\\begin_layout Plain Layout\n"; - begin_inset(os, "Caption Standard\n"); - Context newcontext(true, parent_context.textclass, - 0, 0, parent_context.font); - newcontext.check_layout(os); - os << caption << "\n"; - newcontext.check_end_layout(os); + if (name == "wrapfigure" || name == "wraptable") { + // syntax is \begin{wrapfigure}[lines]{placement}[overhang]{width} + eat_whitespace(p, os, parent_context, false); + parent_context.check_layout(os); + // default values + string lines = "0"; + string overhang = "0col%"; + // parse + if (p.hasOpt()) + lines = p.getArg('[', ']'); + string const placement = p.getArg('{', '}'); + if (p.hasOpt()) + overhang = p.getArg('[', ']'); + string const width = p.getArg('{', '}'); + // write + if (name == "wrapfigure") + begin_inset(os, "Wrap figure\n"); + else + begin_inset(os, "Wrap table\n"); + os << "lines " << lines + << "\nplacement " << placement + << "\noverhang " << lyx::translate_len(overhang) + << "\nwidth " << lyx::translate_len(width) + << "\nstatus open\n\n"; + parse_text_in_inset(p, os, FLAG_END, outer, parent_context); end_inset(os); - os << "\n\\end_layout\n"; - } else if (content.empty()) { - begin_inset(os, "listings\n"); - os << "lstparams " << '"' << minted_float << '"' << '\n'; - os << "inline false\n"; - os << "status collapsed\n"; - } else { - os << content << "\n"; + // We don't need really a new paragraph, but + // we must make sure that the next item gets a \begin_layout. + parent_context.new_paragraph(os); + p.skip_spaces(); + preamble.registerAutomaticallyLoadedPackage("wrapfig"); + break; } - end_inset(os); // close listings inset - parent_context.check_end_layout(os); - parent_context.new_paragraph(os); - p.skip_spaces(); - minted_float.clear(); - minted_float_has_caption = false; - } - else if (name == "lstlisting" || name == "minted") { - bool use_minted = name == "minted"; - eat_whitespace(p, os, parent_context, false); - if (use_minted && minted_float.empty()) { - // look ahead for a bottom caption + if (name == "minipage") { + eat_whitespace(p, os, parent_context, false); + // Test whether this is an outer box of a shaded box p.pushPosition(); - bool found_end_minted = false; - while (!found_end_minted && p.good()) { - Token const & t = p.get_token(); - p.skip_spaces(); - string const envname = - p.next_token().cat() == catBegin - ? p.getArg('{', '}') : string(); - found_end_minted = t.asInput() == "\\end" - && envname == "minted"; + // swallow arguments + while (p.hasOpt()) { + p.getArg('[', ']'); + p.skip_spaces(true); } - eat_whitespace(p, os, parent_context, true); - Token const & t = p.get_token(); + p.getArg('{', '}'); p.skip_spaces(true); - if (t.asInput() == "\\lyxmintcaption") { - string const pos = p.getArg('[', ']'); - if (pos == "b") { - string const caption = - parse_text_snippet(p, FLAG_ITEM, - false, parent_context); - minted_nonfloat_caption = "[b]" + caption; - } + Token t = p.get_token(); + bool shaded = false; + if (t.asInput() == "\\begin") { + p.skip_spaces(true); + if (p.getArg('{', '}') == "shaded") + shaded = true; } p.popPosition(); + if (shaded) + parse_outer_box(p, os, FLAG_END, outer, + parent_context, name, "shaded"); + else + parse_box(p, os, 0, FLAG_END, outer, parent_context, + "", "", name, "", ""); + p.skip_spaces(); + break; } - parse_listings(p, os, parent_context, false, use_minted); - p.skip_spaces(); - } - else if (!parent_context.new_layout_allowed) - parse_unknown_environment(p, name, os, FLAG_END, outer, - parent_context); - - // Alignment and spacing settings - // FIXME (bug xxxx): These settings can span multiple paragraphs and - // therefore are totally broken! - // Note that \centering, raggedright, and raggedleft cannot be handled, as - // they are commands not environments. They are furthermore switches that - // can be ended by another switches, but also by commands like \footnote or - // \parbox. So the only safe way is to leave them untouched. - else if (name == "center" || name == "centering" || - name == "flushleft" || name == "flushright" || - name == "singlespace" || name == "onehalfspace" || - name == "doublespace" || name == "spacing") { - eat_whitespace(p, os, parent_context, false); - // We must begin a new paragraph if not already done - if (! parent_context.atParagraphStart()) { - parent_context.check_end_layout(os); - parent_context.new_paragraph(os); - } - if (name == "flushleft") - parent_context.add_extra_stuff("\\align left\n"); - else if (name == "flushright") - parent_context.add_extra_stuff("\\align right\n"); - else if (name == "center" || name == "centering") - parent_context.add_extra_stuff("\\align center\n"); - else if (name == "singlespace") - parent_context.add_extra_stuff("\\paragraph_spacing single\n"); - else if (name == "onehalfspace") { - parent_context.add_extra_stuff("\\paragraph_spacing onehalf\n"); - preamble.registerAutomaticallyLoadedPackage("setspace"); - } else if (name == "doublespace") { - parent_context.add_extra_stuff("\\paragraph_spacing double\n"); - preamble.registerAutomaticallyLoadedPackage("setspace"); - } else if (name == "spacing") { - parent_context.add_extra_stuff("\\paragraph_spacing other " + p.verbatim_item() + "\n"); - preamble.registerAutomaticallyLoadedPackage("setspace"); - } - parse_text(p, os, FLAG_END, outer, parent_context); - // Just in case the environment is empty - parent_context.extra_stuff.erase(); - // We must begin a new paragraph to reset the alignment - parent_context.new_paragraph(os); - p.skip_spaces(); - } + if (name == "comment") { + eat_whitespace(p, os, parent_context, false); + parent_context.check_layout(os); + begin_inset(os, "Note Comment\n"); + os << "status open\n"; + parse_text_in_inset(p, os, FLAG_END, outer, parent_context); + end_inset(os); + p.skip_spaces(); + skip_braces(p); // eat {} that might by set by LyX behind comments + preamble.registerAutomaticallyLoadedPackage("verbatim"); + break; + } - // The single '=' is meant here. - else if ((newlayout = findLayout(parent_context.textclass, name, false))) { - eat_whitespace(p, os, parent_context, false); - Context context(true, parent_context.textclass, newlayout, - parent_context.layout, parent_context.font); - if (parent_context.deeper_paragraph) { - // We are beginning a nested environment after a - // deeper paragraph inside the outer list environment. - // Therefore we don't need to output a "begin deeper". - context.need_end_deeper = true; - } - parent_context.check_end_layout(os); - if (last_env == name) { - // we need to output a separator since LyX would export - // the two environments as one otherwise (bug 5716) - TeX2LyXDocClass const & textclass(parent_context.textclass); - Context newcontext(true, textclass, - &(textclass.defaultLayout())); - newcontext.check_layout(os); - begin_inset(os, "Separator plain\n"); + if (unstarred_name == "verbatim") { + // FIXME: this should go in the generic code that + // handles environments defined in layout file that + // have "PassThru 1". However, the code over there is + // already too complicated for my taste. + string const ascii_name = + (name == "verbatim*") ? "Verbatim*" : "Verbatim"; + parent_context.new_paragraph(os); + Context context(true, parent_context.textclass, + &parent_context.textclass[from_ascii(ascii_name)]); + string s = p.verbatimEnvironment(name); + output_ert(os, s, context); + p.skip_spaces(); + break; + } + + if (name == "IPA") { + eat_whitespace(p, os, parent_context, false); + parent_context.check_layout(os); + begin_inset(os, "IPA\n"); + parse_text_in_inset(p, os, FLAG_END, outer, parent_context); end_inset(os); - newcontext.check_end_layout(os); + p.skip_spaces(); + preamble.registerAutomaticallyLoadedPackage("tipa"); + preamble.registerAutomaticallyLoadedPackage("tipx"); + break; } - switch (context.layout->latextype) { - case LATEX_LIST_ENVIRONMENT: - context.add_par_extra_stuff("\\labelwidthstring " - + p.verbatim_item() + '\n'); + + if (name == parent_context.textclass.titlename() + && parent_context.textclass.titletype() == TITLE_ENVIRONMENT) { + parse_text(p, os, FLAG_END, outer, parent_context); + // Just in case the environment is empty + parent_context.extra_stuff.erase(); + // We must begin a new paragraph + parent_context.new_paragraph(os); p.skip_spaces(); break; - case LATEX_BIB_ENVIRONMENT: - p.verbatim_item(); // swallow next arg + } + + if (name == "CJK") { + // the scheme is \begin{CJK}{encoding}{mapping}text\end{CJK} + // It is impossible to decide if a CJK environment was in its own paragraph or within + // a line. We therefore always assume a paragraph since the latter is a rare case. + eat_whitespace(p, os, parent_context, false); + parent_context.check_end_layout(os); + // store the encoding to be able to reset it + string const encoding_old = p.getEncoding(); + string const encoding = p.getArg('{', '}'); + // FIXME: For some reason JIS does not work. Although the text + // in tests/CJK.tex is identical with the SJIS version if you + // convert both snippets using the recode command line utility, + // the resulting .lyx file contains some extra characters if + // you set buggy_encoding to false for JIS. + bool const buggy_encoding = encoding == "JIS"; + if (!buggy_encoding) + p.setEncoding(encoding, Encoding::CJK); + else { + // FIXME: This will read garbage, since the data is not encoded in utf8. + p.setEncoding("UTF-8"); + } + // LyX only supports the same mapping for all CJK + // environments, so we might need to output everything as ERT + string const mapping = trim(p.getArg('{', '}')); + char const * const * const where = + is_known(encoding, supported_CJK_encodings); + if (!buggy_encoding && !preamble.fontCJKSet()) + preamble.fontCJK(mapping); + bool knownMapping = mapping == preamble.fontCJK(); + if (buggy_encoding || !knownMapping || !where) { + parent_context.check_layout(os); + output_ert_inset(os, "\\begin{" + name + "}{" + encoding + "}{" + mapping + "}", + parent_context); + // we must parse the content as verbatim because e.g. JIS can contain + // normally invalid characters + // FIXME: This works only for the most simple cases. + // Since TeX control characters are not parsed, + // things like comments are completely wrong. + string const s = p.plainEnvironment("CJK"); + for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) { + string snip; + snip += *it; + if (snip == "\\" || is_known(snip, known_escaped_chars)) + output_ert_inset(os, snip, parent_context); + else if (*it == '\n' && it + 1 != et && s.begin() + 1 != it) + os << "\n "; + else + os << *it; + } + output_ert_inset(os, "\\end{" + name + "}", + parent_context); + } else { + string const lang = + supported_CJK_languages[where - supported_CJK_encodings]; + // store the language because we must reset it at the end + string const lang_old = parent_context.font.language; + parent_context.font.language = lang; + parse_text_in_inset(p, os, FLAG_END, outer, parent_context); + parent_context.font.language = lang_old; + parent_context.new_paragraph(os); + } + p.setEncoding(encoding_old); p.skip_spaces(); break; - default: + } + + if (name == "lyxgreyedout") { + eat_whitespace(p, os, parent_context, false); + parent_context.check_layout(os); + begin_inset(os, "Note Greyedout\n"); + os << "status open\n"; + parse_text_in_inset(p, os, FLAG_END, outer, parent_context); + end_inset(os); + p.skip_spaces(); + if (!preamble.notefontcolor().empty()) + preamble.registerAutomaticallyLoadedPackage("color"); break; } - context.check_deeper(os); - // handle known optional and required arguments - // Unfortunately LyX can't handle arguments of list arguments (bug 7468): - // It is impossible to place anything after the environment name, - // but before the first \\item. - if (context.layout->latextype == LATEX_ENVIRONMENT) - output_arguments(os, p, outer, false, false, context, - context.layout->latexargs()); - parse_text(p, os, FLAG_END, outer, context); - if (context.layout->latextype == LATEX_ENVIRONMENT) - output_arguments(os, p, outer, false, true, context, - context.layout->postcommandargs()); - context.check_end_layout(os); - if (parent_context.deeper_paragraph) { - // We must suppress the "end deeper" because we - // suppressed the "begin deeper" above. - context.need_end_deeper = false; + + if (name == "btSect") { + eat_whitespace(p, os, parent_context, false); + parent_context.check_layout(os); + begin_command_inset(os, "bibtex", "bibtex"); + string bibstyle = "plain"; + if (p.hasOpt()) { + bibstyle = p.getArg('[', ']'); + p.skip_spaces(true); + } + string const bibfile = p.getArg('{', '}'); + eat_whitespace(p, os, parent_context, false); + Token t = p.get_token(); + if (t.asInput() == "\\btPrintCited") { + p.skip_spaces(true); + os << "btprint " << '"' << "btPrintCited" << '"' << "\n"; + } + if (t.asInput() == "\\btPrintNotCited") { + p.skip_spaces(true); + os << "btprint " << '"' << "btPrintNotCited" << '"' << "\n"; + } + if (t.asInput() == "\\btPrintAll") { + p.skip_spaces(true); + os << "btprint " << '"' << "btPrintAll" << '"' << "\n"; + } + os << "bibfiles " << '"' << bibfile << "\"\n" + << "options " << '"' << bibstyle << "\"\n"; + parse_text_in_inset(p, os, FLAG_END, outer, parent_context); + end_inset(os); + p.skip_spaces(); + break; } - context.check_end_deeper(os); - parent_context.new_paragraph(os); - p.skip_spaces(); - if (!preamble.titleLayoutFound()) - preamble.titleLayoutFound(newlayout->intitle); - set const & req = newlayout->requires(); - set::const_iterator it = req.begin(); - set::const_iterator en = req.end(); - for (; it != en; ++it) - preamble.registerAutomaticallyLoadedPackage(*it); - } - // The single '=' is meant here. - else if ((newinsetlayout = findInsetLayout(parent_context.textclass, name, false))) { - eat_whitespace(p, os, parent_context, false); - parent_context.check_layout(os); - begin_inset(os, "Flex "); - docstring flex_name = newinsetlayout->name(); - // FIXME: what do we do if the prefix is not Flex: ? - if (prefixIs(flex_name, from_ascii("Flex:"))) - flex_name.erase(0, 5); - os << to_utf8(flex_name) << '\n' - << "status collapsed\n"; - if (newinsetlayout->isPassThru()) { - string const arg = p.verbatimEnvironment(name); - Context context(true, parent_context.textclass, - &parent_context.textclass.plainLayout(), - parent_context.layout); - output_ert(os, arg, parent_context); - } else - parse_text_in_inset(p, os, FLAG_END, false, parent_context, newinsetlayout); - end_inset(os); - } + if (name == "btUnit") { + string const nt = p.next_next_token().cs(); + // Do not attempt to overwrite a former diverging multibib. + // Those are output as ERT instead. + if ((nt == "part" || nt == "chapter" + || nt == "section" || nt == "subsection") + && (preamble.multibib().empty() || preamble.multibib() == nt)) { + parse_text(p, os, FLAG_END, outer, parent_context); + preamble.multibib(nt); + } else + parse_unknown_environment(p, name, os, FLAG_END, outer, + parent_context); + break; + } - else if (name == "appendix") { - // This is no good latex style, but it works and is used in some documents... - eat_whitespace(p, os, parent_context, false); - parent_context.check_end_layout(os); - Context context(true, parent_context.textclass, parent_context.layout, - parent_context.layout, parent_context.font); - context.check_layout(os); - os << "\\start_of_appendix\n"; - parse_text(p, os, FLAG_END, outer, context); - context.check_end_layout(os); - p.skip_spaces(); - } + // This is only attempted at turn environments that consist only + // of a tabular (this is how tables in LyX, modulo longtables, are rotated). + // Thus we will fall through in other cases. + if (name == "turn") { + // We check if the next thing is a tabular[*|x] + p.pushPosition(); + p.getArg('{', '}');// eat turn argument + bool found_end = false; + bool only_table = false; + bool end_table = false; + p.get_token(); + p.get_token(); + string envname = p.getArg('{', '}'); + if (rtrim(envname, "*") == "tabular" || envname == "tabularx") { + // Now we check if the table is the only content + // of the turn environment + string const tenv = envname; + while (!found_end && !end_table && p.good()) { + envname = p.next_token().cat() == catBegin + ? p.getArg('{', '}') : string(); + Token const & t = p.get_token(); + p.skip_spaces(); + end_table = t.asInput() != "\\end" + && envname == tenv; + found_end = t.asInput() == "\\end" + && envname == "turn"; + } + if (end_table) { + p.get_token(); + envname = p.getArg('{', '}'); + only_table = p.next_next_token().asInput() == "\\end" + && envname == "turn"; + } + if (only_table) { + p.popPosition(); + string const angle = p.getArg('{', '}'); + p.skip_spaces(); + int const save_tablerotation = parent_context.tablerotation; + parent_context.tablerotation = convert(angle); + parse_text(p, os, FLAG_END, outer, parent_context); + parent_context.tablerotation = save_tablerotation; + p.skip_spaces(); + break; + } + // fall through + } + // fall through + p.popPosition(); + } - else if (known_environments.find(name) != known_environments.end()) { - vector arguments = known_environments[name]; - // The last "argument" denotes wether we may translate the - // environment contents to LyX - // The default required if no argument is given makes us - // compatible with the reLyXre environment. - ArgumentType contents = arguments.empty() ? - required : - arguments.back(); - if (!arguments.empty()) - arguments.pop_back(); - // See comment in parse_unknown_environment() - bool const specialfont = - (parent_context.font != parent_context.normalfont); - bool const new_layout_allowed = - parent_context.new_layout_allowed; - if (specialfont) - parent_context.new_layout_allowed = false; - parse_arguments("\\begin{" + name + "}", arguments, p, os, - outer, parent_context); - if (contents == verbatim) - output_ert_inset(os, p.ertEnvironment(name), - parent_context); - else - parse_text_snippet(p, os, FLAG_END, outer, + // This is only attempted at landscape environments that consist only + // of a longtable (this is how longtables in LyX are rotated by 90 degs). + // Other landscape environment is handled via the landscape module, thus + // we will fall through in that case. + if (name == "landscape") { + // We check if the next thing is a longtable + p.pushPosition(); + bool found_end = false; + bool only_longtable = false; + bool end_longtable = false; + p.get_token(); + p.get_token(); + string envname = p.getArg('{', '}'); + if (envname == "longtable" || envname == "xltabular") { + // Now we check if the longtable is the only content + // of the landscape environment + string const ltenv = envname; + while (!found_end && !end_longtable && p.good()) { + envname = p.next_token().cat() == catBegin + ? p.getArg('{', '}') : string(); + Token const & t = p.get_token(); + p.skip_spaces(); + end_longtable = t.asInput() != "\\end" + && envname == ltenv; + found_end = t.asInput() == "\\end" + && envname == "landscape"; + } + if (end_longtable) { + p.get_token(); + envname = p.getArg('{', '}'); + only_longtable = p.next_next_token().asInput() == "\\end" + && envname == "landscape"; + } + if (only_longtable) { + p.popPosition(); + p.skip_spaces(); + int const save_tablerotation = parent_context.tablerotation; + parent_context.tablerotation = 90; + parse_text(p, os, FLAG_END, outer, parent_context); + parent_context.tablerotation = save_tablerotation; + p.skip_spaces(); + break; + } + // fall through + } + // fall through + p.popPosition(); + } + + if (name == "framed" || name == "shaded") { + eat_whitespace(p, os, parent_context, false); + parse_outer_box(p, os, FLAG_END, outer, parent_context, name, ""); + p.skip_spaces(); + preamble.registerAutomaticallyLoadedPackage("framed"); + break; + } + + if (name == "listing") { + minted_float = "float"; + eat_whitespace(p, os, parent_context, false); + string const opt = p.hasOpt() ? p.getArg('[', ']') : string(); + if (!opt.empty()) + minted_float += "=" + opt; + // If something precedes \begin{minted}, we output it at the end + // as a caption, in order to keep it inside the listings inset. + eat_whitespace(p, os, parent_context, true); + p.pushPosition(); + Token const & t = p.get_token(); + p.skip_spaces(true); + string const envname = p.next_token().cat() == catBegin + ? p.getArg('{', '}') : string(); + bool prologue = t.asInput() != "\\begin" || envname != "minted"; + p.popPosition(); + minted_float_has_caption = false; + string content = parse_text_snippet(p, FLAG_END, outer, + parent_context); + size_t i = content.find("\\begin_inset listings"); + bool minted_env = i != string::npos; + string caption; + if (prologue) { + caption = content.substr(0, i); + content.erase(0, i); + } + parent_context.check_layout(os); + if (minted_env && minted_float_has_caption) { + eat_whitespace(p, os, parent_context, true); + os << content << "\n"; + if (!caption.empty()) + os << caption << "\n"; + os << "\n\\end_layout\n"; // close inner layout + end_inset(os); // close caption inset + os << "\n\\end_layout\n"; // close outer layout + } else if (!caption.empty()) { + if (!minted_env) { + begin_inset(os, "listings\n"); + os << "lstparams " << '"' << minted_float << '"' << '\n'; + os << "inline false\n"; + os << "status collapsed\n"; + } + os << "\n\\begin_layout Plain Layout\n"; + begin_inset(os, "Caption Standard\n"); + Context newcontext(true, parent_context.textclass, + 0, 0, parent_context.font); + newcontext.check_layout(os); + os << caption << "\n"; + newcontext.check_end_layout(os); + end_inset(os); + os << "\n\\end_layout\n"; + } else if (content.empty()) { + begin_inset(os, "listings\n"); + os << "lstparams " << '"' << minted_float << '"' << '\n'; + os << "inline false\n"; + os << "status collapsed\n"; + } else { + os << content << "\n"; + } + end_inset(os); // close listings inset + parent_context.check_end_layout(os); + parent_context.new_paragraph(os); + p.skip_spaces(); + minted_float.clear(); + minted_float_has_caption = false; + break; + } + + if (name == "lstlisting" || name == "minted") { + bool use_minted = name == "minted"; + // with listings, we do not eat newlines here since + // \begin{lstlistings} + // [foo] + // and + // // \begin{lstlistings}% + // + // [foo] + // reads [foo] as content, whereas + // // \begin{lstlistings}% + // [foo] + // or + // \begin{lstlistings}[foo, + // bar] + // reads [foo...] as argument. + eat_whitespace(p, os, parent_context, false, use_minted); + if (use_minted && minted_float.empty()) { + // look ahead for a bottom caption + p.pushPosition(); + bool found_end_minted = false; + while (!found_end_minted && p.good()) { + Token const & t = p.get_token(); + p.skip_spaces(); + string const envname = + p.next_token().cat() == catBegin + ? p.getArg('{', '}') : string(); + found_end_minted = t.asInput() == "\\end" + && envname == "minted"; + } + eat_whitespace(p, os, parent_context, true); + Token const & t = p.get_token(); + p.skip_spaces(true); + if (t.asInput() == "\\lyxmintcaption") { + string const pos = p.getArg('[', ']'); + if (pos == "b") { + string const caption = + parse_text_snippet(p, FLAG_ITEM, + false, parent_context); + minted_nonfloat_caption = "[b]" + caption; + eat_whitespace(p, os, parent_context, true); + } + } + p.popPosition(); + } + parse_listings(p, os, parent_context, false, use_minted); + p.skip_spaces(); + break; + } + + if (!parent_context.new_layout_allowed) { + parse_unknown_environment(p, name, os, FLAG_END, outer, + parent_context); + break; + } + + // Alignment and spacing settings + // FIXME (bug xxxx): These settings can span multiple paragraphs and + // therefore are totally broken! + // Note that \centering, \raggedright, and \raggedleft cannot be handled, as + // they are commands not environments. They are furthermore switches that + // can be ended by another switches, but also by commands like \footnote or + // \parbox. So the only safe way is to leave them untouched. + // However, we support the pseudo-environments + // \begin{centering} ... \end{centering} + // \begin{raggedright} ... \end{raggedright} + // \begin{raggedleft} ... \end{raggedleft} + // since they are used by LyX in floats (for spacing reasons) + if (name == "center" || name == "centering" + || name == "flushleft" || name == "raggedright" + || name == "flushright" || name == "raggedleft" + || name == "singlespace" || name == "onehalfspace" + || name == "doublespace" || name == "spacing") { + eat_whitespace(p, os, parent_context, false); + // We must begin a new paragraph if not already done + if (! parent_context.atParagraphStart()) { + parent_context.check_end_layout(os); + parent_context.new_paragraph(os); + } + if (name == "flushleft" || name == "raggedright") + parent_context.add_extra_stuff("\\align left\n"); + else if (name == "flushright" || name == "raggedleft") + parent_context.add_extra_stuff("\\align right\n"); + else if (name == "center" || name == "centering") + parent_context.add_extra_stuff("\\align center\n"); + else if (name == "singlespace") + parent_context.add_extra_stuff("\\paragraph_spacing single\n"); + else if (name == "onehalfspace") { + parent_context.add_extra_stuff("\\paragraph_spacing onehalf\n"); + preamble.registerAutomaticallyLoadedPackage("setspace"); + } else if (name == "doublespace") { + parent_context.add_extra_stuff("\\paragraph_spacing double\n"); + preamble.registerAutomaticallyLoadedPackage("setspace"); + } else if (name == "spacing") { + parent_context.add_extra_stuff("\\paragraph_spacing other " + p.verbatim_item() + "\n"); + preamble.registerAutomaticallyLoadedPackage("setspace"); + } + parse_text(p, os, FLAG_END, outer, parent_context); + // Just in case the environment is empty + parent_context.extra_stuff.erase(); + // We must begin a new paragraph to reset the alignment + parent_context.new_paragraph(os); + p.skip_spaces(); + break; + } + + // The single '=' is meant here. + if ((newlayout = findLayout(parent_context.textclass, name, false))) { + eat_whitespace(p, os, parent_context, false); + Context context(true, parent_context.textclass, newlayout, + parent_context.layout, parent_context.font); + if (parent_context.deeper_paragraph) { + // We are beginning a nested environment after a + // deeper paragraph inside the outer list environment. + // Therefore we don't need to output a "begin deeper". + context.need_end_deeper = true; + } + parent_context.check_end_layout(os); + if (last_env == name) { + // we need to output a separator since LyX would export + // the two environments as one otherwise (bug 5716) + TeX2LyXDocClass const & textclass(parent_context.textclass); + Context newcontext(true, textclass, + &(textclass.defaultLayout())); + newcontext.check_layout(os); + begin_inset(os, "Separator plain\n"); + end_inset(os); + newcontext.check_end_layout(os); + } + switch (context.layout->latextype) { + case LATEX_LIST_ENVIRONMENT: + context.in_list_preamble = + !context.layout->listpreamble().empty() + && p.hasListPreamble(context.layout->itemcommand()); + context.add_par_extra_stuff("\\labelwidthstring " + + p.verbatim_item() + '\n'); + p.skip_spaces(); + break; + case LATEX_BIB_ENVIRONMENT: + p.verbatim_item(); // swallow next arg + p.skip_spaces(); + break; + default: + break; + } + context.check_deeper(os); + if (newlayout->keepempty) { + // We need to start a new paragraph + // even if it is empty. + context.new_paragraph(os); + context.check_layout(os); + } + // handle known optional and required arguments + if (context.layout->latextype == LATEX_ENVIRONMENT) + output_arguments(os, p, outer, false, string(), context, + context.layout->latexargs()); + else if (context.layout->latextype == LATEX_ITEM_ENVIRONMENT) { + context.in_list_preamble = + !context.layout->listpreamble().empty() + && p.hasListPreamble(context.layout->itemcommand()); + ostringstream oss; + output_arguments(oss, p, outer, false, string(), context, + context.layout->latexargs()); + context.list_extra_stuff = oss.str(); + } + if (context.in_list_preamble) { + // Collect the stuff between \begin and first \item + context.list_preamble = + parse_text_snippet(p, FLAG_END, outer, context); + context.in_list_preamble = false; + } + parse_text(p, os, FLAG_END, outer, context); + if (context.layout->latextype == LATEX_ENVIRONMENT) + output_arguments(os, p, outer, false, "post", context, + context.layout->postcommandargs()); + context.check_end_layout(os); + if (parent_context.deeper_paragraph) { + // We must suppress the "end deeper" because we + // suppressed the "begin deeper" above. + context.need_end_deeper = false; + } + context.check_end_deeper(os); + parent_context.new_paragraph(os); + p.skip_spaces(); + if (!preamble.titleLayoutFound()) + preamble.titleLayoutFound(newlayout->intitle); + set const & req = newlayout->required(); + set::const_iterator it = req.begin(); + set::const_iterator en = req.end(); + for (; it != en; ++it) + preamble.registerAutomaticallyLoadedPackage(*it); + break; + } + + // The single '=' is meant here. + if ((newinsetlayout = findInsetLayout(parent_context.textclass, name, false))) { + eat_whitespace(p, os, parent_context, false); + parent_context.check_layout(os); + begin_inset(os, "Flex "); + docstring flex_name = newinsetlayout->name(); + // FIXME: what do we do if the prefix is not Flex: ? + if (prefixIs(flex_name, from_ascii("Flex:"))) + flex_name.erase(0, 5); + os << to_utf8(flex_name) << '\n' + << "status collapsed\n"; + if (newinsetlayout->isPassThru()) { + string const arg = p.verbatimEnvironment(name); + Context context(true, parent_context.textclass, + &parent_context.textclass.plainLayout(), + parent_context.layout); + output_ert(os, arg, parent_context); + } else + parse_text_in_inset(p, os, FLAG_END, false, parent_context, newinsetlayout); + end_inset(os); + break; + } + + if (name == "appendix") { + // This is no good latex style, but it works and is used in some documents... + eat_whitespace(p, os, parent_context, false); + parent_context.check_end_layout(os); + Context context(true, parent_context.textclass, parent_context.layout, + parent_context.layout, parent_context.font); + context.check_layout(os); + os << "\\start_of_appendix\n"; + parse_text(p, os, FLAG_END, outer, context); + context.check_end_layout(os); + p.skip_spaces(); + break; + } + + if (known_environments.find(name) != known_environments.end()) { + vector arguments = known_environments[name]; + // The last "argument" denotes wether we may translate the + // environment contents to LyX + // The default required if no argument is given makes us + // compatible with the reLyXre environment. + ArgumentType contents = arguments.empty() ? + required : + arguments.back(); + if (!arguments.empty()) + arguments.pop_back(); + // See comment in parse_unknown_environment() + bool const specialfont = + (parent_context.font != parent_context.normalfont); + bool const new_layout_allowed = + parent_context.new_layout_allowed; + if (specialfont) + parent_context.new_layout_allowed = false; + parse_arguments("\\begin{" + name + "}", arguments, p, os, + outer, parent_context); + if (contents == verbatim) + output_ert_inset(os, p.ertEnvironment(name), parent_context); - output_ert_inset(os, "\\end{" + name + "}", parent_context); - if (specialfont) - parent_context.new_layout_allowed = new_layout_allowed; - } + else + parse_text_snippet(p, os, FLAG_END, outer, + parent_context); + output_ert_inset(os, "\\end{" + name + "}", parent_context); + if (specialfont) + parent_context.new_layout_allowed = new_layout_allowed; + break; + } - else - parse_unknown_environment(p, name, os, FLAG_END, outer, - parent_context); + parse_unknown_environment(p, name, os, FLAG_END, outer, parent_context); + break; + }// end of loop last_env = name; active_environments.pop_back(); @@ -2078,7 +2442,8 @@ void parse_environment(Parser & p, ostream & os, bool outer, /// parses a comment and outputs it to \p os. -void parse_comment(Parser & p, ostream & os, Token const & t, Context & context) +void parse_comment(Parser & p, ostream & os, Token const & t, Context & context, + bool skipNewlines = false) { LASSERT(t.cat() == catComment, return); if (!t.cs().empty()) { @@ -2096,7 +2461,7 @@ void parse_comment(Parser & p, ostream & os, Token const & t, Context & context) output_ert_inset(os, "\n", context); eat_whitespace(p, os, context, true); } - } else { + } else if (!skipNewlines) { // "%\n" combination p.skip_spaces(); } @@ -2107,17 +2472,18 @@ void parse_comment(Parser & p, ostream & os, Token const & t, Context & context) * Reads spaces and comments until the first non-space, non-comment token. * New paragraphs (double newlines or \\par) are handled like simple spaces * if \p eatParagraph is true. + * If \p eatNewline is false, newlines won't be treated as whitespace. * Spaces are skipped, but comments are written to \p os. */ void eat_whitespace(Parser & p, ostream & os, Context & context, - bool eatParagraph) + bool eatParagraph, bool eatNewline) { while (p.good()) { Token const & t = p.get_token(); if (t.cat() == catComment) - parse_comment(p, os, t, context); - else if ((! eatParagraph && p.isParagraph()) || - (t.cat() != catSpace && t.cat() != catNewline)) { + parse_comment(p, os, t, context, !eatNewline); + else if ((!eatParagraph && p.isParagraph()) || + (t.cat() != catSpace && (t.cat() != catNewline || !eatNewline))) { p.putback(); return; } @@ -2149,109 +2515,19 @@ void parse_text_attributes(Parser & p, ostream & os, unsigned flags, bool outer, /// get the arguments of a natbib or jurabib citation command void get_cite_arguments(Parser & p, bool natbibOrder, - string & before, string & after, bool const qualified = false) -{ - // We need to distinguish "" and "[]", so we can't use p.getOpt(). - - // text before the citation - before.clear(); - // text after the citation - after = qualified ? p.getFullOpt(false, '(', ')') : p.getFullOpt(); - - if (!after.empty()) { - before = qualified ? p.getFullOpt(false, '(', ')') : p.getFullOpt(); - if (natbibOrder && !before.empty()) - swap(before, after); - } -} - - -/// Convert filenames with TeX macros and/or quotes to something LyX -/// can understand -string const normalize_filename(string const & name) -{ - Parser p(name); - ostringstream os; - while (p.good()) { - Token const & t = p.get_token(); - if (t.cat() != catEscape) - os << t.asInput(); - else if (t.cs() == "lyxdot") { - // This is used by LyX for simple dots in relative - // names - os << '.'; - p.skip_spaces(); - } else if (t.cs() == "space") { - os << ' '; - p.skip_spaces(); - } else if (t.cs() == "string") { - // Convert \string" to " and \string~ to ~ - Token const & n = p.next_token(); - if (n.asInput() != "\"" && n.asInput() != "~") - os << t.asInput(); - } else - os << t.asInput(); - } - // Strip quotes. This is a bit complicated (see latex_path()). - string full = os.str(); - if (!full.empty() && full[0] == '"') { - string base = removeExtension(full); - string ext = getExtension(full); - if (!base.empty() && base[base.length()-1] == '"') - // "a b" - // "a b".tex - return addExtension(trim(base, "\""), ext); - if (full[full.length()-1] == '"') - // "a b.c" - // "a b.c".tex - return trim(full, "\""); - } - return full; -} - - -/// Convert \p name from TeX convention (relative to master file) to LyX -/// convention (relative to .lyx file) if it is relative -void fix_child_filename(string & name) -{ - string const absMasterTeX = getMasterFilePath(true); - bool const isabs = FileName::isAbsolute(name); - // convert from "relative to .tex master" to absolute original path - if (!isabs) - name = makeAbsPath(name, absMasterTeX).absFileName(); - bool copyfile = copyFiles(); - string const absParentLyX = getParentFilePath(false); - string abs = name; - if (copyfile) { - // convert from absolute original path to "relative to master file" - string const rel = to_utf8(makeRelPath(from_utf8(name), - from_utf8(absMasterTeX))); - // re-interpret "relative to .tex file" as "relative to .lyx file" - // (is different if the master .lyx file resides in a - // different path than the master .tex file) - string const absMasterLyX = getMasterFilePath(false); - abs = makeAbsPath(rel, absMasterLyX).absFileName(); - // Do not copy if the new path is impossible to create. Example: - // absMasterTeX = "/foo/bar/" - // absMasterLyX = "/bar/" - // name = "/baz.eps" => new absolute name would be "/../baz.eps" - if (contains(name, "/../")) - copyfile = false; - } - if (copyfile) { - if (isabs) - name = abs; - else { - // convert from absolute original path to - // "relative to .lyx file" - name = to_utf8(makeRelPath(from_utf8(abs), - from_utf8(absParentLyX))); - } - } - else if (!isabs) { - // convert from absolute original path to "relative to .lyx file" - name = to_utf8(makeRelPath(from_utf8(name), - from_utf8(absParentLyX))); + string & before, string & after, bool const qualified = false) +{ + // We need to distinguish "" and "[]", so we can't use p.getOpt(). + + // text before the citation + before.clear(); + // text after the citation + after = qualified ? p.getFullOpt(false, '(', ')') : p.getFullOpt(); + + if (!after.empty()) { + before = qualified ? p.getFullOpt(false, '(', ')') : p.getFullOpt(); + if (natbibOrder && !before.empty()) + swap(before, after); } } @@ -2480,8 +2756,114 @@ void registerExternalTemplatePackages(string const & name) } // anonymous namespace +/*! + * Find a file with basename \p name in path \p path and an extension + * in \p extensions. + */ +string find_file(string const & name, string const & path, + char const * const * extensions) +{ + for (char const * const * what = extensions; *what; ++what) { + string const trial = addExtension(name, *what); + if (makeAbsPath(trial, path).exists()) + return trial; + } + return string(); +} + + +/// Convert filenames with TeX macros and/or quotes to something LyX +/// can understand +string const normalize_filename(string const & name) +{ + Parser p(name); + ostringstream os; + while (p.good()) { + Token const & t = p.get_token(); + if (t.cat() != catEscape) + os << t.asInput(); + else if (t.cs() == "lyxdot") { + // This is used by LyX for simple dots in relative + // names + os << '.'; + p.skip_spaces(); + } else if (t.cs() == "space") { + os << ' '; + p.skip_spaces(); + } else if (t.cs() == "string") { + // Convert \string" to " and \string~ to ~ + Token const & n = p.next_token(); + if (n.asInput() != "\"" && n.asInput() != "~") + os << t.asInput(); + } else + os << t.asInput(); + } + // Strip quotes. This is a bit complicated (see latex_path()). + string full = os.str(); + if (!full.empty() && full[0] == '"') { + string base = removeExtension(full); + string ext = getExtension(full); + if (!base.empty() && base[base.length()-1] == '"') + // "a b" + // "a b".tex + return addExtension(trim(base, "\""), ext); + if (full[full.length()-1] == '"') + // "a b.c" + // "a b.c".tex + return trim(full, "\""); + } + return full; +} + + +/// Convert \p name from TeX convention (relative to master file) to LyX +/// convention (relative to .lyx file) if it is relative +void fix_child_filename(string & name) +{ + string const absMasterTeX = getMasterFilePath(true); + bool const isabs = FileName::isAbsolute(name); + // convert from "relative to .tex master" to absolute original path + if (!isabs) + name = makeAbsPath(name, absMasterTeX).absFileName(); + bool copyfile = copyFiles(); + string const absParentLyX = getParentFilePath(false); + string abs = name; + if (copyfile) { + // convert from absolute original path to "relative to master file" + string const rel = to_utf8(makeRelPath(from_utf8(name), + from_utf8(absMasterTeX))); + // re-interpret "relative to .tex file" as "relative to .lyx file" + // (is different if the master .lyx file resides in a + // different path than the master .tex file) + string const absMasterLyX = getMasterFilePath(false); + abs = makeAbsPath(rel, absMasterLyX).absFileName(); + // Do not copy if the new path is impossible to create. Example: + // absMasterTeX = "/foo/bar/" + // absMasterLyX = "/bar/" + // name = "/baz.eps" => new absolute name would be "/../baz.eps" + if (contains(name, "/../")) + copyfile = false; + } + if (copyfile) { + if (isabs) + name = abs; + else { + // convert from absolute original path to + // "relative to .lyx file" + name = to_utf8(makeRelPath(from_utf8(abs), + from_utf8(absParentLyX))); + } + } + else if (!isabs) { + // convert from absolute original path to "relative to .lyx file" + name = to_utf8(makeRelPath(from_utf8(name), + from_utf8(absParentLyX))); + } +} + + void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, - Context & context) + Context & context, string const rdelim) { Layout const * newlayout = 0; InsetLayout const * newinsetlayout = 0; @@ -2537,6 +2919,13 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, debugToken(cerr, t, flags); #endif + if (context.in_list_preamble + && p.next_token().cs() == context.layout->itemcommand()) { + // We are parsing a list preamble. End before first \item. + flags |= FLAG_LEAVE; + context.in_list_preamble = false; + } + if (flags & FLAG_ITEM) { if (t.cat() == catSpace) continue; @@ -2558,6 +2947,16 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, return; if (t.cat() == catEnd && (flags & FLAG_BRACE_LAST)) return; + string tok = t.asInput(); + // we only support delimiters with max 2 chars for now. + if (rdelim.size() > 1) + tok += p.next_token().asInput(); + if (t.cat() != catEscape && !rdelim.empty() + && tok == rdelim && (flags & FLAG_RDELIM)) { + if (rdelim.size() > 1) + p.get_token(); // eat rdelim + return; + } // If there is anything between \end{env} and \begin{env} we // don't need to output a separator. @@ -2605,14 +3004,17 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } - // Basic support for english quotes. This should be - // extended to other quotes, but is not so easy (a - // left english quote is the same as a right german - // quote...) + // Basic support for quotes. We try to disambiguate + // quotes from the context (e.g., a left english quote is + // the same as a right german quote...). + // Try to make a smart guess about the side + Token const prev = p.prev_token(); + bool const opening = (prev.cat() != catSpace && prev.character() != 0 + && prev.character() != '\n' && prev.character() != '~'); if (t.asInput() == "`" && p.next_token().asInput() == "`") { context.check_layout(os); begin_inset(os, "Quotes "); - os << "eld"; + os << guessQuoteStyle("eld", opening); end_inset(os); p.get_token(); skip_braces(p); @@ -2621,7 +3023,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (t.asInput() == "'" && p.next_token().asInput() == "'") { context.check_layout(os); begin_inset(os, "Quotes "); - os << "erd"; + os << guessQuoteStyle("erd", opening); end_inset(os); p.get_token(); skip_braces(p); @@ -2631,7 +3033,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (t.asInput() == ">" && p.next_token().asInput() == ">") { context.check_layout(os); begin_inset(os, "Quotes "); - os << "ald"; + os << guessQuoteStyle("ald", opening); end_inset(os); p.get_token(); skip_braces(p); @@ -2652,9 +3054,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (!has_chunk) { context.check_layout(os); begin_inset(os, "Quotes "); - //FIXME: this is a right danish quote; - // why not a left french quote? - os << "ard"; + os << guessQuoteStyle("ard", opening); end_inset(os); p.get_token(); skip_braces(p); @@ -2780,8 +3180,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, is_known(next.cs(), known_quotes) && end.cat() == catEnd) { // Something like {\textquoteright} (e.g. - // from writer2latex). LyX writes - // \textquoteright{}, so we may skip the + // from writer2latex). We may skip the // braces here for better readability. parse_text_snippet(p, os, FLAG_BRACE_LAST, outer, context); @@ -2946,10 +3345,10 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } - if (t.cs() == "item") { + // "item" by default, but could be something else + if (t.cs() == context.layout->itemcommand()) { string s; - bool const optarg = p.hasOpt(); - if (optarg) { + if (context.layout->labeltype == LABEL_MANUAL) { // FIXME: This swallows comments, but we cannot use // eat_whitespace() since we must not output // anything before the item. @@ -2963,26 +3362,29 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, // An item in an unknown list-like environment // FIXME: Do this in check_layout()! context.has_item = false; - if (optarg) - output_ert_inset(os, "\\item", context); - else - output_ert_inset(os, "\\item ", context); - } - if (optarg) { - if (context.layout->labeltype != LABEL_MANUAL) { - // handle option of itemize item - begin_inset(os, "Argument item:1\n"); - os << "status open\n"; - os << "\n\\begin_layout Plain Layout\n"; - Parser p2(s + ']'); - os << parse_text_snippet(p2, - FLAG_BRACK_LAST, outer, context); - // we must not use context.check_end_layout(os) - // because that would close the outer itemize layout - os << "\n\\end_layout\n"; - end_inset(os); - eat_whitespace(p, os, context, false); - } else if (!s.empty()) { + string item = "\\" + context.layout->itemcommand(); + if (!p.hasOpt()) + item += " "; + output_ert_inset(os, item, context); + } + if (context.layout->labeltype != LABEL_MANUAL) + output_arguments(os, p, outer, false, "item", context, + context.layout->itemargs()); + if (!context.list_preamble.empty()) { + // We have a list preamble. Output it here. + begin_inset(os, "Argument listpreamble:1"); + os << "\nstatus collapsed\n\n" + << "\\begin_layout Plain Layout\n\n" + << rtrim(context.list_preamble) + << "\n\\end_layout"; + end_inset(os); + context.list_preamble.clear(); + } + if (!context.list_extra_stuff.empty()) { + os << context.list_extra_stuff; + context.list_extra_stuff.clear(); + } + else if (!s.empty()) { // LyX adds braces around the argument, // so we need to remove them here. if (s.size() > 2 && s[0] == '{' && @@ -3004,7 +3406,6 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, os << ' '; eat_whitespace(p, os, context, false); } - } continue; } @@ -3012,20 +3413,17 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, context.set_item(); context.check_layout(os); eat_whitespace(p, os, context, false); - string label = convert_command_inset_arg(p.verbatimOption()); - string key = convert_command_inset_arg(p.verbatim_item()); - if (contains(label, '\\') || contains(key, '\\')) { - // LyX can't handle LaTeX commands in labels or keys - output_ert_inset(os, t.asInput() + '[' + label + - "]{" + p.verbatim_item() + '}', - context); - } else { - begin_command_inset(os, "bibitem", "bibitem"); - os << "label \"" << label << "\"\n" - << "key \"" << key << "\"\n" - << "literal \"true\"\n"; - end_inset(os); - } + string label = p.verbatimOption(); + pair lbl = convert_latexed_command_inset_arg(label); + bool const literal = !lbl.first; + label = literal ? subst(label, "\n", " ") : lbl.second; + string lit = literal ? "\"true\"" : "\"false\""; + string key = convert_literate_command_inset_arg(p.verbatim_item()); + begin_command_inset(os, "bibitem", "bibitem"); + os << "label \"" << label << "\"\n" + << "key \"" << key << "\"\n" + << "literal " << lit << "\n"; + end_inset(os); continue; } @@ -3135,7 +3533,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, parse_text_snippet(p, os, FLAG_ITEM, outer, context); if (!preamble.titleLayoutFound()) preamble.titleLayoutFound(newlayout->intitle); - set const & req = newlayout->requires(); + set const & req = newlayout->required(); set::const_iterator it = req.begin(); set::const_iterator en = req.end(); for (; it != en; ++it) @@ -3148,6 +3546,25 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } + // Before we look for the layout name with star and alone below, we check the layouts including + // the LateXParam, which might be one or several options or a star. + // The single '=' is meant here. + if (context.new_layout_allowed && + (newlayout = findLayout(context.textclass, t.cs(), true, p.getCommandLatexParam()))) { + // store the latexparam here. This is eaten in output_command_layout + context.latexparam = newlayout->latexparam(); + // write the layout + output_command_layout(os, p, outer, context, newlayout); + context.latexparam.clear(); + p.skip_spaces(); + if (!preamble.titleLayoutFound()) + preamble.titleLayoutFound(newlayout->intitle); + set const & req = newlayout->required(); + for (set::const_iterator it = req.begin(); it != req.end(); ++it) + preamble.registerAutomaticallyLoadedPackage(*it); + continue; + } + // Starred section headings // Must attempt to parse "Section*" before "Section". if ((p.next_token().asInput() == "*") && @@ -3159,7 +3576,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, p.skip_spaces(); if (!preamble.titleLayoutFound()) preamble.titleLayoutFound(newlayout->intitle); - set const & req = newlayout->requires(); + set const & req = newlayout->required(); for (set::const_iterator it = req.begin(); it != req.end(); ++it) preamble.registerAutomaticallyLoadedPackage(*it); continue; @@ -3173,7 +3590,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, p.skip_spaces(); if (!preamble.titleLayoutFound()) preamble.titleLayoutFound(newlayout->intitle); - set const & req = newlayout->requires(); + set const & req = newlayout->required(); for (set::const_iterator it = req.begin(); it != req.end(); ++it) preamble.registerAutomaticallyLoadedPackage(*it); continue; @@ -3182,7 +3599,6 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (t.cs() == "subfloat") { // the syntax is \subfloat[list entry][sub caption]{content} // if it is a table of figure depends on the surrounding float - // FIXME: second optional argument is not parsed p.skip_spaces(); // do nothing if there is no outer float if (!float_type.empty()) { @@ -3201,6 +3617,12 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, caption = parse_text_snippet(p, FLAG_BRACK_LAST, outer, context); has_caption = true; } + // In case we have two optional args, the second is the caption. + if (p.next_token().cat() != catEscape && + p.next_token().character() == '[') { + p.get_token(); // eat '[' + caption = parse_text_snippet(p, FLAG_BRACK_LAST, outer, context); + } // the content parse_text_in_inset(p, os, FLAG_ITEM, outer, context); // the caption comes always as the last @@ -3214,35 +3636,42 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, newcontext.check_layout(os); os << caption << "\n"; newcontext.check_end_layout(os); - // We don't need really a new paragraph, but - // we must make sure that the next item gets a \begin_layout. - //newcontext.new_paragraph(os); end_inset(os); p.skip_spaces(); + // close the layout we opened + os << "\n\\end_layout"; } - // We don't need really a new paragraph, but - // we must make sure that the next item gets a \begin_layout. - if (has_caption) - context.new_paragraph(os); end_inset(os); p.skip_spaces(); - context.check_end_layout(os); - // close the layout we opened - if (has_caption) - os << "\n\\end_layout\n"; } else { // if the float type is not supported or there is no surrounding float // output it as ERT + string opt_arg1; + string opt_arg2; if (p.hasOpt()) { - string opt_arg = convert_command_inset_arg(p.getArg('[', ']')); - output_ert_inset(os, t.asInput() + '[' + opt_arg + - "]{" + p.verbatim_item() + '}', context); - } else - output_ert_inset(os, t.asInput() + "{" + p.verbatim_item() + '}', context); + opt_arg1 = convert_literate_command_inset_arg(p.getFullOpt()); + if (p.hasOpt()) + opt_arg2 = convert_literate_command_inset_arg(p.getFullOpt()); + } + output_ert_inset(os, t.asInput() + opt_arg1 + opt_arg2 + + "{" + p.verbatim_item() + '}', context); } continue; } + if (t.cs() == "xymatrix") { + // we must open a new math because LyX's xy support is in math + context.check_layout(os); + begin_inset(os, "Formula "); + os << '$'; + os << "\\" << t.cs() << '{'; + parse_math(p, os, FLAG_ITEM, MATH_MODE); + os << '}' << '$'; + end_inset(os); + preamble.registerAutomaticallyLoadedPackage("xy"); + continue; + } + if (t.cs() == "includegraphics") { bool const clip = p.next_token().asInput() == "*"; if (clip) @@ -3304,9 +3733,9 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (opts.find("width") != opts.end()) os << "\twidth " << translate_len(opts["width"]) << '\n'; - if (opts.find("height") != opts.end()) + if (opts.find("totalheight") != opts.end()) os << "\theight " - << translate_len(opts["height"]) << '\n'; + << translate_len(opts["totalheight"]) << '\n'; if (opts.find("scale") != opts.end()) { istringstream iss(opts["scale"]); double val; @@ -3322,7 +3751,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, vector::const_iterator s = find(keys.begin(), keys.end(), "width"); if (s == keys.end()) - s = find(keys.begin(), keys.end(), "height"); + s = find(keys.begin(), keys.end(), "totalheight"); if (s == keys.end()) s = find(keys.begin(), keys.end(), "scale"); if (s != keys.end() && distance(s, a) > 0) @@ -3383,8 +3812,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, special << "trim,"; if (opts.find("viewport") != opts.end()) special << "viewport=" << opts["viewport"] << ','; - if (opts.find("totalheight") != opts.end()) - special << "totalheight=" << opts["totalheight"] << ','; + if (opts.find("height") != opts.end()) + special << "height=" << opts["height"] << ','; if (opts.find("type") != opts.end()) special << "type=" << opts["type"] << ','; if (opts.find("ext") != opts.end()) @@ -3447,7 +3876,9 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } - else if (t.cs() == "makeindex" || t.cs() == "maketitle") { + else if (t.cs() == "makeindex" + || ((t.cs() == "maketitle" || t.cs() == context.textclass.titlename()) + && context.textclass.titletype() == TITLE_COMMAND_AFTER)) { if (preamble.titleLayoutFound()) { // swallow this skip_spaces_braces(p); @@ -3488,18 +3919,29 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (t.cs() == "listof") { p.skip_spaces(true); - string const name = p.get_token().cs(); + string const name = p.verbatim_item(); if (context.textclass.floats().typeExist(name)) { context.check_layout(os); begin_inset(os, "FloatList "); os << name << "\n"; end_inset(os); - p.get_token(); // swallow second arg + p.verbatim_item(); // swallow second arg } else output_ert_inset(os, "\\listof{" + name + "}", context); continue; } + if (t.cs() == "theendnotes" + || (t.cs() == "printendnotes" + && p.next_token().asInput() != "*" + && !p.hasOpt())) { + context.check_layout(os); + begin_inset(os, "FloatList endnote\n"); + end_inset(os); + skip_spaces_braces(p); + continue; + } + if ((where = is_known(t.cs(), known_text_font_families))) { parse_text_attributes(p, os, FLAG_ITEM, outer, context, "\\family", context.font.family, @@ -3507,14 +3949,16 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } - if ((where = is_known(t.cs(), known_text_font_series))) { + // beamer has a \textbf{} inset + if (!p.hasOpt("<") && (where = is_known(t.cs(), known_text_font_series))) { parse_text_attributes(p, os, FLAG_ITEM, outer, context, "\\series", context.font.series, known_coded_font_series[where - known_text_font_series]); continue; } - if ((where = is_known(t.cs(), known_text_font_shapes))) { + // beamer has a \textit{} inset + if (!p.hasOpt("<") && (where = is_known(t.cs(), known_text_font_shapes))) { parse_text_attributes(p, os, FLAG_ITEM, outer, context, "\\shape", context.font.shape, known_coded_font_shapes[where - known_text_font_shapes]); @@ -3593,9 +4037,10 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } - if (t.cs() == "uuline" || t.cs() == "uwave" + // beamer has an \emph{} inset + if ((t.cs() == "uuline" || t.cs() == "uwave" || t.cs() == "emph" || t.cs() == "noun" - || t.cs() == "xout") { + || t.cs() == "xout") && !p.hasOpt("<")) { context.check_layout(os); os << "\n\\" << t.cs() << " on\n"; parse_text_snippet(p, os, FLAG_ITEM, outer, context); @@ -3606,11 +4051,16 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } - if (t.cs() == "lyxadded" || t.cs() == "lyxdeleted") { + if (t.cs() == "lyxadded" || t.cs() == "lyxdeleted" || t.cs() == "lyxobjdeleted" + || t.cs() == "lyxdisplayobjdeleted" || t.cs() == "lyxudisplayobjdeleted") { context.check_layout(os); + string initials; + if (p.hasOpt()) { + initials = p.getArg('[', ']'); + } string name = p.getArg('{', '}'); string localtime = p.getArg('{', '}'); - preamble.registerAuthor(name); + preamble.registerAuthor(name, initials); Author const & author = preamble.getAuthor(name); // from_asctime_utc() will fail if LyX decides to output the // time in the text language. @@ -3626,7 +4076,6 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, os << "\n\\change_deleted "; os << author.bufferId() << ' ' << ptime << '\n'; parse_text_snippet(p, os, FLAG_ITEM, outer, context); - bool dvipost = LaTeXPackages::isAvailable("dvipost"); bool xcolorulem = LaTeXPackages::isAvailable("ulem") && LaTeXPackages::isAvailable("xcolor"); // No need to test for luatex, since luatex comes in @@ -3639,9 +4088,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, preamble.registerAutomaticallyLoadedPackage("pdfcolmk"); } } else { - if (dvipost) { - preamble.registerAutomaticallyLoadedPackage("dvipost"); - } else if (xcolorulem) { + if (xcolorulem) { preamble.registerAutomaticallyLoadedPackage("ulem"); preamble.registerAutomaticallyLoadedPackage("xcolor"); } @@ -3662,9 +4109,14 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } - if (t.cs() == "texttoptiebar" || t.cs() == "textbottomtiebar") { + if ((preamble.isPackageUsed("tipa") && t.cs() == "t" && p.next_token().asInput() == "*") + || t.cs() == "texttoptiebar" || t.cs() == "textbottomtiebar") { context.check_layout(os); - begin_inset(os, "IPADeco " + t.cs().substr(4) + "\n"); + if (t.cs() == "t") + // swallow star + p.get_token(); + string const type = (t.cs() == "t") ? "bottomtiebar" : t.cs().substr(4); + begin_inset(os, "IPADeco " + type + "\n"); os << "status open\n"; parse_text_in_inset(p, os, FLAG_ITEM, outer, context); end_inset(os); @@ -3728,8 +4180,12 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (t.cs() == "href") { context.check_layout(os); - string target = convert_command_inset_arg(p.verbatim_item()); - string name = convert_command_inset_arg(p.verbatim_item()); + string target = convert_literate_command_inset_arg(p.verbatim_item()); + string name = p.verbatim_item(); + pair nm = convert_latexed_command_inset_arg(name); + bool const literal = !nm.first; + name = literal ? subst(name, "\n", " ") : nm.second; + string lit = literal ? "\"true\"" : "\"false\""; string type; size_t i = target.find(':'); if (i != string::npos) { @@ -3746,7 +4202,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, os << "target \"" << target << "\"\n"; if (type == "mailto:" || type == "file:") os << "type \"" << type << "\"\n"; - os << "literal \"true\"\n"; + os << "literal " << lit << "\n"; end_inset(os); skip_spaces_braces(p); continue; @@ -3794,21 +4250,37 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } - // handle refstyle first to catch \eqref which can also occur - // without refstyle. Only recognize these commands if + // Handle refstyle first in order to to catch \eqref, because this + // can also occur without refstyle. Only recognize these commands if // refstyle.sty was found in the preamble (otherwise \eqref // and user defined ref commands could be misdetected). - if ((where = is_known(t.cs(), known_refstyle_commands)) + // We uncapitalize the input in order to catch capitalized commands + // such as \Eqref. + if ((where = is_known(uncapitalize(t.cs()), known_refstyle_commands)) && preamble.refstyle()) { + string const cap = isCapitalized(t.cs()) ? "true" : "false"; + string plural = "false"; + // Catch the plural option [s] + if (p.hasOpt()) { + string const opt = p.getOpt(); + if (opt == "[s]") + plural = "true"; + else { + // LyX does not yet support other optional arguments of ref commands + output_ert_inset(os, t.asInput() + opt + "{" + + p.verbatim_item() + '}', context); + continue; + } + } context.check_layout(os); begin_command_inset(os, "ref", "formatted"); os << "reference \""; os << known_refstyle_prefixes[where - known_refstyle_commands] << ":"; - os << convert_command_inset_arg(p.verbatim_item()) + os << convert_literate_command_inset_arg(p.getArg('{', '}')) << "\"\n"; - os << "plural \"false\"\n"; - os << "caps \"false\"\n"; + os << "plural \"" << plural << "\"\n"; + os << "caps \"" << cap << "\"\n"; os << "noprefix \"false\"\n"; end_inset(os); preamble.registerAutomaticallyLoadedPackage("refstyle"); @@ -3825,7 +4297,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, begin_command_inset(os, "ref", known_coded_ref_commands[where - known_ref_commands]); os << "reference \"" - << convert_command_inset_arg(p.verbatim_item()) + << convert_literate_command_inset_arg(p.verbatim_item()) << "\"\n"; os << "plural \"false\"\n"; os << "caps \"false\"\n"; @@ -3837,8 +4309,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, preamble.registerAutomaticallyLoadedPackage("prettyref"); } else { // LyX does not yet support optional arguments of ref commands - output_ert_inset(os, t.asInput() + '[' + opt + "]{" + - p.verbatim_item() + '}', context); + output_ert_inset(os, t.asInput() + opt + "{" + + p.verbatim_item() + '}', context); } continue; } @@ -3882,24 +4354,34 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, before.erase(); after.erase(); } + bool literal = false; + pair aft; + pair bef; // remove the brackets around after and before if (!after.empty()) { after.erase(0, 1); after.erase(after.length() - 1, 1); - after = convert_command_inset_arg(after); + aft = convert_latexed_command_inset_arg(after); + literal = !aft.first; + after = literal ? subst(after, "\n", " ") : aft.second; } if (!before.empty()) { before.erase(0, 1); before.erase(before.length() - 1, 1); - before = convert_command_inset_arg(before); + bef = convert_latexed_command_inset_arg(before); + literal |= !bef.first; + before = literal ? subst(before, "\n", " ") : bef.second; + if (literal && !after.empty()) + after = subst(after, "\n", " "); } + string lit = literal ? "\"true\"" : "\"false\""; begin_command_inset(os, "citation", command); os << "after " << '"' << after << '"' << "\n"; os << "before " << '"' << before << '"' << "\n"; os << "key \"" - << convert_command_inset_arg(p.verbatim_item()) + << convert_literate_command_inset_arg(p.verbatim_item()) << "\"\n" - << "literal \"true\"\n"; + << "literal " << lit << "\n"; end_inset(os); // Need to set the cite engine if natbib is loaded by // the document class directly @@ -3972,70 +4454,102 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, before.erase(); after.erase(); } + bool literal = false; + pair aft; + pair bef; // remove the brackets around after and before if (!after.empty()) { after.erase(0, 1); after.erase(after.length() - 1, 1); - after = convert_command_inset_arg(after); + aft = convert_latexed_command_inset_arg(after); + literal = !aft.first; + after = literal ? subst(after, "\n", " ") : aft.second; } if (!before.empty()) { before.erase(0, 1); before.erase(before.length() - 1, 1); - before = convert_command_inset_arg(before); + bef = convert_latexed_command_inset_arg(before); + literal |= !bef.first; + before = literal ? subst(before, "\n", " ") : bef.second; } string keys, pretextlist, posttextlist; if (qualified) { - map pres; - map posts; + vector> pres, posts, preslit, postslit; vector lkeys; // text before the citation - string lbefore; + string lbefore, lbeforelit; // text after the citation - string lafter; + string lafter, lafterlit; string lkey; + pair laft, lbef; while (true) { get_cite_arguments(p, true, lbefore, lafter); // remove the brackets around after and before if (!lafter.empty()) { lafter.erase(0, 1); lafter.erase(lafter.length() - 1, 1); - lafter = convert_command_inset_arg(lafter); + laft = convert_latexed_command_inset_arg(lafter); + literal |= !laft.first; + lafter = laft.second; + lafterlit = subst(lafter, "\n", " "); } if (!lbefore.empty()) { lbefore.erase(0, 1); lbefore.erase(lbefore.length() - 1, 1); - lbefore = convert_command_inset_arg(lbefore); + lbef = convert_latexed_command_inset_arg(lbefore); + literal |= !lbef.first; + lbefore = lbef.second; + lbeforelit = subst(lbefore, "\n", " "); } - if (lbefore.empty() && lafter == "[]") + if (lbefore.empty() && lafter == "[]") { // avoid \cite[]{a} lafter.erase(); + lafterlit.erase(); + } else if (lbefore == "[]" && lafter == "[]") { // avoid \cite[][]{a} lbefore.erase(); lafter.erase(); + lbeforelit.erase(); + lafterlit.erase(); } lkey = p.getArg('{', '}'); if (lkey.empty()) break; - if (!lbefore.empty()) - pres.insert(make_pair(lkey, lbefore)); - if (!lafter.empty()) - posts.insert(make_pair(lkey, lafter)); + pres.push_back(make_pair(lkey, lbefore)); + preslit.push_back(make_pair(lkey, lbeforelit)); + posts.push_back(make_pair(lkey, lafter)); + postslit.push_back(make_pair(lkey, lafterlit)); lkeys.push_back(lkey); } - keys = convert_command_inset_arg(getStringFromVector(lkeys)); + keys = convert_literate_command_inset_arg(getStringFromVector(lkeys)); + if (literal) { + pres = preslit; + posts = postslit; + } for (auto const & ptl : pres) { if (!pretextlist.empty()) pretextlist += '\t'; - pretextlist += ptl.first + " " + ptl.second; + pretextlist += ptl.first; + if (!ptl.second.empty()) + pretextlist += " " + ptl.second; } for (auto const & potl : posts) { if (!posttextlist.empty()) posttextlist += '\t'; - posttextlist += potl.first + " " + potl.second; + posttextlist += potl.first; + if (!potl.second.empty()) + posttextlist += " " + potl.second; } } else - keys = convert_command_inset_arg(p.verbatim_item()); + keys = convert_literate_command_inset_arg(p.verbatim_item()); + if (literal) { + if (!after.empty()) + after = subst(after, "\n", " "); + if (!before.empty()) + before = subst(after, "\n", " "); + } + string lit = literal ? "\"true\"" : "\"false\""; begin_command_inset(os, "citation", command); os << "after " << '"' << after << '"' << "\n"; os << "before " << '"' << before << '"' << "\n"; @@ -4046,7 +4560,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, os << "pretextlist " << '"' << pretextlist << '"' << "\n"; if (!posttextlist.empty()) os << "posttextlist " << '"' << posttextlist << '"' << "\n"; - os << "literal \"true\"\n"; + os << "literal " << lit << "\n"; end_inset(os); // Need to set the cite engine if biblatex is loaded by // the document class directly @@ -4092,19 +4606,32 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, "package options if you used an\n" "earlier jurabib version." << endl; } + bool literal = false; + pair aft; + pair bef; + // remove the brackets around after and before if (!after.empty()) { after.erase(0, 1); after.erase(after.length() - 1, 1); + aft = convert_latexed_command_inset_arg(after); + literal = !aft.first; + after = literal ? subst(after, "\n", " ") : aft.second; } if (!before.empty()) { before.erase(0, 1); before.erase(before.length() - 1, 1); + bef = convert_latexed_command_inset_arg(before); + literal |= !bef.first; + before = literal ? subst(before, "\n", " ") : bef.second; + if (literal && !after.empty()) + after = subst(after, "\n", " "); } + string lit = literal ? "\"true\"" : "\"false\""; begin_command_inset(os, "citation", command); os << "after " << '"' << after << "\"\n" << "before " << '"' << before << "\"\n" << "key " << '"' << citation << "\"\n" - << "literal \"true\"\n"; + << "literal " << lit << "\n"; end_inset(os); // Need to set the cite engine if jurabib is loaded by // the document class directly @@ -4116,15 +4643,19 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (t.cs() == "cite" || t.cs() == "nocite") { context.check_layout(os); - string after = convert_command_inset_arg(p.getArg('[', ']')); - string key = convert_command_inset_arg(p.verbatim_item()); + string after = p.getArg('[', ']'); + pair aft = convert_latexed_command_inset_arg(after); + bool const literal = !aft.first; + after = literal ? subst(after, "\n", " ") : aft.second; + string lit = literal ? "\"true\"" : "\"false\""; + string key = convert_literate_command_inset_arg(p.verbatim_item()); // store the case that it is "\nocite{*}" to use it later for // the BibTeX inset if (key != "*") { begin_command_inset(os, "citation", t.cs()); os << "after " << '"' << after << "\"\n" << "key " << '"' << key << "\"\n" - << "literal \"true\"\n"; + << "literal " << lit << "\n"; end_inset(os); } else if (t.cs() == "nocite") btprint = key; @@ -4149,15 +4680,27 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (t.cs() == "nomenclature") { context.check_layout(os); begin_command_inset(os, "nomenclature", "nomenclature"); - string prefix = convert_command_inset_arg(p.getArg('[', ']')); + string prefix = convert_literate_command_inset_arg(p.getArg('[', ']')); if (!prefix.empty()) os << "prefix " << '"' << prefix << '"' << "\n"; - os << "symbol " << '"' - << convert_command_inset_arg(p.verbatim_item()); + string symbol = p.verbatim_item(); + pair sym = convert_latexed_command_inset_arg(symbol); + bool literal = !sym.first; + string description = p.verbatim_item(); + pair desc = convert_latexed_command_inset_arg(description); + literal |= !desc.first; + if (literal) { + symbol = subst(symbol, "\n", " "); + description = subst(description, "\n", " "); + } else { + symbol = sym.second; + description = desc.second; + } + string lit = literal ? "\"true\"" : "\"false\""; + os << "symbol " << '"' << symbol; os << "\"\ndescription \"" - << convert_command_inset_arg(p.verbatim_item()) - << "\"\n" - << "literal \"true\"\n"; + << description << "\"\n" + << "literal " << lit << "\n"; end_inset(os); preamble.registerAutomaticallyLoadedPackage("nomencl"); continue; @@ -4167,7 +4710,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, context.check_layout(os); begin_command_inset(os, "label", "label"); os << "name \"" - << convert_command_inset_arg(p.verbatim_item()) + << convert_literate_command_inset_arg(p.verbatim_item()) << "\"\n"; end_inset(os); continue; @@ -4185,6 +4728,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, // so simply skip it. parse_text_snippet(p, FLAG_ITEM, false, context); } + eat_whitespace(p, os, context, true); continue; } @@ -4230,7 +4774,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, // via \settowidth{\nomlabelwidth}{***} cannot be supported // because the user could have set anything, not only the width // of the longest label (which would be width_type = "auto") - string label = convert_command_inset_arg(p.getArg('{', '}')); + string label = convert_literate_command_inset_arg(p.getArg('{', '}')); if (label.empty() && width_type.empty()) width_type = "none"; os << "set_width \"" << width_type << "\"\n"; @@ -4257,7 +4801,13 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if ((where = is_known(t.cs(), known_quotes))) { context.check_layout(os); begin_inset(os, "Quotes "); - os << known_coded_quotes[where - known_quotes]; + string quotetype = known_coded_quotes[where - known_quotes]; + // try to make a smart guess about the side + Token const prev = p.prev_token(); + bool const opening = (prev.cat() != catSpace && prev.character() != 0 + && prev.character() != '\n' && prev.character() != '~'); + quotetype = guessQuoteStyle(quotetype, opening); + os << quotetype; end_inset(os); // LyX adds {} after the quote, so we have to eat // spaces here if there are any before a possible @@ -4268,7 +4818,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, } if ((where = is_known(t.cs(), known_sizes)) && - context.new_layout_allowed) { + context.new_layout_allowed) { context.check_layout(os); TeXFont const oldFont = context.font; context.font.size = known_coded_sizes[where - known_sizes]; @@ -4433,13 +4983,6 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } - if (t.cs() == "textquotedbl") { - context.check_layout(os); - os << "\""; - skip_braces(p); - continue; - } - if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#" || t.cs() == "$" || t.cs() == "{" || t.cs() == "}" || t.cs() == "%" || t.cs() == "-") { @@ -4519,12 +5062,61 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, } if (t.cs() == "input" || t.cs() == "include" - || t.cs() == "verbatiminput") { + || t.cs() == "verbatiminput" + || t.cs() == "lstinputlisting" + || t.cs() == "inputminted") { string name = t.cs(); - if (t.cs() == "verbatiminput" + if (name == "verbatiminput" && p.next_token().asInput() == "*") name += p.get_token().asInput(); context.check_layout(os); + string lstparams; + bool literal = false; + if (name == "lstinputlisting" && p.hasOpt()) { + lstparams = p.getArg('[', ']'); + pair oa = convert_latexed_command_inset_arg(lstparams); + literal = !oa.first; + if (literal) + lstparams = subst(lstparams, "\n", " "); + else + lstparams = oa.second; + } else if (name == "inputminted") { + name = "lstinputlisting"; + string const lang = p.getArg('{', '}'); + if (lang != "tex") { + string cmd = "\\inputminted{" + lang + "}{"; + cmd += p.getArg('{', '}') + "}"; + output_ert_inset(os, cmd, context); + continue; + } + if (prefixIs(minted_nonfloat_caption, "[t]")) { + minted_nonfloat_caption.erase(0,3); + // extract label and caption from the already produced LyX code + vector nfc = getVectorFromString(minted_nonfloat_caption, "\n"); + string const caption = nfc.front(); + string label; + vector::iterator it = + find(nfc.begin(), nfc.end(), "LatexCommand label"); + if (it != nfc.end()) { + ++it; + if (it != nfc.end()) + label = *it; + label = support::split(label, '"'); + label.pop_back(); + } + minted_nonfloat_caption.clear(); + lstparams = "caption=" + caption; + if (!label.empty()) + lstparams += ",label=" + label; + pair oa = convert_latexed_command_inset_arg(lstparams); + literal = !oa.first; + if (literal) + lstparams = subst(lstparams, "\n", " "); + else + lstparams = oa.second; + } + } + string lit = literal ? "\"true\"" : "\"false\""; string filename(normalize_filename(p.getArg('{', '}'))); string const path = getMasterFilePath(true); // We want to preserve relative / absolute filenames, @@ -4632,6 +5224,9 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, outname = subst(outname, "\"", "\\\""); os << "preview false\n" "filename \"" << outname << "\"\n"; + if (!lstparams.empty()) + os << "lstparams \"" << lstparams << "\"\n"; + os << "literal " << lit << "\n"; if (t.cs() == "verbatiminput") preamble.registerAutomaticallyLoadedPackage("verbatim"); } @@ -4733,6 +5328,15 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, bibliographystyle.clear(); } os << "options " << '"' << BibOpts << '"' << "\n"; + if (p.getEncoding() != preamble.docencoding) { + Encoding const * const enc = encodings.fromIconvName( + p.getEncoding(), Encoding::inputenc, true); + if (!enc) { + cerr << "Unknown bib encoding " << p.getEncoding() + << ". Ignoring." << std::endl; + } else + os << "encoding " << '"' << enc->name() << '"' << "\n"; + } end_inset(os); continue; } @@ -4773,6 +5377,23 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, os << "options " << '"' << BibOpts << '"' << "\n"; if (!bbloptions.empty()) os << "biblatexopts " << '"' << bbloptions << '"' << "\n"; + if (!preamble.bibencoding.empty()) { + Encoding const * const enc = encodings.fromLaTeXName( + preamble.bibencoding, Encoding::inputenc, true); + if (!enc) { + cerr << "Unknown bib encoding " << preamble.bibencoding + << ". Ignoring." << std::endl; + } else + os << "encoding " << '"' << enc->name() << '"' << "\n"; + } + string bibfileencs; + for (auto const & bf : preamble.biblatex_encodings) { + if (!bibfileencs.empty()) + bibfileencs += "\t"; + bibfileencs += bf; + } + if (!bibfileencs.empty()) + os << "file_encodings " << '"' << bibfileencs << '"' << "\n"; end_inset(os); need_commentbib = false; continue; @@ -5187,6 +5808,58 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } + // Before we look for the layout name alone below, we check the layouts including the LateXParam, which + // might be one or several options or a star. + // The single '=' is meant here. + if ((newinsetlayout = findInsetLayout(context.textclass, starredname, true, p.getCommandLatexParam()))) { + if (starred) + p.get_token(); + p.skip_spaces(); + context.check_layout(os); + // store the latexparam here. This is eaten in parse_text_in_inset + context.latexparam = newinsetlayout->latexparam(); + docstring name = newinsetlayout->name(); + bool const caption = name.find(from_ascii("Caption:")) == 0; + if (caption) { + // Already done for floating minted listings. + if (minted_float.empty()) { + begin_inset(os, "Caption "); + os << to_utf8(name.substr(8)) << '\n'; + } + } else { + // FIXME: what do we do if the prefix is not Flex: ? + if (prefixIs(name, from_ascii("Flex:"))) + name.erase(0, 5); + begin_inset(os, "Flex "); + os << to_utf8(name) << '\n' + << "status collapsed\n"; + } + if (!minted_float.empty()) { + parse_text_snippet(p, os, FLAG_ITEM, false, context); + } else if (newinsetlayout->isPassThru()) { + // set catcodes to verbatim early, just in case. + p.setCatcodes(VERBATIM_CATCODES); + string delim = p.get_token().asInput(); + if (delim != "{") + cerr << "Warning: bad delimiter for command " << t.asInput() << endl; + //FIXME: handle error condition + string const arg = p.verbatimStuff("}").second; + Context newcontext(true, context.textclass); + if (newinsetlayout->forcePlainLayout()) + newcontext.layout = &context.textclass.plainLayout(); + output_ert(os, arg, newcontext); + } else + parse_text_in_inset(p, os, FLAG_ITEM, false, context, newinsetlayout); + context.latexparam.clear(); + if (caption) + p.skip_spaces(); + // Minted caption insets are not closed here because + // we collect everything into the caption. + if (minted_float.empty()) + end_inset(os); + continue; + } + // The single '=' is meant here. if ((newinsetlayout = findInsetLayout(context.textclass, starredname, true))) { if (starred) @@ -5348,7 +6021,9 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, // and math commands may be invalid (bug 6797) string name = t.asInput(); // handle the dingbats, cyrillic and greek - if (name == "\\ding" || name == "\\textcyr" || + if (name == "\\textcyr") + name = "\\textcyrillic"; + if (name == "\\ding" || name == "\\textcyrillic" || (name == "\\textgreek" && !preamble.usePolyglossia())) name = name + '{' + p.getArg('{', '}') + '}'; // handle the ifsym characters @@ -5475,8 +6150,18 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, p.get_token(); // Eat '*' name += '*'; } - if (!parse_command(name, p, os, outer, context)) + if (!parse_command(name, p, os, outer, context)) { output_ert_inset(os, name, context); + // Try to handle options of unknown commands: + // Look if we have optional arguments, + // and if so, put the brackets in ERT. + while (p.hasOpt()) { + p.get_token(); // eat '[' + output_ert_inset(os, "[", context); + os << parse_text_snippet(p, FLAG_BRACK_LAST, outer, context); + output_ert_inset(os, "]", context); + } + } } } } @@ -5574,6 +6259,14 @@ void check_comment_bib(ostream & os, Context & context) } if (!bibfiles.empty()) os << "bibfiles " << '"' << bibfiles << '"' << "\n"; + string bibfileencs; + for (auto const & bf : preamble.biblatex_encodings) { + if (!bibfileencs.empty()) + bibfileencs += "\t"; + bibfileencs += bf; + } + if (!bibfileencs.empty()) + os << "file_encodings " << '"' << bibfileencs << '"' << "\n"; end_inset(os);// Bibtex os << "\\end_layout\n"; end_inset(os);// Note