3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Jean-Marc Lasgouttes
9 * Full author contact details are available in file CREDITS.
18 #include "FloatList.h"
19 #include "lengthcommon.h"
20 #include "support/lstrings.h"
21 #include "support/tostr.h"
22 #include "support/filetools.h"
29 using lyx::support::rtrim;
30 using lyx::support::suffixIs;
31 using lyx::support::contains;
32 using lyx::support::subst;
39 using std::ostringstream;
40 using std::istringstream;
45 // thin wrapper around parse_text using a string
46 string parse_text(Parser & p, unsigned flags, const bool outer,
50 parse_text(p, os, flags, outer, context);
54 // parses a subdocument, usually useful in insets (whence the name)
55 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
58 Context newcontext(true, context.textclass);
59 parse_text(p, os, flags, outer, newcontext);
60 newcontext.check_end_layout(os);
64 // parses a paragraph snippet, useful for example for \emph{...}
65 void parse_text_snippet(Parser & p, ostream & os, unsigned flags, bool outer,
68 Context newcontext(false, context.textclass);
69 parse_text(p, os, flags, outer, newcontext);
70 // should not be needed
71 newcontext.check_end_layout(os);
77 char const * known_latex_commands[] = { "ref", "cite", "label", "index",
78 "printindex", "pageref", "url", "vref", "vpageref", "prettyref", "eqref", 0 };
80 // LaTeX names for quotes
81 char const * known_quotes[] = { "glqq", "grqq", "quotedblbase",
82 "textquotedblleft", "quotesinglbase", "guilsinglleft", "guilsinglright", 0};
84 // the same as known_quotes with .lyx names
85 char const * known_coded_quotes[] = { "gld", "grd", "gld",
86 "grd", "gls", "fls", "frd", 0};
88 char const * known_sizes[] = { "tiny", "scriptsize", "footnotesize",
89 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
91 char const * known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
92 "small", "normal", "large", "larger", "largest", "huge", "giant", 0};
94 // splits "x=z, y=b" into a map
95 map<string, string> split_map(string const & s)
97 map<string, string> res;
100 for (size_t i = 0; i < v.size(); ++i) {
101 size_t const pos = v[i].find('=');
102 string const index = v[i].substr(0, pos);
103 string const value = v[i].substr(pos + 1, string::npos);
104 res[trim(index)] = trim(value);
111 * Split a LaTeX length into value and unit.
112 * The latter can be a real unit like "pt", or a latex length variable
113 * like "\textwidth". The unit may contain additional stuff like glue
114 * lengths, but we don't care, because such lengths are ERT anyway.
115 * \return true if \param value and \param unit are valid.
117 bool splitLatexLength(string const & len, string & value, string & unit)
121 const string::size_type i = len.find_first_not_of(" -+0123456789.,");
122 //'4,5' is a valid LaTeX length number. Change it to '4.5'
123 string const length = subst(len, ',', '.');
124 if (i == string::npos)
127 if (len[0] == '\\') {
128 // We had something like \textwidth without a factor
134 value = trim(string(length, 0, i));
138 // 'cM' is a valid LaTeX length unit. Change it to 'cm'
139 if (contains(len, '\\'))
140 unit = trim(string(len, i));
142 unit = lyx::support::lowercase(trim(string(len, i)));
147 // A simple function to translate a latex length to something lyx can
148 // understand. Not perfect, but rather best-effort.
149 bool translate_len(string const & length, string & valstring, string & unit)
151 if (!splitLatexLength(length, valstring, unit))
153 // LyX uses percent values
155 istringstream iss(valstring);
160 string const percentval = oss.str();
162 if (unit.empty() || unit[0] != '\\')
164 string::size_type const i = unit.find(' ');
165 string const endlen = (i == string::npos) ? string() : string(unit, i);
166 if (unit == "\\textwidth") {
167 valstring = percentval;
168 unit = "text%" + endlen;
169 } else if (unit == "\\columnwidth") {
170 valstring = percentval;
171 unit = "col%" + endlen;
172 } else if (unit == "\\paperwidth") {
173 valstring = percentval;
174 unit = "page%" + endlen;
175 } else if (unit == "\\linewidth") {
176 valstring = percentval;
177 unit = "line%" + endlen;
178 } else if (unit == "\\paperheight") {
179 valstring = percentval;
180 unit = "pheight%" + endlen;
181 } else if (unit == "\\textheight") {
182 valstring = percentval;
183 unit = "theight%" + endlen;
189 string translate_len(string const & length)
193 if (translate_len(length, value, unit))
195 // If the input is invalid, return what we have.
201 * Translates a LaTeX length into \param value, \param unit and
202 * \param special parts suitable for a box inset.
203 * The difference from translate_len() is that a box inset knows about
204 * some special "units" that are stored in \param special.
206 void translate_box_len(string const & length, string & value, string & unit, string & special)
208 if (translate_len(length, value, unit)) {
209 if (unit == "\\height" || unit == "\\depth" ||
210 unit == "\\totalheight" || unit == "\\width") {
211 special = unit.substr(1);
212 // The unit is not used, but LyX requires a dummy setting
224 void begin_inset(ostream & os, string const & name)
226 os << "\n\\begin_inset " << name;
230 void end_inset(ostream & os)
232 os << "\n\\end_inset \n\n";
236 void skip_braces(Parser & p)
238 if (p.next_token().cat() != catBegin)
241 if (p.next_token().cat() == catEnd) {
249 void handle_ert(ostream & os, string const & s, Context & context, bool check_layout = true)
252 // We must have a valid layout before outputting the ERT inset.
253 context.check_layout(os);
255 Context newcontext(true, context.textclass);
256 begin_inset(os, "ERT");
257 os << "\nstatus collapsed\n";
258 newcontext.check_layout(os);
259 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
261 os << "\n\\backslash \n";
262 else if (*it == '\n')
263 os << "\n\\newline \n";
267 newcontext.check_end_layout(os);
272 void handle_comment(ostream & os, string const & s, Context & context)
274 // TODO: Handle this better
275 Context newcontext(true, context.textclass);
276 begin_inset(os, "ERT");
277 os << "\nstatus collapsed\n";
278 newcontext.check_layout(os);
279 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
281 os << "\n\\backslash \n";
285 // make sure that our comment is the last thing on the line
287 newcontext.check_end_layout(os);
292 class isLayout : public std::unary_function<LyXLayout_ptr, bool> {
294 isLayout(string const name) : name_(name) {}
295 bool operator()(LyXLayout_ptr const & ptr) const {
296 return ptr->latexname() == name_;
303 LyXLayout_ptr findLayout(LyXTextClass const & textclass,
306 LyXTextClass::const_iterator beg = textclass.begin();
307 LyXTextClass::const_iterator end = textclass.end();
309 LyXTextClass::const_iterator
310 it = std::find_if(beg, end, isLayout(name));
312 return (it == end) ? LyXLayout_ptr() : *it;
316 void output_command_layout(ostream & os, Parser & p, bool outer,
317 Context & parent_context,
318 LyXLayout_ptr newlayout)
320 parent_context.check_end_layout(os);
321 Context context(true, parent_context.textclass, newlayout,
322 parent_context.layout);
323 context.check_deeper(os);
324 context.check_layout(os);
325 if (context.layout->optionalargs > 0) {
327 if (p.next_token().character() == '[') {
328 p.get_token(); // eat '['
329 begin_inset(os, "OptArg\n");
330 os << "status collapsed\n\n";
331 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
335 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
336 context.check_end_layout(os);
337 context.check_end_deeper(os);
338 // We don't need really a new paragraph, but
339 // we must make sure that the next item gets a \begin_layout.
340 parent_context.new_paragraph(os);
345 * Output a space if necessary.
346 * This function gets called for every whitespace token.
348 * We have three cases here:
349 * 1. A space must be suppressed. Example: The lyxcode case below
350 * 2. A space may be suppressed. Example: Spaces before "\par"
351 * 3. A space must not be suppressed. Example: A space between two words
353 * We currently handle only 1. and 3 and from 2. only the case of
354 * spaces before newlines as a side effect.
356 * 2. could be used to suppress as many spaces as possible. This has two effects:
357 * - Reimporting LyX generated LaTeX files changes almost no whitespace
358 * - Superflous whitespace from non LyX generated LaTeX files is removed.
359 * The drawback is that the logic inside the function becomes
360 * complicated, and that is the reason why it is not implemented.
362 void check_space(Parser const & p, ostream & os, Context & context)
364 Token const next = p.next_token();
365 Token const curr = p.curr_token();
366 // A space before a single newline and vice versa must be ignored
367 // LyX emits a newline before \end{lyxcode}.
368 // This newline must be ignored,
369 // otherwise LyX will add an additional protected space.
370 if (next.cat() == catSpace ||
371 next.cat() == catNewline ||
372 (next.cs() == "end" && context.layout->free_spacing && curr.cat() == catNewline)) {
375 context.check_layout(os);
381 * Check wether \param command is a known command. If yes,
382 * handle the command with all arguments.
383 * \return true if the command was parsed, false otherwise.
385 bool parse_command(string const & command, Parser & p, ostream & os,
386 bool outer, Context & context)
388 if (known_commands.find(command) != known_commands.end()) {
389 vector<ArgumentType> const & template_arguments = known_commands[command];
390 string ert = command;
391 size_t no_arguments = template_arguments.size();
392 for (size_t i = 0; i < no_arguments; ++i) {
393 switch (template_arguments[i]) {
395 // This argument contains regular LaTeX
396 handle_ert(os, ert + '{', context);
397 parse_text(p, os, FLAG_ITEM, outer, context);
401 // This argument may contain special characters
402 ert += '{' + p.verbatim_item() + '}';
409 handle_ert(os, ert, context);
416 /// Parses a minipage or parbox
417 void parse_box(Parser & p, ostream & os, unsigned flags, bool outer,
418 Context & parent_context, bool use_parbox)
422 string height_value = "0";
423 string height_unit = "pt";
424 string height_special = "none";
426 if (p.next_token().asInput() == "[") {
427 position = p.getArg('[', ']');
428 if (position != "t" && position != "c" && position != "b") {
430 cerr << "invalid position for minipage/parbox" << endl;
432 if (p.next_token().asInput() == "[") {
433 latex_height = p.getArg('[', ']');
434 translate_box_len(latex_height, height_value, height_unit, height_special);
436 if (p.next_token().asInput() == "[") {
437 inner_pos = p.getArg('[', ']');
438 if (inner_pos != "c" && inner_pos != "t" &&
439 inner_pos != "b" && inner_pos != "s") {
440 inner_pos = position;
441 cerr << "invalid inner_pos for minipage/parbox"
449 string const latex_width = p.verbatim_item();
450 translate_len(latex_width, width_value, width_unit);
451 if (contains(width_unit, '\\') || contains(height_unit, '\\')) {
452 // LyX can't handle length variables
457 ss << "\\begin{minipage}";
458 if (!position.empty())
459 ss << '[' << position << ']';
460 if (!latex_height.empty())
461 ss << '[' << latex_height << ']';
462 if (!inner_pos.empty())
463 ss << '[' << inner_pos << ']';
464 ss << "{" << latex_width << "}";
467 handle_ert(os, ss.str(), parent_context);
468 parent_context.new_paragraph(os);
469 parse_text_in_inset(p, os, flags, outer, parent_context);
471 handle_ert(os, "}", parent_context);
473 handle_ert(os, "\\end{minipage}", parent_context);
475 // LyX does not like empty positions, so we have
476 // to set them to the LaTeX default values here.
477 if (position.empty())
479 if (inner_pos.empty())
480 inner_pos = position;
481 parent_context.check_layout(os);
482 begin_inset(os, "Box Frameless\n");
483 os << "position \"" << position << "\"\n";
484 os << "hor_pos \"c\"\n";
485 os << "has_inner_box 1\n";
486 os << "inner_pos \"" << inner_pos << "\"\n";
487 os << "use_parbox " << use_parbox << "\n";
488 os << "width \"" << width_value << width_unit << "\"\n";
489 os << "special \"none\"\n";
490 os << "height \"" << height_value << height_unit << "\"\n";
491 os << "height_special \"" << height_special << "\"\n";
492 os << "status open\n\n";
493 parse_text_in_inset(p, os, flags, outer, parent_context);
495 #ifdef PRESERVE_LAYOUT
496 // lyx puts a % after the end of the minipage
497 if (p.next_token().cat() == catNewline && p.next_token().cs().size() > 1) {
499 //handle_comment(os, "%dummy", parent_context);
502 parent_context.new_paragraph(os);
504 else if (p.next_token().cat() == catSpace || p.next_token().cat() == catNewline) {
505 //handle_comment(os, "%dummy", parent_context);
508 // We add a protected space if something real follows
509 if (p.good() && p.next_token().cat() != catComment) {
510 os << "\\InsetSpace ~\n";
518 void parse_environment(Parser & p, ostream & os, bool outer,
519 Context & parent_context)
521 LyXLayout_ptr newlayout;
522 string const name = p.getArg('{', '}');
523 const bool is_starred = suffixIs(name, '*');
524 string const unstarred_name = rtrim(name, "*");
525 active_environments.push_back(name);
528 if (is_math_env(name)) {
529 parent_context.check_layout(os);
530 begin_inset(os, "Formula ");
531 os << "\\begin{" << name << "}";
532 parse_math(p, os, FLAG_END, MATH_MODE);
533 os << "\\end{" << name << "}";
537 else if (name == "tabular") {
538 parent_context.check_layout(os);
539 begin_inset(os, "Tabular ");
540 handle_tabular(p, os, parent_context);
544 else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
545 parent_context.check_layout(os);
546 begin_inset(os, "Float " + unstarred_name + "\n");
547 if (p.next_token().asInput() == "[") {
548 os << "placement " << p.getArg('[', ']') << '\n';
550 os << "wide " << tostr(is_starred)
551 << "\nstatus open\n\n";
552 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
554 // We don't need really a new paragraph, but
555 // we must make sure that the next item gets a \begin_layout.
556 parent_context.new_paragraph(os);
559 else if (name == "minipage")
560 parse_box(p, os, FLAG_END, outer, parent_context, false);
562 // Alignment settings
563 else if (name == "center" || name == "flushleft" || name == "flushright" ||
564 name == "centering" || name == "raggedright" || name == "raggedleft") {
565 // We must begin a new paragraph if not already done
566 if (! parent_context.atParagraphStart()) {
567 parent_context.check_end_layout(os);
568 parent_context.new_paragraph(os);
570 if (name == "flushleft" || name == "raggedright")
571 parent_context.add_extra_stuff("\\align left ");
572 else if (name == "flushright" || name == "raggedleft")
573 parent_context.add_extra_stuff("\\align right ");
575 parent_context.add_extra_stuff("\\align center ");
576 parse_text(p, os, FLAG_END, outer, parent_context);
577 // Just in case the environment is empty ..
578 parent_context.extra_stuff.erase();
579 // We must begin a new paragraph to reset the alignment
580 parent_context.new_paragraph(os);
583 // The single '=' is meant here.
584 else if ((newlayout = findLayout(parent_context.textclass, name)).get() &&
585 newlayout->isEnvironment()) {
586 Context context(true, parent_context.textclass, newlayout,
587 parent_context.layout);
588 parent_context.check_end_layout(os);
589 switch (context.layout->latextype) {
590 case LATEX_LIST_ENVIRONMENT:
591 context.extra_stuff = "\\labelwidthstring "
592 + p.verbatim_item() + '\n';
595 case LATEX_BIB_ENVIRONMENT:
596 p.verbatim_item(); // swallow next arg
602 context.check_deeper(os);
603 parse_text(p, os, FLAG_END, outer, context);
604 context.check_end_layout(os);
605 context.check_end_deeper(os);
606 parent_context.new_paragraph(os);
609 else if (name == "appendix") {
610 // This is no good latex style, but it works and is used in some documents...
611 parent_context.check_end_layout(os);
612 Context context(true, parent_context.textclass, parent_context.layout,
613 parent_context.layout);
614 context.check_layout(os);
615 os << "\\start_of_appendix\n";
616 parse_text(p, os, FLAG_END, outer, context);
617 context.check_end_layout(os);
620 else if (name == "comment") {
621 parent_context.check_layout(os);
622 begin_inset(os, "Note Comment\n");
623 os << "status open\n";
624 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
628 else if (name == "lyxgreyedout") {
629 parent_context.check_layout(os);
630 begin_inset(os, "Note Greyedout\n");
631 os << "status open\n";
632 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
636 else if (name == "tabbing") {
637 // We need to remember that we have to handle '\=' specially
638 handle_ert(os, "\\begin{" + name + "}", parent_context);
639 parse_text_snippet(p, os, FLAG_END | FLAG_TABBING, outer, parent_context);
640 handle_ert(os, "\\end{" + name + "}", parent_context);
644 handle_ert(os, "\\begin{" + name + "}", parent_context);
645 parse_text_snippet(p, os, FLAG_END, outer, parent_context);
646 handle_ert(os, "\\end{" + name + "}", parent_context);
649 active_environments.pop_back();
654 } // anonymous namespace
659 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
662 LyXLayout_ptr newlayout;
663 // Store the latest bibliographystyle (needed for bibtex inset)
664 string bibliographystyle;
666 Token const & t = p.get_token();
669 cerr << "t: " << t << " flags: " << flags << "\n";
672 if (flags & FLAG_ITEM) {
673 if (t.cat() == catSpace)
677 if (t.cat() == catBegin) {
678 // skip the brace and collect everything to the next matching
680 flags |= FLAG_BRACE_LAST;
684 // handle only this single token, leave the loop if done
688 if (t.character() == ']' && (flags & FLAG_BRACK_LAST))
694 if (t.cat() == catMath) {
695 // we are inside some text mode thingy, so opening new math is allowed
696 context.check_layout(os);
697 begin_inset(os, "Formula ");
698 Token const & n = p.get_token();
699 if (n.cat() == catMath && outer) {
700 // TeX's $$...$$ syntax for displayed math
702 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
704 p.get_token(); // skip the second '$' token
706 // simple $...$ stuff
709 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
715 else if (t.cat() == catSuper || t.cat() == catSub)
716 cerr << "catcode " << t << " illegal in text mode\n";
718 // Basic support for english quotes. This should be
719 // extended to other quotes, but is not so easy (a
720 // left english quote is the same as a right german
722 else if (t.asInput() == "`"
723 && p.next_token().asInput() == "`") {
724 context.check_layout(os);
725 begin_inset(os, "Quotes ");
731 else if (t.asInput() == "'"
732 && p.next_token().asInput() == "'") {
733 context.check_layout(os);
734 begin_inset(os, "Quotes ");
741 else if (t.cat() == catSpace || (t.cat() == catNewline && t.cs().size() == 1))
742 check_space(p, os, context);
744 else if (t.cat() == catLetter ||
745 t.cat() == catOther ||
746 t.cat() == catAlign ||
747 t.cat() == catParameter) {
748 context.check_layout(os);
752 else if (t.cat() == catNewline || (t.cat() == catEscape && t.cs() == "par")) {
754 context.new_paragraph(os);
757 else if (t.cat() == catActive) {
758 context.check_layout(os);
759 if (t.character() == '~') {
760 if (context.layout->free_spacing)
763 os << "\\InsetSpace ~\n";
768 else if (t.cat() == catBegin) {
769 // special handling of size changes
770 context.check_layout(os);
771 bool const is_size = is_known(p.next_token().cs(), known_sizes);
772 Token const prev = p.prev_token();
773 string const s = parse_text(p, FLAG_BRACE_LAST, outer, context);
774 if (s.empty() && (p.next_token().character() == '`' ||
775 (prev.character() == '-' && p.next_token().character())))
776 ; // ignore it in {}`` or -{}-
777 else if (is_size || s == "[" || s == "]" || s == "*")
780 handle_ert(os, "{", context, false);
781 // s will end the current layout and begin a new one if necessary
783 handle_ert(os, "}", context);
787 else if (t.cat() == catEnd) {
788 if (flags & FLAG_BRACE_LAST) {
791 cerr << "stray '}' in text\n";
792 handle_ert(os, "}", context);
795 else if (t.cat() == catComment) {
796 context.check_layout(os);
797 if (!t.cs().empty()) {
798 handle_comment(os, '%' + t.cs(), context);
799 if (p.next_token().cat() == catNewline) {
800 // A newline after a comment line starts a new paragraph
801 context.new_paragraph(os);
814 else if (t.cs() == "(") {
815 context.check_layout(os);
816 begin_inset(os, "Formula");
818 parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
823 else if (t.cs() == "[") {
824 context.check_layout(os);
825 begin_inset(os, "Formula");
827 parse_math(p, os, FLAG_EQUATION, MATH_MODE);
832 else if (t.cs() == "begin")
833 parse_environment(p, os, outer, context);
835 else if (t.cs() == "end") {
836 if (flags & FLAG_END) {
837 // eat environment name
838 string const name = p.getArg('{', '}');
839 if (name != active_environment())
840 cerr << "\\end{" + name + "} does not match \\begin{"
841 + active_environment() + "}\n";
844 p.error("found 'end' unexpectedly");
847 else if (t.cs() == "item") {
851 if (p.next_token().character() == '[') {
852 p.get_token(); // eat '['
853 Context newcontext(false, context.textclass);
854 s = parse_text(p, FLAG_BRACK_LAST, outer, newcontext);
858 context.check_layout(os);
860 if (context.layout->labeltype != LABEL_MANUAL) {
861 // lyx does not support \item[\mybullet] in itemize environments
862 handle_ert(os, "[", context);
864 handle_ert(os, "]", context);
865 } else if (!s.empty()) {
866 // The space is needed to separate the item from the rest of the sentence.
873 else if (t.cs() == "bibitem") {
875 context.check_layout(os);
878 os << '{' << p.verbatim_item() << '}' << "\n";
881 else if (t.cs() == "def") {
883 context.check_layout(os);
884 string name = p.get_token().cs();
885 while (p.next_token().cat() != catBegin)
886 name += p.get_token().asString();
887 handle_ert(os, "\\def\\" + name + '{' + p.verbatim_item() + '}', context);
890 else if (t.cs() == "noindent") {
892 context.add_extra_stuff("\\noindent ");
895 else if (t.cs() == "appendix") {
897 context.add_extra_stuff("\\start_of_appendix ");
900 // Must attempt to parse "Section*" before "Section".
901 else if ((p.next_token().asInput() == "*") &&
902 // The single '=' is meant here.
903 (newlayout = findLayout(context.textclass,
904 t.cs() + '*')).get() &&
905 newlayout->isCommand()) {
907 output_command_layout(os, p, outer, context, newlayout);
911 // The single '=' is meant here.
912 else if ((newlayout = findLayout(context.textclass, t.cs())).get() &&
913 newlayout->isCommand()) {
914 output_command_layout(os, p, outer, context, newlayout);
918 else if (t.cs() == "includegraphics") {
919 map<string, string> opts = split_map(p.getArg('[', ']'));
920 string name = subst(p.verbatim_item(), "\\lyxdot ", ".");
922 context.check_layout(os);
923 begin_inset(os, "Graphics ");
924 os << "\n\tfilename " << name << '\n';
925 if (opts.find("width") != opts.end())
927 << translate_len(opts["width"]) << '\n';
928 if (opts.find("height") != opts.end())
930 << translate_len(opts["height"]) << '\n';
931 if (opts.find("scale") != opts.end()) {
932 istringstream iss(opts["scale"]);
936 os << "\tscale " << val << '\n';
938 if (opts.find("angle") != opts.end())
939 os << "\trotateAngle "
940 << opts["angle"] << '\n';
941 if (opts.find("origin") != opts.end()) {
943 string const opt = opts["origin"];
944 if (opt.find('l') != string::npos) ss << "left";
945 if (opt.find('r') != string::npos) ss << "right";
946 if (opt.find('c') != string::npos) ss << "center";
947 if (opt.find('t') != string::npos) ss << "Top";
948 if (opt.find('b') != string::npos) ss << "Bottom";
949 if (opt.find('B') != string::npos) ss << "Baseline";
950 if (!ss.str().empty())
951 os << "\trotateOrigin " << ss.str() << '\n';
953 cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
955 if (opts.find("keepaspectratio") != opts.end())
956 os << "\tkeepAspectRatio\n";
957 if (opts.find("clip") != opts.end())
959 if (opts.find("draft") != opts.end())
961 if (opts.find("bb") != opts.end())
962 os << "\tBoundingBox "
963 << opts["bb"] << '\n';
964 int numberOfbbOptions = 0;
965 if (opts.find("bbllx") != opts.end())
967 if (opts.find("bblly") != opts.end())
969 if (opts.find("bburx") != opts.end())
971 if (opts.find("bbury") != opts.end())
973 if (numberOfbbOptions == 4)
974 os << "\tBoundingBox "
975 << opts["bbllx"] << opts["bblly"]
976 << opts["bburx"] << opts["bbury"] << '\n';
977 else if (numberOfbbOptions > 0)
978 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
979 numberOfbbOptions = 0;
980 if (opts.find("natwidth") != opts.end())
982 if (opts.find("natheight") != opts.end())
984 if (numberOfbbOptions == 2)
985 os << "\tBoundingBox 0bp 0bp "
986 << opts["natwidth"] << opts["natheight"] << '\n';
987 else if (numberOfbbOptions > 0)
988 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
989 ostringstream special;
990 if (opts.find("hiresbb") != opts.end())
991 special << "hiresbb,";
992 if (opts.find("trim") != opts.end())
994 if (opts.find("viewport") != opts.end())
995 special << "viewport=" << opts["viewport"] << ',';
996 if (opts.find("totalheight") != opts.end())
997 special << "totalheight=" << opts["totalheight"] << ',';
998 if (opts.find("type") != opts.end())
999 special << "type=" << opts["type"] << ',';
1000 if (opts.find("ext") != opts.end())
1001 special << "ext=" << opts["ext"] << ',';
1002 if (opts.find("read") != opts.end())
1003 special << "read=" << opts["read"] << ',';
1004 if (opts.find("command") != opts.end())
1005 special << "command=" << opts["command"] << ',';
1006 string s_special = special.str();
1007 if (!s_special.empty()) {
1008 // We had special arguments. Remove the trailing ','.
1009 os << "\tspecial " << s_special.substr(0, s_special.size() - 1) << '\n';
1011 // TODO: Handle the unknown settings better.
1012 // Warn about invalid options.
1013 // Check wether some option was given twice.
1017 else if (t.cs() == "footnote") {
1019 context.check_layout(os);
1020 begin_inset(os, "Foot\n");
1021 os << "status collapsed\n\n";
1022 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
1026 else if (t.cs() == "marginpar") {
1028 context.check_layout(os);
1029 begin_inset(os, "Marginal\n");
1030 os << "status collapsed\n\n";
1031 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
1035 else if (t.cs() == "ensuremath") {
1037 context.check_layout(os);
1038 Context newcontext(false, context.textclass);
1039 string s = parse_text(p, FLAG_ITEM, false, newcontext);
1040 if (s == "±" || s == "³" || s == "²" || s == "µ")
1043 handle_ert(os, "\\ensuremath{" + s + "}",
1047 else if (t.cs() == "hfill") {
1048 context.check_layout(os);
1049 os << "\n\\hfill\n";
1054 else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
1056 skip_braces(p); // swallow this
1059 else if (t.cs() == "tableofcontents") {
1061 context.check_layout(os);
1062 begin_inset(os, "LatexCommand \\tableofcontents\n");
1064 skip_braces(p); // swallow this
1067 else if (t.cs() == "listoffigures") {
1069 context.check_layout(os);
1070 begin_inset(os, "FloatList figure\n");
1072 skip_braces(p); // swallow this
1075 else if (t.cs() == "listoftables") {
1077 context.check_layout(os);
1078 begin_inset(os, "FloatList table\n");
1080 skip_braces(p); // swallow this
1083 else if (t.cs() == "listof") {
1084 p.skip_spaces(true);
1085 string const name = p.get_token().asString();
1086 if (context.textclass.floats().typeExist(name)) {
1087 context.check_layout(os);
1088 begin_inset(os, "FloatList ");
1091 p.get_token(); // swallow second arg
1093 handle_ert(os, "\\listof{" + name + "}", context);
1096 else if (t.cs() == "textrm") {
1097 context.check_layout(os);
1098 os << "\n\\family roman \n";
1099 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1100 os << "\n\\family default \n";
1103 else if (t.cs() == "textsf") {
1104 context.check_layout(os);
1105 os << "\n\\family sans \n";
1106 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1107 os << "\n\\family default \n";
1110 else if (t.cs() == "textsl") {
1111 context.check_layout(os);
1112 os << "\n\\shape slanted \n";
1113 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1114 os << "\n\\shape default \n";
1117 else if (t.cs() == "texttt") {
1118 context.check_layout(os);
1119 os << "\n\\family typewriter \n";
1120 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1121 os << "\n\\family default \n";
1124 else if (t.cs() == "textit") {
1125 context.check_layout(os);
1126 os << "\n\\shape italic \n";
1127 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1128 os << "\n\\shape default \n";
1131 else if (t.cs() == "textsc") {
1132 context.check_layout(os);
1133 os << "\n\\noun on \n";
1134 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1135 os << "\n\\noun default \n";
1138 else if (t.cs() == "textbf") {
1139 context.check_layout(os);
1140 os << "\n\\series bold \n";
1141 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1142 os << "\n\\series default \n";
1145 else if (t.cs() == "underbar") {
1146 context.check_layout(os);
1147 os << "\n\\bar under \n";
1148 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1149 os << "\n\\bar default \n";
1152 else if (t.cs() == "emph" || t.cs() == "noun") {
1153 context.check_layout(os);
1154 os << "\n\\" << t.cs() << " on \n";
1155 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1156 os << "\n\\" << t.cs() << " default \n";
1159 else if (is_known(t.cs(), known_latex_commands)) {
1160 context.check_layout(os);
1161 begin_inset(os, "LatexCommand ");
1162 os << '\\' << t.cs();
1165 os << '{' << p.verbatim_item() << "}\n";
1169 else if (is_known(t.cs(), known_quotes)) {
1170 char const ** where = is_known(t.cs(), known_quotes);
1171 context.check_layout(os);
1172 begin_inset(os, "Quotes ");
1173 os << known_coded_quotes[where - known_quotes];
1178 else if (is_known(t.cs(), known_sizes)) {
1179 char const ** where = is_known(t.cs(), known_sizes);
1180 context.check_layout(os);
1181 os << "\n\\size " << known_coded_sizes[where - known_sizes] << "\n";
1185 else if (t.cs() == "LyX" || t.cs() == "TeX"
1186 || t.cs() == "LaTeX") {
1187 context.check_layout(os);
1189 skip_braces(p); // eat {}
1192 else if (t.cs() == "LaTeXe") {
1193 context.check_layout(os);
1195 skip_braces(p); // eat {}
1198 else if (t.cs() == "ldots") {
1199 context.check_layout(os);
1201 os << "\\SpecialChar \\ldots{}\n";
1204 else if (t.cs() == "lyxarrow") {
1205 context.check_layout(os);
1206 os << "\\SpecialChar \\menuseparator\n";
1210 else if (t.cs() == "textcompwordmark") {
1211 context.check_layout(os);
1212 os << "\\SpecialChar \\textcompwordmark{}\n";
1216 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
1217 context.check_layout(os);
1218 os << "\\SpecialChar \\@.\n";
1222 else if (t.cs() == "-") {
1223 context.check_layout(os);
1224 os << "\\SpecialChar \\-\n";
1227 else if (t.cs() == "textasciitilde") {
1228 context.check_layout(os);
1233 else if (t.cs() == "textasciicircum") {
1234 context.check_layout(os);
1239 else if (t.cs() == "textbackslash") {
1240 context.check_layout(os);
1241 os << "\n\\backslash \n";
1245 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
1246 || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
1248 context.check_layout(os);
1252 else if (t.cs() == "char") {
1253 context.check_layout(os);
1254 if (p.next_token().character() == '`') {
1256 if (p.next_token().cs() == "\"") {
1261 handle_ert(os, "\\char`", context);
1264 handle_ert(os, "\\char", context);
1268 else if (t.cs() == "\"") {
1269 context.check_layout(os);
1270 string const name = p.verbatim_item();
1271 if (name == "a") os << 'ä';
1272 else if (name == "o") os << 'ö';
1273 else if (name == "u") os << 'ü';
1274 else if (name == "A") os << 'Ä';
1275 else if (name == "O") os << 'Ö';
1276 else if (name == "U") os << 'Ü';
1277 else handle_ert(os, "\"{" + name + "}", context);
1280 // Problem: \= creates a tabstop inside the tabbing environment
1281 // and else an accent. In the latter case we really would want
1282 // \={o} instead of \= o.
1283 else if (t.cs() == "=" && (flags & FLAG_TABBING))
1284 handle_ert(os, t.asInput(), context);
1286 else if (t.cs() == "H" || t.cs() == "c" || t.cs() == "^" || t.cs() == "'"
1287 || t.cs() == "~" || t.cs() == "." || t.cs() == "=") {
1288 // we need the trim as the LyX parser chokes on such spaces
1289 context.check_layout(os);
1290 os << "\n\\i \\" << t.cs() << "{"
1291 << trim(parse_text(p, FLAG_ITEM, outer, context), " ") << "}\n";
1294 else if (t.cs() == "ss") {
1295 context.check_layout(os);
1299 else if (t.cs() == "i" || t.cs() == "j") {
1300 context.check_layout(os);
1301 os << "\\" << t.cs() << ' ';
1304 else if (t.cs() == "\\") {
1305 context.check_layout(os);
1306 string const next = p.next_token().asInput();
1308 handle_ert(os, "\\\\" + p.getOpt(), context);
1309 else if (next == "*") {
1311 handle_ert(os, "\\\\*" + p.getOpt(), context);
1314 os << "\n\\newline \n";
1318 else if (t.cs() == "input" || t.cs() == "include"
1319 || t.cs() == "verbatiminput") {
1320 string name = '\\' + t.cs();
1321 if (t.cs() == "verbatiminput"
1322 && p.next_token().asInput() == "*")
1323 name += p.get_token().asInput();
1324 context.check_layout(os);
1325 begin_inset(os, "Include ");
1326 string filename(p.getArg('{', '}'));
1327 string lyxname(lyx::support::ChangeExtension(filename, ".lyx"));
1328 if (tex2lyx(filename, lyxname)) {
1329 os << name << '{' << lyxname << "}\n";
1331 os << name << '{' << filename << "}\n";
1333 os << "preview false\n";
1337 else if (t.cs() == "fancyhead") {
1338 context.check_layout(os);
1340 ss << "\\fancyhead";
1342 ss << '{' << p.verbatim_item() << "}\n";
1343 handle_ert(os, ss.str(), context);
1346 else if (t.cs() == "bibliographystyle") {
1347 // store new bibliographystyle
1348 bibliographystyle = p.verbatim_item();
1349 // output new bibliographystyle.
1350 // This is only necessary if used in some other macro than \bibliography.
1351 handle_ert(os, "\\bibliographystyle{" + bibliographystyle + "}", context);
1354 else if (t.cs() == "bibliography") {
1355 context.check_layout(os);
1356 begin_inset(os, "LatexCommand ");
1358 // Do we have a bibliographystyle set?
1359 if (!bibliographystyle.empty()) {
1360 os << '[' << bibliographystyle << ']';
1362 os << '{' << p.verbatim_item() << "}\n";
1366 else if (t.cs() == "parbox")
1367 parse_box(p, os, FLAG_ITEM, outer, context, true);
1369 else if (t.cs() == "smallskip" ||
1370 t.cs() == "medskip" ||
1371 t.cs() == "bigskip" ||
1372 t.cs() == "vfill") {
1373 context.check_layout(os);
1374 begin_inset(os, "VSpace ");
1379 else if (t.cs() == "vspace") {
1380 bool starred = false;
1381 if (p.next_token().asInput() == "*") {
1385 string const length = p.verbatim_item();
1388 bool valid = splitLatexLength(length, valstring, unit);
1389 bool known_vspace = false;
1390 bool known_unit = false;
1393 istringstream iss(valstring);
1396 if (unit == "\\smallskipamount") {
1398 known_vspace = true;
1399 } else if (unit == "\\medskipamount") {
1401 known_vspace = true;
1402 } else if (unit == "\\bigskipamount") {
1404 known_vspace = true;
1405 } else if (unit == "\\fill") {
1407 known_vspace = true;
1410 if (!known_vspace) {
1411 switch (unitFromString(unit)) {
1432 if (known_unit || known_vspace) {
1433 // Literal length or known variable
1434 context.check_layout(os);
1435 begin_inset(os, "VSpace ");
1443 // LyX can't handle other length variables in Inset VSpace
1444 string name = t.asInput();
1449 handle_ert(os, name + '{' + unit + '}', context);
1450 else if (value == -1.0)
1451 handle_ert(os, name + "{-" + unit + '}', context);
1453 handle_ert(os, name + '{' + valstring + unit + '}', context);
1455 handle_ert(os, name + '{' + length + '}', context);
1460 //cerr << "#: " << t << " mode: " << mode << endl;
1461 // heuristic: read up to next non-nested space
1463 string s = t.asInput();
1464 string z = p.verbatim_item();
1465 while (p.good() && z != " " && z.size()) {
1466 //cerr << "read: " << z << endl;
1468 z = p.verbatim_item();
1470 cerr << "found ERT: " << s << endl;
1471 handle_ert(os, s + ' ', context);
1473 string name = t.asInput();
1474 if (p.next_token().asInput() == "*") {
1475 // Starred commands like \vspace*{}
1476 p.get_token(); // Eat '*'
1479 if (! parse_command(name, p, os, outer, context))
1480 handle_ert(os, name, context);
1483 if (flags & FLAG_LEAVE) {
1484 flags &= ~FLAG_LEAVE;