X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ftex2lyx%2Ftext.cpp;h=2b5d0acb0bcce21293a0d124c28f565ea8ae7167;hb=55a3dd7b346d29a52ba305a4558e9e380ef50f47;hp=4543c488abaac5f973b9896f6c207e8303dff37e;hpb=812c27d7936145ae0435bb693ad6e5db25a330d1;p=lyx.git diff --git a/src/tex2lyx/text.cpp b/src/tex2lyx/text.cpp index 4543c488ab..2b5d0acb0b 100644 --- a/src/tex2lyx/text.cpp +++ b/src/tex2lyx/text.cpp @@ -19,14 +19,17 @@ #include "Context.h" #include "Encoding.h" #include "FloatList.h" +#include "LaTeXPackages.h" #include "Layout.h" #include "Length.h" +#include "Preamble.h" #include "support/lassert.h" #include "support/convert.h" #include "support/FileName.h" #include "support/filetools.h" #include "support/lstrings.h" +#include "support/lyxtime.h" #include #include @@ -41,10 +44,15 @@ namespace lyx { void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer, - Context const & context) + Context const & context, InsetLayout const * layout) { + bool const forcePlainLayout = + layout ? layout->forcePlainLayout() : false; Context newcontext(true, context.textclass); - newcontext.font = context.font; + if (forcePlainLayout) + newcontext.layout = &context.textclass.plainLayout(); + else + newcontext.font = context.font; parse_text(p, os, flags, outer, newcontext); newcontext.check_end_layout(os); } @@ -52,6 +60,17 @@ 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) +{ + 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); +} + /// parses a paragraph snippet, useful for example for \\emph{...} void parse_text_snippet(Parser & p, ostream & os, unsigned flags, bool outer, Context & context) @@ -93,6 +112,9 @@ string parse_text_snippet(Parser & p, unsigned flags, const bool outer, char const * const known_ref_commands[] = { "ref", "pageref", "vref", "vpageref", "prettyref", "eqref", 0 }; +char const * const known_coded_ref_commands[] = { "ref", "pageref", "vref", + "vpageref", "formatted", "eqref", 0 }; + /*! * natbib commands. * The starred forms are also known except for "citefullauthor", @@ -130,8 +152,8 @@ char const * const known_coded_quotes[] = { "prd", "ard", "ard", "ard", char const * const known_sizes[] = { "tiny", "scriptsize", "footnotesize", "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0}; -/// the same as known_sizes with .lyx names plus a default entry -char const * const known_coded_sizes[] = { "default", "tiny", "scriptsize", "footnotesize", +/// the same as known_sizes with .lyx names +char const * const known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize", "small", "normal", "large", "larger", "largest", "huge", "giant", 0}; /// LaTeX 2.09 names for font families @@ -141,7 +163,12 @@ char const * const known_old_font_families[] = { "rm", "sf", "tt", 0}; char const * const known_font_families[] = { "rmfamily", "sffamily", "ttfamily", 0}; -/// the same as known_old_font_families and known_font_families with .lyx names +/// LaTeX names for font family changing commands +char const * const known_text_font_families[] = { "textrm", "textsf", +"texttt", 0}; + +/// The same as known_old_font_families, known_font_families and +/// known_text_font_families with .lyx names char const * const known_coded_font_families[] = { "roman", "sans", "typewriter", 0}; @@ -151,7 +178,11 @@ char const * const known_old_font_series[] = { "bf", 0}; /// LaTeX names for font series char const * const known_font_series[] = { "bfseries", "mdseries", 0}; -/// the same as known_old_font_series and known_font_series with .lyx names +/// LaTeX names for font series changing commands +char const * const known_text_font_series[] = { "textbf", "textmd", 0}; + +/// The same as known_old_font_series, known_font_series and +/// known_text_font_series with .lyx names char const * const known_coded_font_series[] = { "bold", "medium", 0}; /// LaTeX 2.09 names for font shapes @@ -161,10 +192,23 @@ char const * const known_old_font_shapes[] = { "it", "sl", "sc", 0}; char const * const known_font_shapes[] = { "itshape", "slshape", "scshape", "upshape", 0}; -/// the same as known_old_font_shapes and known_font_shapes with .lyx names +/// LaTeX names for font shape changing commands +char const * const known_text_font_shapes[] = { "textit", "textsl", "textsc", +"textup", 0}; + +/// The same as known_old_font_shapes, known_font_shapes and +/// known_text_font_shapes with .lyx names char const * const known_coded_font_shapes[] = { "italic", "slanted", "smallcaps", "up", 0}; +/// Known special characters which need skip_spaces_braces() afterwards +char const * const known_special_chars[] = {"ldots", "lyxarrow", +"textcompwordmark", "slash", 0}; + +/// the same as known_special_chars with .lyx names +char const * const known_coded_special_chars[] = {"ldots{}", "menuseparator", +"textcompwordmark{}", "slash{}", 0}; + /*! * Graphics file extensions known by the dvips driver of the graphics package. * These extensions are used to complete the filename of an included @@ -191,15 +235,18 @@ char const * const known_pdftex_graphics_formats[] = {"png", "pdf", "jpg", char const * const known_tex_extensions[] = {"tex", 0}; /// spaces known by InsetSpace -char const * const known_spaces[] = { " ", "space", ",", "thinspace", "quad", -"qquad", "enspace", "enskip", "negthinspace", "hfill", "dotfill", "hrulefill", -"leftarrowfill", "rightarrowfill", "upbracefill", "downbracefill", 0}; +char const * const known_spaces[] = { " ", "space", ",", +"thinspace", "quad", "qquad", "enspace", "enskip", +"negthinspace", "negmedspace", "negthickspace", "textvisiblespace", +"hfill", "dotfill", "hrulefill", "leftarrowfill", "rightarrowfill", +"upbracefill", "downbracefill", 0}; /// the same as known_spaces with .lyx names char const * const known_coded_spaces[] = { "space{}", "space{}", "thinspace{}", "thinspace{}", "quad{}", "qquad{}", "enspace{}", "enskip{}", -"negthinspace{}", "hfill{}", "dotfill{}", "hrulefill{}", "leftarrowfill{}", -"rightarrowfill{}", "upbracefill{}", "downbracefill{}", 0}; +"negthinspace{}", "negmedspace{}", "negthickspace{}", "textvisiblespace{}", +"hfill{}", "dotfill{}", "hrulefill{}", "leftarrowfill{}", "rightarrowfill{}", +"upbracefill{}", "downbracefill{}", 0}; /// These are translated by LyX to commands like "\\LyX{}", so we have to put /// them in ERT. "LaTeXe" must come before "LaTeX"! @@ -207,6 +254,9 @@ char const * const known_phrases[] = {"LyX", "TeX", "LaTeXe", "LaTeX", 0}; char const * const known_coded_phrases[] = {"LyX", "TeX", "LaTeX2e", "LaTeX", 0}; int const known_phrase_lengths[] = {3, 5, 7, 0}; +// string to store the float type to be able to determine the type of subfloats +string float_type = ""; + /// splits "x=z, y=b" into a map and an ordered keyword vector void split_map(string const & s, map & res, vector & keys) @@ -217,8 +267,8 @@ void split_map(string const & s, map & res, vector & key keys.resize(v.size()); for (size_t i = 0; i < v.size(); ++i) { size_t const pos = v[i].find('='); - string const index = trim(v[i].substr(0, pos)); - string const value = trim(v[i].substr(pos + 1, string::npos)); + string const index = trimSpaceAndEol(v[i].substr(0, pos)); + string const value = trimSpaceAndEol(v[i].substr(pos + 1, string::npos)); res[index] = value; keys[i] = index; } @@ -249,15 +299,15 @@ bool splitLatexLength(string const & len, string & value, string & unit) return false; } } else { - value = trim(string(length, 0, i)); + value = trimSpaceAndEol(string(length, 0, i)); } if (value == "-") value = "-1.0"; // 'cM' is a valid LaTeX length unit. Change it to 'cm' if (contains(len, '\\')) - unit = trim(string(len, i)); + unit = trimSpaceAndEol(string(len, i)); else - unit = ascii_lowercase(trim(string(len, i))); + unit = ascii_lowercase(trimSpaceAndEol(string(len, i))); return true; } @@ -393,6 +443,56 @@ bool skip_braces(Parser & p) } +/// replace LaTeX commands in \p s from the unicodesymbols file with their +/// unicode points +docstring convert_unicodesymbols(docstring s) +{ + odocstringstream os; + for (size_t i = 0; i < s.size();) { + if (s[i] != '\\') { + os.put(s[i++]); + continue; + } + s = s.substr(i); + docstring rem; + set req; + docstring parsed = encodings.fromLaTeXCommand(s, + Encodings::TEXT_CMD, rem, &req); + for (set::const_iterator it = req.begin(); it != req.end(); it++) + preamble.registerAutomaticallyLoadedPackage(*it); + os << parsed; + s = rem; + if (s.empty() || s[0] != '\\') + i = 0; + else + i = 1; + } + return os.str(); +} + + +/// try to convert \p s to a valid InsetCommand argument +string convert_command_inset_arg(string s) +{ + 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))); + // LyX cannot handle newlines in a latex command + return subst(s, "\n", " "); +} + + +void handle_backslash(ostream & os, string const & s) +{ + for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) { + if (*it == '\\') + os << "\n\\backslash\n"; + else + os << *it; + } +} + + void handle_ert(ostream & os, string const & s, Context & context) { // We must have a valid layout before outputting the ERT inset. @@ -422,12 +522,7 @@ void handle_comment(ostream & os, string const & s, Context & context) begin_inset(os, "ERT"); os << "\nstatus collapsed\n"; newcontext.check_layout(os); - for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) { - if (*it == '\\') - os << "\n\\backslash\n"; - else - os << *it; - } + handle_backslash(os, s); // make sure that our comment is the last thing on the line newcontext.new_paragraph(os); newcontext.check_layout(os); @@ -436,14 +531,25 @@ void handle_comment(ostream & os, string const & s, Context & context) } -Layout const * findLayout(TextClass const & textclass, string const & name) +Layout const * findLayout(TextClass const & textclass, string const & name, bool command) +{ + Layout const * layout = findLayoutWithoutModule(textclass, name, command); + if (layout) + return layout; + if (checkModule(name, command)) + return findLayoutWithoutModule(textclass, name, command); + return layout; +} + + +InsetLayout const * findInsetLayout(TextClass const & textclass, string const & name, bool command) { - DocumentClass::const_iterator lit = textclass.begin(); - DocumentClass::const_iterator len = textclass.end(); - for (; lit != len; ++lit) - if (lit->latexname() == name) - return &*lit; - return 0; + InsetLayout const * insetlayout = findInsetLayoutWithoutModule(textclass, name, command); + if (insetlayout) + return insetlayout; + if (checkModule(name, command)) + return findInsetLayoutWithoutModule(textclass, name, command); + return insetlayout; } @@ -481,6 +587,15 @@ void output_command_layout(ostream & os, Parser & p, bool outer, Context & parent_context, Layout const * newlayout) { + TeXFont const oldFont = parent_context.font; + // save the current font size + string const size = oldFont.size; + // reset the font size to default, because the font size switches + // don't affect section headings and the like + parent_context.font.size = Context::normalfont.size; + // we only need to write the font change if we have an open layout + if (!parent_context.atParagraphStart()) + output_font_change(os, oldFont, parent_context.font); parent_context.check_end_layout(os); Context context(true, parent_context.textclass, newlayout, parent_context.layout, parent_context.font); @@ -495,34 +610,30 @@ void output_command_layout(ostream & os, Parser & p, bool outer, unsigned int optargs = 0; while (optargs < context.layout->optargs) { eat_whitespace(p, os, context, false); - if (p.next_token().character() != '[') + if (p.next_token().cat() == catEscape || + p.next_token().character() != '[') break; p.get_token(); // eat '[' - begin_inset(os, "OptArg\n"); + begin_inset(os, "Argument\n"); os << "status collapsed\n\n"; parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context); end_inset(os); eat_whitespace(p, os, context, false); ++optargs; } -#if 0 - // This is the code needed to parse required arguments, but - // required arguments come into being only much later than the - // file format tex2lyx is presently outputting. unsigned int reqargs = 0; while (reqargs < context.layout->reqargs) { eat_whitespace(p, os, context, false); - if (p.next_token().character() != '{') + if (p.next_token().cat() != catBegin) break; p.get_token(); // eat '{' - begin_inset(os, "OptArg\n"); + begin_inset(os, "Argument\n"); os << "status collapsed\n\n"; parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context); end_inset(os); eat_whitespace(p, os, context, false); ++reqargs; } -#endif parse_text(p, os, FLAG_ITEM, outer, context); context.check_end_layout(os); if (parent_context.deeper_paragraph) { @@ -534,6 +645,9 @@ void output_command_layout(ostream & os, Parser & p, bool outer, // 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); + // Set the font size to the original value. No need to output it here + // (Context::begin_layout() will do that if needed) + parent_context.font.size = size; } @@ -585,10 +699,14 @@ void parse_arguments(string const & command, for (size_t i = 0; i < no_arguments; ++i) { switch (template_arguments[i]) { case required: + case req_group: // This argument contains regular LaTeX handle_ert(os, ert + '{', context); eat_whitespace(p, os, context, false); - parse_text(p, os, FLAG_ITEM, outer, context); + if (template_arguments[i] == required) + parse_text(p, os, FLAG_ITEM, outer, context); + else + parse_text_snippet(p, os, FLAG_ITEM, outer, context); ert = "}"; break; case item: @@ -600,13 +718,21 @@ void parse_arguments(string const & command, else ert += p.verbatim_item(); break; + case displaymath: case verbatim: // This argument may contain special characters ert += '{' + p.verbatim_item() + '}'; break; case optional: + case opt_group: // true because we must not eat whitespace - ert += p.getOpt(true); + // if an optional arg follows we must not strip the + // brackets from this one + if (i < no_arguments - 1 && + template_arguments[i+1] == optional) + ert += p.getFullOpt(true); + else + ert += p.getOpt(true); break; } } @@ -632,65 +758,193 @@ bool parse_command(string const & command, Parser & p, ostream & os, /// Parses a minipage or parbox -void parse_box(Parser & p, ostream & os, unsigned flags, bool outer, - Context & parent_context, bool use_parbox) +void parse_box(Parser & p, ostream & os, unsigned outer_flags, + unsigned inner_flags, bool outer, Context & parent_context, + string const & outer_type, string const & special, + string const & inner_type) { string position; string inner_pos; + string hor_pos = "c"; // We need to set the height to the LaTeX default of 1\\totalheight // for the case when no height argument is given string height_value = "1"; string height_unit = "in"; string height_special = "totalheight"; string latex_height; - if (p.hasOpt()) { - position = p.getArg('[', ']'); + string width_value; + string width_unit; + string latex_width; + string width_special = "none"; + if (!inner_type.empty() && p.hasOpt()) { + if (inner_type != "makebox") + position = p.getArg('[', ']'); + else { + latex_width = p.getArg('[', ']'); + translate_box_len(latex_width, width_value, width_unit, width_special); + position = "t"; + } if (position != "t" && position != "c" && position != "b") { + cerr << "invalid position " << position << " for " + << inner_type << endl; position = "c"; - cerr << "invalid position for minipage/parbox" << endl; } if (p.hasOpt()) { - latex_height = p.getArg('[', ']'); - translate_box_len(latex_height, height_value, height_unit, height_special); + if (inner_type != "makebox") { + latex_height = p.getArg('[', ']'); + translate_box_len(latex_height, height_value, height_unit, height_special); + } else + hor_pos = p.getArg('[', ']'); if (p.hasOpt()) { inner_pos = p.getArg('[', ']'); if (inner_pos != "c" && inner_pos != "t" && inner_pos != "b" && inner_pos != "s") { + cerr << "invalid inner_pos " + << inner_pos << " for " + << inner_type << endl; inner_pos = position; - cerr << "invalid inner_pos for minipage/parbox" - << endl; } } } } - string width_value; - string width_unit; - string const latex_width = p.verbatim_item(); + if (inner_type.empty()) { + if (special.empty() && outer_type != "framebox") + latex_width = "1\\columnwidth"; + else { + Parser p2(special); + latex_width = p2.getArg('[', ']'); + string const opt = p2.getArg('[', ']'); + if (!opt.empty()) { + hor_pos = opt; + if (hor_pos != "l" && hor_pos != "c" && + hor_pos != "r") { + cerr << "invalid hor_pos " << hor_pos + << " for " << outer_type << endl; + hor_pos = "c"; + } + } + } + } else if (inner_type != "makebox") + latex_width = p.verbatim_item(); + // if e.g. only \ovalbox{content} was used, set the width to 1\columnwidth + // as this is LyX's standard for such cases (except for makebox) + // \framebox is more special and handled below + if (latex_width.empty() && inner_type != "makebox" + && outer_type != "framebox") + latex_width = "1\\columnwidth"; + translate_len(latex_width, width_value, width_unit); - if (contains(width_unit, '\\') || contains(height_unit, '\\')) { - // LyX can't handle length variables - ostringstream ss; - if (use_parbox) - ss << "\\parbox"; + + bool shadedparbox = false; + if (inner_type == "shaded") { + eat_whitespace(p, os, parent_context, false); + if (outer_type == "parbox") { + // Eat '{' + if (p.next_token().cat() == catBegin) + p.get_token(); + eat_whitespace(p, os, parent_context, false); + shadedparbox = true; + } + p.get_token(); + p.getArg('{', '}'); + } + // If we already read the inner box we have to push the inner env + if (!outer_type.empty() && !inner_type.empty() && + (inner_flags & FLAG_END)) + active_environments.push_back(inner_type); + // LyX can't handle length variables + bool use_ert = contains(width_unit, '\\') || contains(height_unit, '\\'); + if (!use_ert && !outer_type.empty() && !inner_type.empty()) { + // Look whether there is some content after the end of the + // inner box, but before the end of the outer box. + // If yes, we need to output ERT. + p.pushPosition(); + if (inner_flags & FLAG_END) + p.verbatimEnvironment(inner_type); else - ss << "\\begin{minipage}"; - if (!position.empty()) - ss << '[' << position << ']'; - if (!latex_height.empty()) - ss << '[' << latex_height << ']'; - if (!inner_pos.empty()) - ss << '[' << inner_pos << ']'; - ss << "{" << latex_width << "}"; - if (use_parbox) - ss << '{'; + p.verbatim_item(); + p.skip_spaces(true); + bool const outer_env(outer_type == "framed" || outer_type == "minipage"); + if ((outer_env && p.next_token().asInput() != "\\end") || + (!outer_env && p.next_token().cat() != catEnd)) { + // something is between the end of the inner box and + // the end of the outer box, so we need to use ERT. + use_ert = true; + } + p.popPosition(); + } + // if only \makebox{content} was used we can set its width to 1\width + // because this identic and also identic to \mbox + // this doesn't work for \framebox{content}, thus we have to use ERT for this + if (latex_width.empty() && inner_type == "makebox") { + width_value = "1"; + width_unit = "in"; + width_special = "width"; + } else if (latex_width.empty() && outer_type == "framebox") { + use_ert = true; + } + if (use_ert) { + ostringstream ss; + if (!outer_type.empty()) { + if (outer_flags & FLAG_END) + ss << "\\begin{" << outer_type << '}'; + else { + ss << '\\' << outer_type << '{'; + if (!special.empty()) + ss << special; + } + } + if (!inner_type.empty()) { + if (inner_type != "shaded") { + if (inner_flags & FLAG_END) + ss << "\\begin{" << inner_type << '}'; + else + ss << '\\' << inner_type; + } + if (!position.empty()) + ss << '[' << position << ']'; + if (!latex_height.empty()) + ss << '[' << latex_height << ']'; + if (!inner_pos.empty()) + ss << '[' << inner_pos << ']'; + ss << '{' << latex_width << '}'; + if (!(inner_flags & FLAG_END)) + ss << '{'; + } + if (inner_type == "shaded") + ss << "\\begin{shaded}"; handle_ert(os, ss.str(), parent_context); - parent_context.new_paragraph(os); - parse_text_in_inset(p, os, flags, outer, parent_context); - if (use_parbox) - handle_ert(os, "}", parent_context); - else - handle_ert(os, "\\end{minipage}", parent_context); + if (!inner_type.empty()) { + parse_text(p, os, inner_flags, outer, parent_context); + if (inner_flags & FLAG_END) + handle_ert(os, "\\end{" + inner_type + '}', + parent_context); + else + handle_ert(os, "}", parent_context); + } + if (!outer_type.empty()) { + // If we already read the inner box we have to pop + // the inner env + if (!inner_type.empty() && (inner_flags & FLAG_END)) + active_environments.pop_back(); + + // Ensure that the end of the outer box is parsed correctly: + // The opening brace has been eaten by parse_outer_box() + if (!outer_type.empty() && (outer_flags & FLAG_ITEM)) { + outer_flags &= ~FLAG_ITEM; + outer_flags |= FLAG_BRACE_LAST; + } + parse_text(p, os, outer_flags, outer, parent_context); + if (outer_flags & FLAG_END) + handle_ert(os, "\\end{" + outer_type + '}', + parent_context); + else if (inner_type.empty() && outer_type == "framebox") + // in this case it is already closed later + ; + else + handle_ert(os, "}", parent_context); + } } else { // LyX does not like empty positions, so we have // to set them to the LaTeX default values here. @@ -699,18 +953,74 @@ void parse_box(Parser & p, ostream & os, unsigned flags, bool outer, if (inner_pos.empty()) inner_pos = position; parent_context.check_layout(os); - begin_inset(os, "Box Frameless\n"); + begin_inset(os, "Box "); + if (outer_type == "framed") + os << "Framed\n"; + else if (outer_type == "framebox") + os << "Boxed\n"; + else if (outer_type == "shadowbox") + os << "Shadowbox\n"; + else if ((outer_type == "shaded" && inner_type.empty()) || + (outer_type == "minipage" && inner_type == "shaded") || + (outer_type == "parbox" && inner_type == "shaded")) { + os << "Shaded\n"; + preamble.registerAutomaticallyLoadedPackage("color"); + } else if (outer_type == "doublebox") + os << "Doublebox\n"; + else if (outer_type.empty()) + os << "Frameless\n"; + else + os << outer_type << '\n'; os << "position \"" << position << "\"\n"; - os << "hor_pos \"c\"\n"; - os << "has_inner_box 1\n"; + os << "hor_pos \"" << hor_pos << "\"\n"; + os << "has_inner_box " << !inner_type.empty() << "\n"; os << "inner_pos \"" << inner_pos << "\"\n"; - os << "use_parbox " << use_parbox << "\n"; + os << "use_parbox " << (inner_type == "parbox" || shadedparbox) + << '\n'; + os << "use_makebox " << (inner_type == "makebox") << '\n'; os << "width \"" << width_value << width_unit << "\"\n"; - os << "special \"none\"\n"; + os << "special \"" << width_special << "\"\n"; os << "height \"" << height_value << height_unit << "\"\n"; os << "height_special \"" << height_special << "\"\n"; os << "status open\n\n"; - parse_text_in_inset(p, os, flags, outer, parent_context); + + // Unfortunately we can't use parse_text_in_inset: + // InsetBox::forcePlainLayout() is hard coded and does not + // use the inset layout. Apart from that do we call parse_text + // up to two times, but need only one check_end_layout. + bool const forcePlainLayout = + (!inner_type.empty() || inner_type == "makebox") && + outer_type != "shaded" && outer_type != "framed"; + Context context(true, parent_context.textclass); + if (forcePlainLayout) + context.layout = &context.textclass.plainLayout(); + else + context.font = parent_context.font; + + // If we have no inner box the contents will be read with the outer box + if (!inner_type.empty()) + parse_text(p, os, inner_flags, outer, context); + + // Ensure that the end of the outer box is parsed correctly: + // The opening brace has been eaten by parse_outer_box() + if (!outer_type.empty() && (outer_flags & FLAG_ITEM)) { + outer_flags &= ~FLAG_ITEM; + outer_flags |= FLAG_BRACE_LAST; + } + + // Find end of outer box, output contents if inner_type is + // empty and output possible comments + if (!outer_type.empty()) { + // If we already read the inner box we have to pop + // the inner env + if (!inner_type.empty() && (inner_flags & FLAG_END)) + active_environments.pop_back(); + // This does not output anything but comments if + // inner_type is not empty (see use_ert) + parse_text(p, os, outer_flags, outer, context); + } + + context.check_end_layout(os); end_inset(os); #ifdef PRESERVE_LAYOUT // LyX puts a % after the end of the minipage @@ -736,6 +1046,103 @@ void parse_box(Parser & p, ostream & os, unsigned flags, bool outer, } +void parse_outer_box(Parser & p, ostream & os, unsigned flags, bool outer, + Context & parent_context, string const & outer_type, + string const & special) +{ + eat_whitespace(p, os, parent_context, false); + if (flags & FLAG_ITEM) { + // Eat '{' + if (p.next_token().cat() == catBegin) + p.get_token(); + else + cerr << "Warning: Ignoring missing '{' after \\" + << outer_type << '.' << endl; + eat_whitespace(p, os, parent_context, false); + } + string inner; + unsigned int inner_flags = 0; + p.pushPosition(); + if (outer_type == "minipage" || outer_type == "parbox") { + p.skip_spaces(true); + while (p.hasOpt()) { + p.getArg('[', ']'); + p.skip_spaces(true); + } + p.getArg('{', '}'); + p.skip_spaces(true); + if (outer_type == "parbox") { + // Eat '{' + if (p.next_token().cat() == catBegin) + p.get_token(); + p.skip_spaces(true); + } + } + if (outer_type == "shaded") { + // These boxes never have an inner box + ; + } else if (p.next_token().asInput() == "\\parbox") { + inner = p.get_token().cs(); + inner_flags = FLAG_ITEM; + } else if (p.next_token().asInput() == "\\begin") { + // Is this a minipage or shaded box? + p.pushPosition(); + p.get_token(); + inner = p.getArg('{', '}'); + p.popPosition(); + if (inner == "minipage" || inner == "shaded") + inner_flags = FLAG_END; + else + inner = ""; + } + p.popPosition(); + if (inner_flags == FLAG_END) { + if (inner != "shaded") + { + p.get_token(); + p.getArg('{', '}'); + eat_whitespace(p, os, parent_context, false); + } + parse_box(p, os, flags, FLAG_END, outer, parent_context, + outer_type, special, inner); + } else { + if (inner_flags == FLAG_ITEM) { + p.get_token(); + eat_whitespace(p, os, parent_context, false); + } + parse_box(p, os, flags, inner_flags, outer, parent_context, + outer_type, special, inner); + } +} + + +void parse_listings(Parser & p, ostream & os, Context & parent_context) +{ + parent_context.check_layout(os); + begin_inset(os, "listings\n"); + os << "inline false\n" + << "status collapsed\n"; + Context context(true, parent_context.textclass); + context.layout = &parent_context.textclass.plainLayout(); + context.check_layout(os); + string const s = p.verbatimEnvironment("lstlisting"); + for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) { + if (*it == '\\') + os << "\n\\backslash\n"; + else if (*it == '\n') { + // avoid adding an empty paragraph at the end + if (it + 1 != et) { + context.new_paragraph(os); + context.check_layout(os); + } + } else + os << *it; + } + context.check_end_layout(os); + end_inset(os); +} + + /// parse an unknown environment void parse_unknown_environment(Parser & p, string const & name, ostream & os, unsigned flags, bool outer, @@ -764,9 +1171,11 @@ void parse_unknown_environment(Parser & p, string const & name, ostream & os, void parse_environment(Parser & p, ostream & os, bool outer, - string & last_env, Context & parent_context) + string & last_env, bool & title_layout_found, + Context & parent_context) { Layout const * newlayout; + InsetLayout const * newinsetlayout = 0; string const name = p.getArg('{', '}'); const bool is_starred = suffixIs(name, '*'); string const unstarred_name = rtrim(name, "*"); @@ -779,23 +1188,53 @@ void parse_environment(Parser & p, ostream & os, bool outer, 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); + } } - else if (name == "tabular" || name == "longtable") { + 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('{', '}')); + eat_whitespace(p, os, parent_context, false); + } parent_context.check_layout(os); begin_inset(os, "Tabular "); - handle_tabular(p, os, name == "longtable", parent_context); + 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"); - if (p.hasOpt()) - os << "placement " << p.getArg('[', ']') << '\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()) + preamble.registerAutomaticallyLoadedPackage("float"); + } + os << "wide " << convert(is_starred) << "\nsideways false" << "\nstatus open\n\n"; @@ -805,11 +1244,88 @@ void parse_environment(Parser & p, ostream & os, bool outer, // 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") { + eat_whitespace(p, os, parent_context, false); + parent_context.check_layout(os); + if (unstarred_name == "sidewaysfigure") + begin_inset(os, "Float figure\n"); + else + begin_inset(os, "Float table\n"); + 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 if (name == "minipage") { eat_whitespace(p, os, parent_context, false); - parse_box(p, os, FLAG_END, outer, 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; + } + 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(); } @@ -822,6 +1338,7 @@ void parse_environment(Parser & p, ostream & os, bool outer, end_inset(os); p.skip_spaces(); skip_braces(p); // eat {} that might by set by LyX behind comments + preamble.registerAutomaticallyLoadedPackage("verbatim"); } else if (name == "lyxgreyedout") { @@ -832,27 +1349,26 @@ void parse_environment(Parser & p, ostream & os, bool outer, 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 == "framed" || name == "shaded") { eat_whitespace(p, os, parent_context, false); - parent_context.check_layout(os); - if (name == "framed") - begin_inset(os, "Box Framed\n"); + parse_outer_box(p, os, FLAG_END, outer, parent_context, name, ""); + p.skip_spaces(); + } + + else if (name == "lstlisting") { + eat_whitespace(p, os, parent_context, false); + // FIXME handle listings with parameters + // If this is added, don't forgot to handle the + // automatic color package loading + if (p.hasOpt()) + parse_unknown_environment(p, name, os, FLAG_END, + outer, parent_context); else - begin_inset(os, "Box Shaded\n"); - os << "position \"t\"\n" - "hor_pos \"c\"\n" - "has_inner_box 0\n" - "inner_pos \"t\"\n" - "use_parbox 0\n" - "width \"100col%\"\n" - "special \"none\"\n" - "height \"1in\"\n" - "height_special \"totalheight\"\n" - "status open\n"; - parse_text_in_inset(p, os, FLAG_END, outer, parent_context); - end_inset(os); + parse_listings(p, os, parent_context); p.skip_spaces(); } @@ -885,12 +1401,16 @@ void parse_environment(Parser & p, ostream & os, bool outer, 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") + else if (name == "onehalfspace") { parent_context.add_extra_stuff("\\paragraph_spacing onehalf\n"); - else if (name == "doublespace") + preamble.registerAutomaticallyLoadedPackage("setspace"); + } else if (name == "doublespace") { parent_context.add_extra_stuff("\\paragraph_spacing double\n"); - else if (name == "spacing") + 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(); @@ -900,8 +1420,7 @@ void parse_environment(Parser & p, ostream & os, bool outer, } // The single '=' is meant here. - else if ((newlayout = findLayout(parent_context.textclass, name)) && - newlayout->isEnvironment()) { + 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); @@ -948,6 +1467,49 @@ void parse_environment(Parser & p, ostream & os, bool outer, break; } context.check_deeper(os); + // handle known optional and required arguments + // layouts require all optional arguments before the required ones + // 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) { + bool need_layout = true; + unsigned int optargs = 0; + while (optargs < context.layout->optargs) { + eat_whitespace(p, os, context, false); + if (p.next_token().cat() == catEscape || + p.next_token().character() != '[') + break; + p.get_token(); // eat '[' + if (need_layout) { + context.check_layout(os); + need_layout = false; + } + begin_inset(os, "Argument\n"); + os << "status collapsed\n\n"; + parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context); + end_inset(os); + eat_whitespace(p, os, context, false); + ++optargs; + } + unsigned int reqargs = 0; + while (reqargs < context.layout->reqargs) { + eat_whitespace(p, os, context, false); + if (p.next_token().cat() != catBegin) + break; + p.get_token(); // eat '{' + if (need_layout) { + context.check_layout(os); + need_layout = false; + } + begin_inset(os, "Argument\n"); + os << "status collapsed\n\n"; + parse_text_in_inset(p, os, FLAG_BRACE_LAST, outer, context); + end_inset(os); + eat_whitespace(p, os, context, false); + ++reqargs; + } + } parse_text(p, os, FLAG_END, outer, context); context.check_end_layout(os); if (parent_context.deeper_paragraph) { @@ -958,6 +1520,22 @@ void parse_environment(Parser & p, ostream & os, bool outer, context.check_end_deeper(os); parent_context.new_paragraph(os); p.skip_spaces(); + if (!title_layout_found) + title_layout_found = newlayout->intitle; + set const & req = newlayout->requires(); + for (set::const_iterator it = req.begin(); it != req.end(); 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 "); + os << to_utf8(newinsetlayout->name()) << '\n' + << "status collapsed\n"; + parse_text_in_inset(p, os, FLAG_END, false, parent_context, newinsetlayout); + end_inset(os); } else if (name == "appendix") { @@ -1184,8 +1762,10 @@ void parse_noweb(Parser & p, ostream & os, Context & context) os << subst(t.asInput(), "\\", "\n\\backslash\n"); else { ostringstream oss; - begin_inset(oss, "Newline newline"); - end_inset(oss); + Context tmp(false, context.textclass, + &context.textclass[from_ascii("Scrap")]); + tmp.need_end_layout = true; + tmp.check_layout(oss); os << subst(t.asInput(), "\n", oss.str()); } // The scrap chunk is ended by an @ at the beginning of a line. @@ -1326,18 +1906,21 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, Context & context) { Layout const * newlayout = 0; + InsetLayout const * newinsetlayout = 0; + char const * const * where = 0; // Store the latest bibliographystyle and nocite{*} option // (needed for bibtex inset) string btprint; string bibliographystyle; - bool const use_natbib = used_packages.find("natbib") != used_packages.end(); - bool const use_jurabib = used_packages.find("jurabib") != used_packages.end(); + bool const use_natbib = preamble.isPackageUsed("natbib"); + bool const use_jurabib = preamble.isPackageUsed("jurabib"); string last_env; + bool title_layout_found = false; while (p.good()) { Token const & t = p.get_token(); #ifdef FILEDEBUG - cerr << "t: " << t << " flags: " << flags << "\n"; + debugToken(cerr, t, flags); #endif if (flags & FLAG_ITEM) { @@ -1356,9 +1939,10 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, flags |= FLAG_LEAVE; } - if (t.character() == ']' && (flags & FLAG_BRACK_LAST)) + if (t.cat() != catEscape && t.character() == ']' && + (flags & FLAG_BRACK_LAST)) return; - if (t.character() == '}' && (flags & FLAG_BRACE_LAST)) + if (t.cat() == catEnd && (flags & FLAG_BRACE_LAST)) return; // If there is anything between \end{env} and \begin{env} we @@ -1375,7 +1959,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, context.check_layout(os); begin_inset(os, "Formula "); Token const & n = p.get_token(); - if (n.cat() == catMath && outer) { + bool const display(n.cat() == catMath && outer); + if (display) { // TeX's $$...$$ syntax for displayed math os << "\\["; parse_math(p, os, FLAG_SIMPLE, MATH_MODE); @@ -1389,6 +1974,12 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, os << '$'; } end_inset(os); + if (display) { + // 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, context, false); + } } else if (t.cat() == catSuper || t.cat() == catSub) @@ -1510,8 +2101,10 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, os << t.cs(); } - else if (t.cat() == catBegin && - p.next_token().cat() == catEnd) { + else if (t.cat() == catBegin) { + Token const next = p.next_token(); + Token const end = p.next_next_token(); + if (next.cat() == catEnd) { // {} Token const prev = p.prev_token(); p.get_token(); @@ -1521,14 +2114,19 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, ; // ignore it in {}`` or -{}- else handle_ert(os, "{}", context); - - } - - else if (t.cat() == catBegin) { + } else if (next.cat() == catEscape && + is_known(next.cs(), known_quotes) && + end.cat() == catEnd) { + // Something like {\textquoteright} (e.g. + // from writer2latex). LyX writes + // \textquoteright{}, so we may skip the + // braces here for better readability. + parse_text_snippet(p, os, FLAG_BRACE_LAST, + outer, context); + } else { context.check_layout(os); // special handling of font attribute changes Token const prev = p.prev_token(); - Token const next = p.next_token(); TeXFont const oldFont = context.font; if (next.character() == '[' || next.character() == ']' || @@ -1601,6 +2199,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, parse_text_snippet(p, os, FLAG_BRACE_LAST, outer, context); handle_ert(os, "}", context); + } } } @@ -1635,10 +2234,15 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, parse_math(p, os, FLAG_EQUATION, MATH_MODE); os << "\\]"; end_inset(os); + // 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, context, false); } else if (t.cs() == "begin") - parse_environment(p, os, outer, last_env, context); + parse_environment(p, os, outer, last_env, + title_layout_found, context); else if (t.cs() == "end") { if (flags & FLAG_END) { @@ -1653,15 +2257,16 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, } else if (t.cs() == "item") { - p.skip_spaces(); string s; - bool optarg = false; - if (p.next_token().character() == '[') { - p.get_token(); // eat '[' - s = parse_text_snippet(p, FLAG_BRACK_LAST, - outer, context); - optarg = true; - } + bool const optarg = p.hasOpt(); + if (optarg) { + // FIXME: This swallows comments, but we cannot use + // eat_whitespace() since we must not output + // anything before the item. + p.skip_spaces(true); + s = p.verbatimOption(); + } else + p.skip_spaces(false); context.set_item(); context.check_layout(os); if (context.has_item) { @@ -1677,13 +2282,30 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (context.layout->labeltype != LABEL_MANUAL) { // LyX does not support \item[\mybullet] // in itemize environments - handle_ert(os, "[", context); - os << s; - handle_ert(os, "]", context); + Parser p2(s + ']'); + os << parse_text_snippet(p2, + FLAG_BRACK_LAST, outer, context); } else if (!s.empty()) { + // LyX adds braces around the argument, + // so we need to remove them here. + if (s.size() > 2 && s[0] == '{' && + s[s.size()-1] == '}') + s = s.substr(1, s.size()-2); + // If the argument contains a space we + // must put it into ERT: Otherwise LyX + // would misinterpret the space as + // item delimiter (bug 7663) + if (contains(s, ' ')) { + handle_ert(os, s, context); + } else { + Parser p2(s + ']'); + os << parse_text_snippet(p2, + FLAG_BRACK_LAST, + outer, context); + } // The space is needed to separate the // item from the rest of the sentence. - os << s << ' '; + os << ' '; eat_whitespace(p, os, context, false); } } @@ -1692,14 +2314,81 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, else if (t.cs() == "bibitem") { context.set_item(); context.check_layout(os); - begin_command_inset(os, "bibitem", "bibitem"); - os << "label \"" << p.getOptContent() << "\"\n"; - os << "key \"" << p.verbatim_item() << "\"\n"; - end_inset(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 + handle_ert(os, t.asInput() + '[' + label + + "]{" + p.verbatim_item() + '}', + context); + } else { + begin_command_inset(os, "bibitem", "bibitem"); + os << "label \"" << label << "\"\n" + "key \"" << key << "\"\n"; + end_inset(os); + } } - else if (is_macro(p)) - parse_macro(p, os, context); + else if (is_macro(p)) { + // catch the case of \def\inputGnumericTable + bool macro = true; + if (t.cs() == "def") { + Token second = p.next_token(); + if (second.cs() == "inputGnumericTable") { + p.pushPosition(); + p.get_token(); + skip_braces(p); + Token third = p.get_token(); + p.popPosition(); + if (third.cs() == "input") { + p.get_token(); + skip_braces(p); + p.get_token(); + string name = normalize_filename(p.verbatim_item()); + string const path = getMasterFilePath(); + // We want to preserve relative / absolute filenames, + // therefore path is only used for testing + // The file extension is in every case ".tex". + // So we need to remove this extension and check for + // the original one. + name.erase(name.length() - 4, name.length()); + if (!makeAbsPath(name, path).exists()) { + char const * const Gnumeric_formats[] = {"gnumeric" + "ods", "xls", 0}; + string const Gnumeric_name = + find_file(name, path, Gnumeric_formats); + if (!Gnumeric_name.empty()) + name = Gnumeric_name; + } + if (makeAbsPath(name, path).exists()) + fix_relative_filename(name); + else + cerr << "Warning: Could not find file '" + << name << "'." << endl; + context.check_layout(os); + begin_inset(os, "External\n\ttemplate "); + os << "GnumericSpreadsheet\n\tfilename " + << name << "\n"; + end_inset(os); + context.check_layout(os); + macro = false; + // register the packages that are automatically reloaded + // by the Gnumeric template + // Fixme: InsetExternal.cpp should give us that list + preamble.registerAutomaticallyLoadedPackage("array"); + preamble.registerAutomaticallyLoadedPackage("calc"); + preamble.registerAutomaticallyLoadedPackage("color"); + preamble.registerAutomaticallyLoadedPackage("hhline"); + preamble.registerAutomaticallyLoadedPackage("ifthen"); + preamble.registerAutomaticallyLoadedPackage("longtable"); + preamble.registerAutomaticallyLoadedPackage("multirow"); + } + } + } + if (macro) + parse_macro(p, os, context); + } else if (t.cs() == "noindent") { p.skip_spaces(); @@ -1729,62 +2418,73 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, eat_whitespace(p, os, context, true); } + // Must catch empty dates before findLayout is called below + else if (t.cs() == "date") { + string const date = p.verbatim_item(); + if (date.empty()) + preamble.suppressDate(true); + else { + preamble.suppressDate(false); + if (context.new_layout_allowed && + (newlayout = findLayout(context.textclass, + t.cs(), true))) { + // write the layout + output_command_layout(os, p, outer, + context, newlayout); + p.skip_spaces(); + if (!title_layout_found) + title_layout_found = newlayout->intitle; + set const & req = newlayout->requires(); + for (set::const_iterator it = req.begin(); + it != req.end(); it++) + preamble.registerAutomaticallyLoadedPackage(*it); + } else + handle_ert(os, "\\date{" + date + '}', + context); + } + } + // Starred section headings // Must attempt to parse "Section*" before "Section". else if ((p.next_token().asInput() == "*") && context.new_layout_allowed && - (newlayout = findLayout(context.textclass, t.cs() + '*')) && - newlayout->isCommand()) { - TeXFont const oldFont = context.font; - // save the current font size - string const size = oldFont.size; - // reset the font size to default, because the - // font size switches don't affect section - // headings and the like - context.font.size = known_coded_sizes[0]; - output_font_change(os, oldFont, context.font); + (newlayout = findLayout(context.textclass, t.cs() + '*', true))) { // write the layout p.get_token(); output_command_layout(os, p, outer, context, newlayout); - // set the font size to the original value - context.font.size = size; - output_font_change(os, oldFont, context.font); p.skip_spaces(); + if (!title_layout_found) + title_layout_found = newlayout->intitle; + set const & req = newlayout->requires(); + for (set::const_iterator it = req.begin(); it != req.end(); it++) + preamble.registerAutomaticallyLoadedPackage(*it); } // Section headings and the like else if (context.new_layout_allowed && - (newlayout = findLayout(context.textclass, t.cs())) && - newlayout->isCommand()) { - TeXFont const oldFont = context.font; - // save the current font size - string const size = oldFont.size; - // reset the font size to default, because the font size switches don't - // affect section headings and the like - context.font.size = known_coded_sizes[0]; - output_font_change(os, oldFont, context.font); + (newlayout = findLayout(context.textclass, t.cs(), true))) { // write the layout output_command_layout(os, p, outer, context, newlayout); - // set the font size to the original value - context.font.size = size; - output_font_change(os, oldFont, context.font); p.skip_spaces(); + if (!title_layout_found) + title_layout_found = newlayout->intitle; + set const & req = newlayout->requires(); + for (set::const_iterator it = req.begin(); it != req.end(); it++) + preamble.registerAutomaticallyLoadedPackage(*it); } else if (t.cs() == "caption") { - // FIXME: this should get some cleanup. All - // the \begin_layout:s are output by the - // Context class! p.skip_spaces(); context.check_layout(os); p.skip_spaces(); - begin_inset(os, "Caption\n\n"); - os << "\\begin_layout " - << to_utf8(context.textclass.defaultLayout().name()) - << '\n'; - if (p.next_token().character() == '[') { + begin_inset(os, "Caption\n"); + Context newcontext(true, context.textclass); + newcontext.font = context.font; + newcontext.check_layout(os); + if (p.next_token().cat() != catEscape && + p.next_token().character() == '[') { p.get_token(); // eat '[' - begin_inset(os, "OptArg\n"); + begin_inset(os, "Argument\n"); os << "status collapsed\n"; parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context); end_inset(os); @@ -1797,7 +2497,69 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, context.new_paragraph(os); end_inset(os); p.skip_spaces(); - os << "\\end_layout\n"; + newcontext.check_end_layout(os); + } + + else if (t.cs() == "subfloat") { + // the syntax is \subfloat[caption]{content} + // if it is a table of figure depends on the surrounding float + bool has_caption = false; + p.skip_spaces(); + // do nothing if there is no outer float + if (!float_type.empty()) { + context.check_layout(os); + p.skip_spaces(); + begin_inset(os, "Float " + float_type + "\n"); + os << "wide false" + << "\nsideways false" + << "\nstatus collapsed\n\n"; + // test for caption + string 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); + has_caption = true; + } + // the content + parse_text_in_inset(p, os, FLAG_ITEM, outer, context); + // the caption comes always as the last + if (has_caption) { + // we must make sure that the caption gets a \begin_layout + os << "\n\\begin_layout Plain Layout"; + p.skip_spaces(); + begin_inset(os, "Caption\n"); + Context newcontext(true, context.textclass); + newcontext.font = context.font; + 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(); + } + // 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 + if (p.hasOpt()) { + string opt_arg = convert_command_inset_arg(p.getArg('[', ']')); + handle_ert(os, t.asInput() + '[' + opt_arg + + "]{" + p.verbatim_item() + '}', context); + } else + handle_ert(os, t.asInput() + "{" + p.verbatim_item() + '}', context); + } } else if (t.cs() == "includegraphics") { @@ -1991,10 +2753,11 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, } else if (t.cs() == "makeindex" || t.cs() == "maketitle") { - // FIXME: Somehow prevent title layouts if - // "maketitle" was not found - // swallow this - skip_spaces_braces(p); + if (title_layout_found) { + // swallow this + skip_spaces_braces(p); + } else + handle_ert(os, t.asInput(), context); } else if (t.cs() == "tableofcontents") { @@ -2031,50 +2794,20 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, handle_ert(os, "\\listof{" + name + "}", context); } - else if (t.cs() == "textrm") - parse_text_attributes(p, os, FLAG_ITEM, outer, - context, "\\family", - context.font.family, "roman"); - - else if (t.cs() == "textsf") - parse_text_attributes(p, os, FLAG_ITEM, outer, - context, "\\family", - context.font.family, "sans"); - - else if (t.cs() == "texttt") - parse_text_attributes(p, os, FLAG_ITEM, outer, - context, "\\family", - context.font.family, "typewriter"); - - else if (t.cs() == "textmd") - parse_text_attributes(p, os, FLAG_ITEM, outer, - context, "\\series", - context.font.series, "medium"); - - else if (t.cs() == "textbf") - parse_text_attributes(p, os, FLAG_ITEM, outer, - context, "\\series", - context.font.series, "bold"); - - else if (t.cs() == "textup") + else if ((where = is_known(t.cs(), known_text_font_families))) parse_text_attributes(p, os, FLAG_ITEM, outer, - context, "\\shape", - context.font.shape, "up"); + context, "\\family", context.font.family, + known_coded_font_families[where - known_text_font_families]); - else if (t.cs() == "textit") + else if ((where = is_known(t.cs(), known_text_font_series))) parse_text_attributes(p, os, FLAG_ITEM, outer, - context, "\\shape", - context.font.shape, "italic"); + context, "\\series", context.font.series, + known_coded_font_series[where - known_text_font_series]); - else if (t.cs() == "textsl") + else if ((where = is_known(t.cs(), known_text_font_shapes))) parse_text_attributes(p, os, FLAG_ITEM, outer, - context, "\\shape", - context.font.shape, "slanted"); - - else if (t.cs() == "textsc") - parse_text_attributes(p, os, FLAG_ITEM, outer, - context, "\\shape", - context.font.shape, "smallcaps"); + context, "\\shape", context.font.shape, + known_coded_font_shapes[where - known_text_font_shapes]); else if (t.cs() == "textnormal" || t.cs() == "normalfont") { context.check_layout(os); @@ -2104,12 +2837,16 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, parse_text_snippet(p, os, FLAG_ITEM, outer, context); context.check_layout(os); os << "\n\\color inherit\n"; + preamble.registerAutomaticallyLoadedPackage("color"); } else // for custom defined colors handle_ert(os, t.asInput() + "{" + color + "}", context); } - else if (t.cs() == "underbar") { + else if (t.cs() == "underbar" || t.cs() == "uline") { + // \underbar is not 100% correct (LyX outputs \uline + // of ulem.sty). The difference is that \ulem allows + // line breaks, and \underbar does not. // Do NOT handle \underline. // \underbar cuts through y, g, q, p etc., // \underline does not. @@ -2118,37 +2855,184 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, parse_text_snippet(p, os, FLAG_ITEM, outer, context); context.check_layout(os); os << "\n\\bar default\n"; + preamble.registerAutomaticallyLoadedPackage("ulem"); + } + + else if (t.cs() == "sout") { + context.check_layout(os); + os << "\n\\strikeout on\n"; + parse_text_snippet(p, os, FLAG_ITEM, outer, context); + context.check_layout(os); + os << "\n\\strikeout default\n"; + preamble.registerAutomaticallyLoadedPackage("ulem"); } - else if (t.cs() == "emph" || t.cs() == "noun") { + else if (t.cs() == "uuline" || t.cs() == "uwave" || + t.cs() == "emph" || t.cs() == "noun") { context.check_layout(os); os << "\n\\" << t.cs() << " on\n"; parse_text_snippet(p, os, FLAG_ITEM, outer, context); context.check_layout(os); os << "\n\\" << t.cs() << " default\n"; + if (t.cs() == "uuline" || t.cs() == "uwave") + preamble.registerAutomaticallyLoadedPackage("ulem"); + } + + else if (t.cs() == "lyxadded" || t.cs() == "lyxdeleted") { + context.check_layout(os); + string name = p.getArg('{', '}'); + string localtime = p.getArg('{', '}'); + preamble.registerAuthor(name); + Author const & author = preamble.getAuthor(name); + // from_ctime() will fail if LyX decides to output the + // time in the text language. It might also use a wrong + // time zone (if the original LyX document was exported + // with a different time zone). + time_t ptime = from_ctime(localtime); + if (ptime == static_cast(-1)) { + cerr << "Warning: Could not parse time `" << localtime + << "´ for change tracking, using current time instead.\n"; + ptime = current_time(); + } + if (t.cs() == "lyxadded") + os << "\n\\change_inserted "; + else + 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 + // two flavours (dvi and pdf), like latex, and those + // are detected by pdflatex. + if (pdflatex || xetex) { + if (xcolorulem) { + preamble.registerAutomaticallyLoadedPackage("ulem"); + preamble.registerAutomaticallyLoadedPackage("xcolor"); + preamble.registerAutomaticallyLoadedPackage("pdfcolmk"); + } + } else { + if (dvipost) { + preamble.registerAutomaticallyLoadedPackage("dvipost"); + } else if (xcolorulem) { + preamble.registerAutomaticallyLoadedPackage("ulem"); + preamble.registerAutomaticallyLoadedPackage("xcolor"); + } + } + } + + else if (t.cs() == "phantom" || t.cs() == "hphantom" || + t.cs() == "vphantom") { + context.check_layout(os); + if (t.cs() == "phantom") + begin_inset(os, "Phantom Phantom\n"); + if (t.cs() == "hphantom") + begin_inset(os, "Phantom HPhantom\n"); + if (t.cs() == "vphantom") + begin_inset(os, "Phantom VPhantom\n"); + os << "status open\n"; + parse_text_in_inset(p, os, FLAG_ITEM, outer, context, + "Phantom"); + end_inset(os); } + else if (t.cs() == "href") { + context.check_layout(os); + string target = p.getArg('{', '}'); + string name = p.getArg('{', '}'); + string type; + size_t i = target.find(':'); + if (i != string::npos) { + type = target.substr(0, i + 1); + if (type == "mailto:" || type == "file:") + target = target.substr(i + 1); + // handle the case that name is equal to target, except of "http://" + else if (target.substr(i + 3) == name && type == "http:") + target = name; + } + begin_command_inset(os, "href", "href"); + if (name != target) + os << "name \"" << name << "\"\n"; + os << "target \"" << target << "\"\n"; + if (type == "mailto:" || type == "file:") + os << "type \"" << type << "\"\n"; + end_inset(os); + skip_spaces_braces(p); + } + else if (t.cs() == "lyxline") { + // swallow size argument (it is not used anyway) + p.getArg('{', '}'); + if (!context.atParagraphStart()) { + // so our line is in the middle of a paragraph + // we need to add a new line, lest this line + // follow the other content on that line and + // run off the side of the page + // FIXME: This may create an empty paragraph, + // but without that it would not be + // possible to set noindent below. + // Fortunately LaTeX does not care + // about the empty paragraph. + context.new_paragraph(os); + } + if (preamble.indentParagraphs()) { + // we need to unindent, lest the line be too long + context.add_par_extra_stuff("\\noindent\n"); + } context.check_layout(os); - os << "\\lyxline"; + begin_command_inset(os, "line", "rule"); + os << "offset \"0.5ex\"\n" + "width \"100line%\"\n" + "height \"1pt\"\n"; + end_inset(os); + } + + else if (t.cs() == "rule") { + string const offset = (p.hasOpt() ? p.getArg('[', ']') : string()); + string const width = p.getArg('{', '}'); + string const thickness = p.getArg('{', '}'); + context.check_layout(os); + begin_command_inset(os, "line", "rule"); + if (!offset.empty()) + os << "offset \"" << translate_len(offset) << "\"\n"; + os << "width \"" << translate_len(width) << "\"\n" + "height \"" << translate_len(thickness) << "\"\n"; + end_inset(os); } - else if (is_known(t.cs(), known_phrases)) { + else if (is_known(t.cs(), known_phrases) || + (t.cs() == "protect" && + p.next_token().cat() == catEscape && + is_known(p.next_token().cs(), known_phrases))) { + // LyX sometimes puts a \protect in front, so we have to ignore it // FIXME: This needs to be changed when bug 4752 is fixed. - char const * const * where = is_known(t.cs(), known_phrases); + where = is_known( + t.cs() == "protect" ? p.get_token().cs() : t.cs(), + known_phrases); context.check_layout(os); os << known_coded_phrases[where - known_phrases]; skip_spaces_braces(p); } - else if (is_known(t.cs(), known_ref_commands)) { - context.check_layout(os); - begin_command_inset(os, "ref", t.cs()); - // LyX cannot handle newlines in a latex command - // FIXME: Move the substitution into parser::getOpt()? - os << subst(p.getOpt(), "\n", " "); - os << "reference " << '"' << subst(p.verbatim_item(), "\n", " ") << '"' << "\n"; - end_inset(os); + else if ((where = is_known(t.cs(), known_ref_commands))) { + string const opt = p.getOpt(); + if (opt.empty()) { + context.check_layout(os); + begin_command_inset(os, "ref", + known_coded_ref_commands[where - known_ref_commands]); + os << "reference \"" + << convert_command_inset_arg(p.verbatim_item()) + << "\"\n"; + end_inset(os); + if (t.cs() == "vref" || t.cs() == "vpageref") + preamble.registerAutomaticallyLoadedPackage("varioref"); + + } else { + // LyX does not support optional arguments of ref commands + handle_ert(os, t.asInput() + '[' + opt + "]{" + + p.verbatim_item() + "}", context); + } } else if (use_natbib && @@ -2194,19 +3078,19 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, if (!after.empty()) { after.erase(0, 1); after.erase(after.length() - 1, 1); - // LyX cannot handle newlines in the parameter - after = subst(after, "\n", " "); + after = convert_command_inset_arg(after); } if (!before.empty()) { before.erase(0, 1); before.erase(before.length() - 1, 1); - // LyX cannot handle newlines in the parameter - before = subst(before, "\n", " "); + before = convert_command_inset_arg(before); } begin_command_inset(os, "citation", command); os << "after " << '"' << after << '"' << "\n"; os << "before " << '"' << before << '"' << "\n"; - os << "key " << '"' << p.verbatim_item() << '"' << "\n"; + os << "key \"" + << convert_command_inset_arg(p.verbatim_item()) + << "\"\n"; end_inset(os); } @@ -2220,7 +3104,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, p.get_token(); } char argumentOrder = '\0'; - vector const & options = used_packages["jurabib"]; + vector const options = + preamble.getPackageOptions("jurabib"); if (find(options.begin(), options.end(), "natbiborder") != options.end()) argumentOrder = 'n'; @@ -2262,9 +3147,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, else if (t.cs() == "cite" || t.cs() == "nocite") { context.check_layout(os); - // LyX cannot handle newlines in a latex command - string after = subst(p.getOptContent(), "\n", " "); - string key = subst(p.verbatim_item(), "\n", " "); + string after = convert_command_inset_arg(p.getArg('[', ']')); + string key = convert_command_inset_arg(p.verbatim_item()); // store the case that it is "\nocite{*}" to use it later for // the BibTeX inset if (key != "*") { @@ -2272,71 +3156,91 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, os << "after " << '"' << after << '"' << "\n"; os << "key " << '"' << key << '"' << "\n"; end_inset(os); - } else + } else if (t.cs() == "nocite") btprint = key; } else if (t.cs() == "index") { context.check_layout(os); - begin_inset(os, "Index\n"); + begin_inset(os, "Index idx\n"); os << "status collapsed\n"; - parse_text_in_inset(p, os, FLAG_ITEM, false, context); + parse_text_in_inset(p, os, FLAG_ITEM, false, context, "Index"); end_inset(os); } else if (t.cs() == "nomenclature") { context.check_layout(os); begin_command_inset(os, "nomenclature", "nomenclature"); - // LyX cannot handle newlines in a latex command - string prefix = subst(p.getOptContent(), "\n", " "); + string prefix = convert_command_inset_arg(p.getArg('[', ']')); if (!prefix.empty()) os << "prefix " << '"' << prefix << '"' << "\n"; - os << "symbol " << '"' << subst(p.verbatim_item(), "\n", " ") << '"' << "\n"; - os << "description " << '"' << subst(p.verbatim_item(), "\n", " ") << '"' << "\n"; + os << "symbol " << '"' + << convert_command_inset_arg(p.verbatim_item()); + os << "\"\ndescription \"" + << convert_command_inset_arg(p.verbatim_item()) + << "\"\n"; end_inset(os); + preamble.registerAutomaticallyLoadedPackage("nomencl"); } else if (t.cs() == "label") { context.check_layout(os); begin_command_inset(os, "label", "label"); - // LyX cannot handle newlines in a latex command - os << "name " << '"' << subst(p.verbatim_item(), "\n", " ") << '"' << "\n"; + os << "name \"" + << convert_command_inset_arg(p.verbatim_item()) + << "\"\n"; end_inset(os); } else if (t.cs() == "printindex") { context.check_layout(os); begin_command_inset(os, "index_print", "printindex"); + os << "type \"idx\"\n"; end_inset(os); skip_spaces_braces(p); + preamble.registerAutomaticallyLoadedPackage("makeidx"); + if (preamble.use_indices() == "true") + preamble.registerAutomaticallyLoadedPackage("splitidx"); } else if (t.cs() == "printnomenclature") { + string width = ""; + string width_type = ""; context.check_layout(os); begin_command_inset(os, "nomencl_print", "printnomenclature"); + // case of a custom width + if (p.hasOpt()) { + width = p.getArg('[', ']'); + width = translate_len(width); + width_type = "custom"; + } + // case of no custom width + // the case of no custom width but the width set + // 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('{', '}')); + if (label.empty() && width_type.empty()) + width_type = "none"; + os << "set_width \"" << width_type << "\"\n"; + if (width_type == "custom") + os << "width \"" << width << '\"'; end_inset(os); skip_spaces_braces(p); + preamble.registerAutomaticallyLoadedPackage("nomencl"); } - else if (t.cs() == "url") { - context.check_layout(os); - begin_inset(os, "Flex URL\n"); - os << "status collapsed\n"; - parse_text_in_inset(p, os, FLAG_ITEM, false, context); - end_inset(os); - } - - else if (LYX_FORMAT >= 408 && - (t.cs() == "textsuperscript" || t.cs() == "textsubscript")) { + else if ((t.cs() == "textsuperscript" || t.cs() == "textsubscript")) { context.check_layout(os); begin_inset(os, "script "); os << t.cs().substr(4) << '\n'; parse_text_in_inset(p, os, FLAG_ITEM, false, context); end_inset(os); + if (t.cs() == "textsubscript") + preamble.registerAutomaticallyLoadedPackage("subscript"); } - else if (is_known(t.cs(), known_quotes)) { - char const * const * where = is_known(t.cs(), known_quotes); + else if ((where = is_known(t.cs(), known_quotes))) { context.check_layout(os); begin_inset(os, "Quotes "); os << known_coded_quotes[where - known_quotes]; @@ -2348,22 +3252,17 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, skip_braces(p); } - else if (is_known(t.cs(), known_sizes) && + else if ((where = is_known(t.cs(), known_sizes)) && context.new_layout_allowed) { - char const * const * where = is_known(t.cs(), known_sizes); context.check_layout(os); TeXFont const oldFont = context.font; - // the font size index differs by 1, because the known_coded_sizes - // has additionally a "default" entry - context.font.size = known_coded_sizes[where - known_sizes + 1]; + context.font.size = known_coded_sizes[where - known_sizes]; output_font_change(os, oldFont, context.font); eat_whitespace(p, os, context, false); } - else if (is_known(t.cs(), known_font_families) && + else if ((where = is_known(t.cs(), known_font_families)) && context.new_layout_allowed) { - char const * const * where = - is_known(t.cs(), known_font_families); context.check_layout(os); TeXFont const oldFont = context.font; context.font.family = @@ -2372,10 +3271,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, eat_whitespace(p, os, context, false); } - else if (is_known(t.cs(), known_font_series) && + else if ((where = is_known(t.cs(), known_font_series)) && context.new_layout_allowed) { - char const * const * where = - is_known(t.cs(), known_font_series); context.check_layout(os); TeXFont const oldFont = context.font; context.font.series = @@ -2384,10 +3281,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, eat_whitespace(p, os, context, false); } - else if (is_known(t.cs(), known_font_shapes) && + else if ((where = is_known(t.cs(), known_font_shapes)) && context.new_layout_allowed) { - char const * const * where = - is_known(t.cs(), known_font_shapes); context.check_layout(os); TeXFont const oldFont = context.font; context.font.shape = @@ -2395,10 +3290,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, output_font_change(os, oldFont, context.font); eat_whitespace(p, os, context, false); } - else if (is_known(t.cs(), known_old_font_families) && + else if ((where = is_known(t.cs(), known_old_font_families)) && context.new_layout_allowed) { - char const * const * where = - is_known(t.cs(), known_old_font_families); context.check_layout(os); TeXFont const oldFont = context.font; context.font.init(); @@ -2409,10 +3302,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, eat_whitespace(p, os, context, false); } - else if (is_known(t.cs(), known_old_font_series) && + else if ((where = is_known(t.cs(), known_old_font_series)) && context.new_layout_allowed) { - char const * const * where = - is_known(t.cs(), known_old_font_series); context.check_layout(os); TeXFont const oldFont = context.font; context.font.init(); @@ -2423,10 +3314,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, eat_whitespace(p, os, context, false); } - else if (is_known(t.cs(), known_old_font_shapes) && + else if ((where = is_known(t.cs(), known_old_font_shapes)) && context.new_layout_allowed) { - char const * const * where = - is_known(t.cs(), known_old_font_shapes); context.check_layout(os); TeXFont const oldFont = context.font; context.font.init(); @@ -2442,19 +3331,15 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, // save the language for the case that a // \foreignlanguage is used - context.font.language = subst(p.verbatim_item(), "\n", " "); - os << "\\lang " << context.font.language << "\n"; + context.font.language = babel2lyx(p.verbatim_item()); + os << "\n\\lang " << context.font.language << "\n"; } else if (t.cs() == "foreignlanguage") { - context.check_layout(os); - os << "\n\\lang " << subst(p.verbatim_item(), "\n", " ") << "\n"; - os << subst(p.verbatim_item(), "\n", " "); - // FIXME: the second argument of selectlanguage - // has to be parsed (like for \textsf, for - // example). - // set back to last selectlanguage - os << "\n\\lang " << context.font.language << "\n"; + string const lang = babel2lyx(p.verbatim_item()); + parse_text_attributes(p, os, FLAG_ITEM, outer, + context, "\\lang", + context.font.language, lang); } else if (t.cs() == "inputencoding") { @@ -2463,27 +3348,11 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, p.setEncoding(enc); } - else if (t.cs() == "ldots") { + else if ((where = is_known(t.cs(), known_special_chars))) { context.check_layout(os); - os << "\\SpecialChar \\ldots{}\n"; - skip_spaces_braces(p); - } - - else if (t.cs() == "lyxarrow") { - context.check_layout(os); - os << "\\SpecialChar \\menuseparator\n"; - skip_spaces_braces(p); - } - - else if (t.cs() == "textcompwordmark") { - context.check_layout(os); - os << "\\SpecialChar \\textcompwordmark{}\n"; - skip_spaces_braces(p); - } - - else if (t.cs() == "slash") { - context.check_layout(os); - os << "\\SpecialChar \\slash{}\n"; + os << "\\SpecialChar \\" + << known_coded_special_chars[where - known_special_chars] + << '\n'; skip_spaces_braces(p); } @@ -2573,15 +3442,19 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, // try to see whether the string is in unicodesymbols docstring rem; string command = t.asInput() + "{" - + trim(p.verbatim_item()) + + trimSpaceAndEol(p.verbatim_item()) + "}"; - docstring s = encodings.fromLaTeXCommand(from_utf8(command), rem); + set req; + docstring s = encodings.fromLaTeXCommand(from_utf8(command), + Encodings::TEXT_CMD | Encodings::MATH_CMD, rem, &req); if (!s.empty()) { if (!rem.empty()) cerr << "When parsing " << command << ", result is " << to_utf8(s) << "+" << to_utf8(rem) << endl; os << to_utf8(s); + for (set::const_iterator it = req.begin(); it != req.end(); it++) + preamble.registerAutomaticallyLoadedPackage(*it); } else // we did not find a non-ert version handle_ert(os, command, context); @@ -2715,6 +3588,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, begin_command_inset(os, "include", name); os << "preview false\n" "filename \"" << outname << "\"\n"; + if (t.cs() == "verbatiminput") + preamble.registerAutomaticallyLoadedPackage("verbatim"); } end_inset(os); } @@ -2722,9 +3597,30 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, else if (t.cs() == "bibliographystyle") { // store new bibliographystyle bibliographystyle = p.verbatim_item(); - // output new bibliographystyle. - // This is only necessary if used in some other macro than \bibliography. - handle_ert(os, "\\bibliographystyle{" + bibliographystyle + "}", context); + // If any other command than \bibliography and + // \nocite{*} follows, we need to output the style + // (because it might be used by that command). + // Otherwise, it will automatically be output by LyX. + p.pushPosition(); + bool output = true; + for (Token t2 = p.get_token(); p.good(); t2 = p.get_token()) { + if (t2.cat() == catBegin) + break; + if (t2.cat() != catEscape) + continue; + if (t2.cs() == "nocite") { + if (p.getArg('{', '}') == "*") + continue; + } else if (t2.cs() == "bibliography") + output = false; + break; + } + p.popPosition(); + if (output) { + handle_ert(os, + "\\bibliographystyle{" + bibliographystyle + '}', + context); + } } else if (t.cs() == "bibliography") { @@ -2743,20 +3639,76 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, end_inset(os); } - else if (t.cs() == "parbox") - parse_box(p, os, FLAG_ITEM, outer, context, true); - + else if (t.cs() == "parbox") { + // 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); + // eat the '{' + if (p.next_token().cat() == catBegin) + p.get_token(); + p.skip_spaces(true); + Token to = p.get_token(); + bool shaded = false; + if (to.asInput() == "\\begin") { + p.skip_spaces(true); + if (p.getArg('{', '}') == "shaded") + shaded = true; + } + p.popPosition(); + if (shaded) { + parse_outer_box(p, os, FLAG_ITEM, outer, + context, "parbox", "shaded"); + } else + parse_box(p, os, 0, FLAG_ITEM, outer, context, + "", "", t.cs()); + } + + else if (t.cs() == "ovalbox" || t.cs() == "Ovalbox" || + t.cs() == "shadowbox" || t.cs() == "doublebox") + parse_outer_box(p, os, FLAG_ITEM, outer, context, t.cs(), ""); + + else if (t.cs() == "framebox") { + if (p.next_token().character() == '(') { + //the syntax is: \framebox(x,y)[position]{content} + string arg = t.asInput(); + arg += p.getFullParentheseArg(); + arg += p.getFullOpt(); + eat_whitespace(p, os, context, false); + handle_ert(os, arg + '{', context); + eat_whitespace(p, os, context, false); + parse_text(p, os, FLAG_ITEM, outer, context); + handle_ert(os, "}", context); + } else { + string special = p.getFullOpt(); + special += p.getOpt(); + parse_outer_box(p, os, FLAG_ITEM, outer, + context, t.cs(), special); + } + } + //\makebox() is part of the picture environment and different from \makebox{} - //\makebox{} will be parsed by parse_box when bug 2956 is fixed + //\makebox{} will be parsed by parse_box else if (t.cs() == "makebox") { - string arg = t.asInput(); - if (p.next_token().character() == '(') + if (p.next_token().character() == '(') { //the syntax is: \makebox(x,y)[position]{content} + string arg = t.asInput(); arg += p.getFullParentheseArg(); - else - //the syntax is: \makebox[width][position]{content} arg += p.getFullOpt(); - handle_ert(os, arg + p.getFullOpt(), context); + eat_whitespace(p, os, context, false); + handle_ert(os, arg + '{', context); + eat_whitespace(p, os, context, false); + parse_text(p, os, FLAG_ITEM, outer, context); + handle_ert(os, "}", context); + } else + //the syntax is: \makebox[width][position]{content} + parse_box(p, os, 0, FLAG_ITEM, outer, context, + "", "", t.cs()); } else if (t.cs() == "smallskip" || @@ -2770,8 +3722,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, skip_spaces_braces(p); } - else if (is_known(t.cs(), known_spaces)) { - char const * const * where = is_known(t.cs(), known_spaces); + else if ((where = is_known(t.cs(), known_spaces))) { context.check_layout(os); begin_inset(os, "space "); os << '\\' << known_coded_spaces[where - known_spaces] @@ -2801,11 +3752,18 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, skip_spaces_braces(p); } - else if (t.cs() == "newcommand" || - t.cs() == "providecommand" || - t.cs() == "renewcommand") { - // providecommand could be handled by parse_command(), - // but we need to call add_known_command() here. + else if (t.cs() == "DeclareRobustCommand" || + t.cs() == "DeclareRobustCommandx" || + t.cs() == "newcommand" || + t.cs() == "newcommandx" || + t.cs() == "providecommand" || + t.cs() == "providecommandx" || + t.cs() == "renewcommand" || + t.cs() == "renewcommandx") { + // DeclareRobustCommand, DeclareRobustCommandx, + // providecommand and providecommandx could be handled + // by parse_command(), but we need to call + // add_known_command() here. string name = t.asInput(); if (p.next_token().asInput() == "*") { // Starred form. Eat '*' @@ -2813,14 +3771,18 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, name += '*'; } string const command = p.verbatim_item(); - string const opt1 = p.getOpt(); + string const opt1 = p.getFullOpt(); string const opt2 = p.getFullOpt(); add_known_command(command, opt1, !opt2.empty()); string const ert = name + '{' + command + '}' + opt1 + opt2 + '{' + p.verbatim_item() + '}'; - if (t.cs() == "providecommand") + if (t.cs() == "DeclareRobustCommand" || + t.cs() == "DeclareRobustCommandx" || + t.cs() == "providecommand" || + t.cs() == "providecommandx" || + name[name.length()-1] == '*') handle_ert(os, ert, context); else { context.check_layout(os); @@ -2967,13 +3929,124 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, } } + // The single '=' is meant here. + else if ((newinsetlayout = findInsetLayout(context.textclass, t.cs(), true))) { + p.skip_spaces(); + context.check_layout(os); + begin_inset(os, "Flex "); + os << to_utf8(newinsetlayout->name()) << '\n' + << "status collapsed\n"; + parse_text_in_inset(p, os, FLAG_ITEM, false, context, newinsetlayout); + end_inset(os); + } + + else if (t.cs() == "includepdf") { + p.skip_spaces(); + string const arg = p.getArg('[', ']'); + map opts; + vector keys; + split_map(arg, opts, keys); + string name = normalize_filename(p.verbatim_item()); + string const path = getMasterFilePath(); + // We want to preserve relative / absolute filenames, + // therefore path is only used for testing + if (!makeAbsPath(name, path).exists()) { + // The file extension is probably missing. + // Now try to find it out. + char const * const pdfpages_format[] = {"pdf", 0}; + string const pdftex_name = + find_file(name, path, pdfpages_format); + if (!pdftex_name.empty()) { + name = pdftex_name; + pdflatex = true; + } + } + if (makeAbsPath(name, path).exists()) + fix_relative_filename(name); + else + cerr << "Warning: Could not find file '" + << name << "'." << endl; + // write output + context.check_layout(os); + begin_inset(os, "External\n\ttemplate "); + os << "PDFPages\n\tfilename " + << name << "\n"; + // parse the options + if (opts.find("pages") != opts.end()) + os << "\textra LaTeX \"pages=" + << opts["pages"] << "\"\n"; + if (opts.find("angle") != opts.end()) + os << "\trotateAngle " + << opts["angle"] << '\n'; + if (opts.find("origin") != opts.end()) { + ostringstream ss; + string const opt = opts["origin"]; + if (opt == "tl") ss << "topleft"; + if (opt == "bl") ss << "bottomleft"; + if (opt == "Bl") ss << "baselineleft"; + if (opt == "c") ss << "center"; + if (opt == "tc") ss << "topcenter"; + if (opt == "bc") ss << "bottomcenter"; + if (opt == "Bc") ss << "baselinecenter"; + if (opt == "tr") ss << "topright"; + if (opt == "br") ss << "bottomright"; + if (opt == "Br") ss << "baselineright"; + if (!ss.str().empty()) + os << "\trotateOrigin " << ss.str() << '\n'; + else + cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n"; + } + if (opts.find("width") != opts.end()) + os << "\twidth " + << translate_len(opts["width"]) << '\n'; + if (opts.find("height") != opts.end()) + os << "\theight " + << translate_len(opts["height"]) << '\n'; + if (opts.find("keepaspectratio") != opts.end()) + os << "\tkeepAspectRatio\n"; + end_inset(os); + context.check_layout(os); + } + + else if (t.cs() == "loadgame") { + p.skip_spaces(); + string name = normalize_filename(p.verbatim_item()); + string const path = getMasterFilePath(); + // We want to preserve relative / absolute filenames, + // therefore path is only used for testing + if (!makeAbsPath(name, path).exists()) { + // The file extension is probably missing. + // Now try to find it out. + char const * const lyxskak_format[] = {"fen", 0}; + string const lyxskak_name = + find_file(name, path, lyxskak_format); + if (!lyxskak_name.empty()) + name = lyxskak_name; + } + if (makeAbsPath(name, path).exists()) + fix_relative_filename(name); + else + cerr << "Warning: Could not find file '" + << name << "'." << endl; + context.check_layout(os); + begin_inset(os, "External\n\ttemplate "); + os << "ChessDiagram\n\tfilename " + << name << "\n"; + end_inset(os); + context.check_layout(os); + // after a \loadgame follows a \showboard + if (p.get_token().asInput() == "showboard") + p.get_token(); + } + else { // try to see whether the string is in unicodesymbols // Only use text mode commands, since we are in text mode here, // and math commands may be invalid (bug 6797) docstring rem; + set req; docstring s = encodings.fromLaTeXCommand(from_utf8(t.asInput()), - rem, Encodings::TEXT_CMD); + Encodings::TEXT_CMD, rem, &req); if (!s.empty()) { if (!rem.empty()) cerr << "When parsing " << t.cs() @@ -2982,6 +4055,8 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer, context.check_layout(os); os << to_utf8(s); skip_spaces_braces(p); + for (set::const_iterator it = req.begin(); it != req.end(); it++) + preamble.registerAutomaticallyLoadedPackage(*it); } //cerr << "#: " << t << " mode: " << mode << endl; // heuristic: read up to next non-nested space