X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ftex2lyx%2Ftext.cpp;h=f9201cd1a26f73f307979729869a3f243d5d0683;hb=a2b21e3cd4bbfd42e59161143eba6e7681aaa93f;hp=b87e44d61d810e5ba9287247f6a8f804c8fd9ccb;hpb=02d37932d0e3000e31d726593d953aa5a506a474;p=lyx.git diff --git a/src/tex2lyx/text.cpp b/src/tex2lyx/text.cpp index b87e44d61d..f9201cd1a2 100644 --- a/src/tex2lyx/text.cpp +++ b/src/tex2lyx/text.cpp @@ -528,6 +528,35 @@ string guessQuoteStyle(string in, bool const opening) } +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 @@ -729,7 +758,8 @@ InsetLayout const * findInsetLayout(TextClass const & textclass, string const & } -void eat_whitespace(Parser &, ostream &, Context &, bool); +void eat_whitespace(Parser &, ostream &, Context &, bool eatParagraph, + bool eatNewline = true); /*! @@ -1565,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) @@ -1582,631 +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 { + Floating const & fl = parent_context.textclass.floats() + .getType(unstarred_name); + if (!fl.floattype().empty() && fl.usesFloatPkg()) + preamble.registerAutomaticallyLoadedPackage("float"); + } - 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"); - } + 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; + } - 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"); - } + 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"); + break; + } - 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); + 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"); + break; } - p.getArg('{', '}'); - p.skip_spaces(true); - Token t = p.get_token(); - bool shaded = false; - if (t.asInput() == "\\begin") { + + 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); - if (p.getArg('{', '}') == "shaded") - shaded = true; + 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; } - 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"); - } + 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; + } - 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"); + 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; } - // 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 (name == "IPA") { + 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) { - 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; + begin_inset(os, "IPA\n"); parse_text_in_inset(p, os, FLAG_END, outer, parent_context); - parent_context.font.language = lang_old; + end_inset(os); + p.skip_spaces(); + preamble.registerAutomaticallyLoadedPackage("tipa"); + preamble.registerAutomaticallyLoadedPackage("tipx"); + break; + } + + 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; } - 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"); - } + 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; + } - 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); + 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; } - 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 (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; } - if (t.asInput() == "\\btPrintNotCited") { - p.skip_spaces(true); - os << "btprint " << '"' << "btPrintNotCited" << '"' << "\n"; + + 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; } - if (t.asInput() == "\\btPrintAll") { - p.skip_spaces(true); - os << "btprint " << '"' << "btPrintAll" << '"' << "\n"; + + // 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(); } - 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"); - } + // 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(); + } - 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); + 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; } - parent_context.check_layout(os); - if (minted_env && minted_float_has_caption) { + + 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); - os << content << "\n"; - if (!caption.empty()) + 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"; - 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) { + 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"; } - 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; } - 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 - p.pushPosition(); - bool found_end_minted = false; - while (!found_end_minted && p.good()) { + 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(); - 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.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(); } - p.popPosition(); + parse_listings(p, os, parent_context, false, use_minted); + 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. - // 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) - else 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 (!parent_context.new_layout_allowed) { + parse_unknown_environment(p, name, os, FLAG_END, outer, + parent_context); + break; } - 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(); - } - // 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"); - end_inset(os); - newcontext.check_end_layout(os); - } - switch (context.layout->latextype) { - case LATEX_LIST_ENVIRONMENT: - context.add_par_extra_stuff("\\labelwidthstring " - + p.verbatim_item() + '\n'); + // 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; - case LATEX_BIB_ENVIRONMENT: - p.verbatim_item(); // swallow next arg + } + + // 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; - default: + } + + // 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; } - context.check_deeper(os); - if (newlayout->keepempty) { - // We need to start a new paragraph - // even if it is empty. - context.new_paragraph(os); + + 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; } - // 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) { - ostringstream oss; - output_arguments(oss, p, outer, false, string(), context, - context.layout->latexargs()); - context.list_extra_stuff = oss.str(); - } - 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->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); - } - - 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(); - } - 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, + 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(); @@ -2214,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()) { @@ -2232,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(); } @@ -2243,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; } @@ -2689,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; @@ -3133,6 +3370,16 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, 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(); @@ -3286,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) @@ -3312,13 +3559,12 @@ 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; } - // Starred section headings // Must attempt to parse "Section*" before "Section". if ((p.next_token().asInput() == "*") && @@ -3330,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; @@ -3344,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; @@ -3487,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; @@ -3505,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) @@ -3566,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()) @@ -3630,7 +3876,9 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, continue; } - else if (t.cs() == "makeindex" || t.cs() == "maketitle" || t.cs() == "makebeamertitle") { + 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); @@ -3671,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, @@ -3792,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. @@ -3812,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 @@ -3825,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"); } @@ -3989,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_literate_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"); @@ -4032,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; } @@ -4197,13 +4474,13 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, } string keys, pretextlist, posttextlist; if (qualified) { - map pres, posts, preslit, postslit; + vector> pres, posts, preslit, postslit; vector lkeys; // text before the citation string lbefore, lbeforelit; // text after the citation string lafter, lafterlit; - string lkey; + string lkey; pair laft, lbef; while (true) { get_cite_arguments(p, true, lbefore, lafter); @@ -4214,7 +4491,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, laft = convert_latexed_command_inset_arg(lafter); literal |= !laft.first; lafter = laft.second; - lafterlit = subst(lbefore, "\n", " "); + lafterlit = subst(lafter, "\n", " "); } if (!lbefore.empty()) { lbefore.erase(0, 1); @@ -4239,14 +4516,10 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, lkey = p.getArg('{', '}'); if (lkey.empty()) break; - if (!lbefore.empty()) { - pres.insert(make_pair(lkey, lbefore)); - preslit.insert(make_pair(lkey, lbeforelit)); - } - if (!lafter.empty()) { - posts.insert(make_pair(lkey, lafter)); - postslit.insert(make_pair(lkey, lafterlit)); - } + 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_literate_command_inset_arg(getStringFromVector(lkeys)); @@ -4257,12 +4530,16 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, 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_literate_command_inset_arg(p.verbatim_item()); @@ -5051,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; } @@ -5091,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; @@ -5847,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); + } + } } } } @@ -5946,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