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 "support/lstrings.h"
20 #include "support/tostr.h"
32 using std::ostringstream;
33 using std::istringstream;
37 using lyx::support::rtrim;
38 using lyx::support::suffixIs;
41 // thin wrapper around parse_text using a string
42 string parse_text(Parser & p, unsigned flags, const bool outer,
46 parse_text(p, os, flags, outer, context);
50 // parses a subdocument, usually useful in insets (whence the name)
51 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
54 Context newcontext(true, context.textclass);
55 parse_text(p, os, flags, outer, newcontext);
56 newcontext.check_end_layout(os);
60 // parses a paragraph snippet, useful for example for \emph{...}
61 void parse_text_snippet(Parser & p, ostream & os, unsigned flags, bool outer,
64 Context newcontext(false, context.textclass);
65 parse_text(p, os, flags, outer, newcontext);
66 // should not be needed
67 newcontext.check_end_layout(os);
73 char const * known_latex_commands[] = { "ref", "cite", "label", "index",
74 "printindex", "pageref", "url", "vref", "vpageref", "prettyref", "eqref", 0 };
76 // LaTeX names for quotes
77 char const * known_quotes[] = { "glqq", "grqq", "quotedblbase",
78 "textquotedblleft", "quotesinglbase", "guilsinglleft", "guilsinglright", 0};
80 // the same as known_quotes with .lyx names
81 char const * known_coded_quotes[] = { "gld", "grd", "gld",
82 "grd", "gls", "fls", "frd", 0};
84 char const * known_sizes[] = { "tiny", "scriptsize", "footnotesize",
85 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
87 char const * known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
88 "small", "normal", "large", "larger", "largest", "huge", "giant", 0};
90 // splits "x=z, y=b" into a map
91 map<string, string> split_map(string const & s)
93 map<string, string> res;
96 for (size_t i = 0; i < v.size(); ++i) {
97 size_t const pos = v[i].find('=');
98 string const index = v[i].substr(0, pos);
99 string const value = v[i].substr(pos + 1, string::npos);
100 res[trim(index)] = trim(value);
105 // A simple function to translate a latex length to something lyx can
106 // understand. Not perfect, but rather best-effort.
107 string translate_len(string const & len)
109 const string::size_type i = len.find_first_not_of(" -01234567890.");
111 if (i == string::npos || len[i] != '\\')
113 istringstream iss(string(len, 0, i));
119 string const valstring = oss.str();
120 const string::size_type i2 = len.find(" ", i);
121 string const unit = string(len, i, i2 - i);
122 string const endlen = (i2 == string::npos) ? string() : string(len, i2);
123 if (unit == "\\textwidth")
124 return valstring + "text%" + endlen;
125 else if (unit == "\\columnwidth")
126 return valstring + "col%" + endlen;
127 else if (unit == "\\paperwidth")
128 return valstring + "page%" + endlen;
129 else if (unit == "\\linewidth")
130 return valstring + "line%" + endlen;
131 else if (unit == "\\paperheight")
132 return valstring + "pheight%" + endlen;
133 else if (unit == "\\textheight")
134 return valstring + "theight%" + endlen;
140 void begin_inset(ostream & os, string const & name)
142 os << "\n\\begin_inset " << name;
146 void end_inset(ostream & os)
148 os << "\n\\end_inset \n\n";
152 void skip_braces(Parser & p)
154 if (p.next_token().cat() != catBegin)
157 if (p.next_token().cat() == catEnd) {
165 void handle_ert(ostream & os, string const & s, Context const & context)
167 Context newcontext(true, context.textclass);
168 begin_inset(os, "ERT");
169 os << "\nstatus Collapsed\n";
170 newcontext.check_layout(os);
171 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
173 os << "\n\\backslash \n";
177 newcontext.check_end_layout(os);
183 isLayout(string const name) : name_(name) {}
184 bool operator()(LyXLayout_ptr const & ptr) {
185 return ptr.get() && ptr->latexname() == name_;
192 LyXLayout_ptr findLayout(LyXTextClass const & textclass,
195 LyXTextClass::const_iterator it = textclass.begin();
196 LyXTextClass::const_iterator end = textclass.end();
197 it = std::find_if(it, end, isLayout(name));
198 return (it == end) ? LyXLayout_ptr() : *it;
202 void output_command_layout(ostream & os, Parser & p, bool outer,
203 Context & parent_context,
204 LyXLayout_ptr newlayout)
206 parent_context.check_end_layout(os);
207 Context context(true, parent_context.textclass, newlayout,
208 parent_context.layout);
209 context.check_deeper(os);
210 context.check_layout(os);
211 if (context.layout->optionalargs > 0) {
213 if (p.next_token().character() == '[') {
214 p.get_token(); // eat '['
215 begin_inset(os, "OptArg\n");
216 os << "collapsed true\n";
217 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
221 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
222 context.check_end_layout(os);
223 context.check_end_deeper(os);
227 void parse_environment(Parser & p, ostream & os, bool outer,
228 Context & parent_context)
230 LyXLayout_ptr newlayout;
231 string const name = p.getArg('{', '}');
232 const bool is_starred = suffixIs(name, '*');
233 string const unstarred_name = rtrim(name, "*");
234 active_environments.push_back(name);
235 if (is_math_env(name)) {
236 parent_context.check_layout(os);
237 begin_inset(os, "Formula ");
238 os << "\\begin{" << name << "}";
239 parse_math(p, os, FLAG_END, MATH_MODE);
240 os << "\\end{" << name << "}";
244 else if (name == "tabular") {
245 parent_context.check_layout(os);
246 begin_inset(os, "Tabular ");
247 handle_tabular(p, os, parent_context);
251 else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
252 parent_context.check_layout(os);
253 begin_inset(os, "Float " + unstarred_name + "\n");
254 if (p.next_token().asInput() == "[") {
255 os << "placement " << p.getArg('[', ']') << '\n';
257 os << "wide " << tostr(is_starred)
258 << "\ncollapsed false\n";
259 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
263 else if (name == "minipage") {
264 parent_context.check_layout(os);
265 begin_inset(os, "Minipage\n");
266 string position = "1";
267 string inner_pos = "0";
269 if (p.next_token().asInput() == "[") {
270 switch(p.getArg('[', ']')[0]) {
271 case 't': position = "0"; break;
272 case 'c': position = "1"; break;
273 case 'b': position = "2"; break;
275 cerr << "invalid position for minipage"
279 if (p.next_token().asInput() == "[") {
280 height = translate_len(p.getArg('[', ']'));
282 if (p.next_token().asInput() == "[") {
283 switch(p.getArg('[', ']')[0]) {
284 case 't': inner_pos = "0"; break;
285 case 'c': inner_pos = "1"; break;
286 case 'b': inner_pos = "2"; break;
287 case 's': inner_pos = "3"; break;
289 cerr << "invalid inner_pos for minipage"
296 os << "position " << position << '\n';
297 os << "inner_position " << inner_pos << '\n';
298 os << "height \"" << height << "\"\n";
299 os << "width \"" << translate_len(p.verbatim_item()) << "\"\n";
300 os << "collapsed false\n";
301 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
306 else if (name == "center") {
307 parse_text(p, os, FLAG_END, outer, parent_context);
308 // The single '=' is meant here.
311 else if ((newlayout = findLayout(parent_context.textclass, name)).get() &&
312 newlayout->isEnvironment()) {
313 Context context(true, parent_context.textclass, newlayout,
314 parent_context.layout);
315 parent_context.check_end_layout(os);
316 switch (context.layout->latextype) {
317 case LATEX_LIST_ENVIRONMENT:
318 context.extra_stuff = "\\labelwidthstring "
319 + p.verbatim_item() + '\n';
321 case LATEX_BIB_ENVIRONMENT:
322 p.verbatim_item(); // swallow next arg
327 context.check_deeper(os);
328 parse_text(p, os, FLAG_END, outer, context);
329 context.check_end_layout(os);
330 context.check_end_deeper(os);
334 parent_context.check_layout(os);
335 handle_ert(os, "\\begin{" + name + "}", parent_context);
336 parse_text_snippet(p, os, FLAG_END, outer, parent_context);
337 handle_ert(os, "\\end{" + name + "}", parent_context);
341 } // anonymous namespace
346 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
349 LyXLayout_ptr newlayout;
351 Token const & t = p.get_token();
354 cerr << "t: " << t << " flags: " << flags << "\n";
357 if (flags & FLAG_ITEM) {
358 if (t.cat() == catSpace)
362 if (t.cat() == catBegin) {
363 // skip the brace and collect everything to the next matching
365 flags |= FLAG_BRACE_LAST;
369 // handle only this single token, leave the loop if done
373 if (t.character() == ']' && (flags & FLAG_BRACK_LAST))
379 if (t.cat() == catMath) {
380 // we are inside some text mode thingy, so opening new math is allowed
381 context.check_layout(os);
382 begin_inset(os, "Formula ");
383 Token const & n = p.get_token();
384 if (n.cat() == catMath && outer) {
385 // TeX's $$...$$ syntax for displayed math
387 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
389 p.get_token(); // skip the second '$' token
391 // simple $...$ stuff
394 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
400 else if (t.cat() == catSuper || t.cat() == catSub)
401 cerr << "catcode " << t << " illegal in text mode\n";
403 // Basic support for english quotes. This should be
404 // extended to other quotes, but is not so easy (a
405 // left english quote is the same as a right german
407 else if (t.asInput() == "`"
408 && p.next_token().asInput() == "`") {
409 context.check_layout(os);
410 begin_inset(os, "Quotes ");
416 else if (t.asInput() == "'"
417 && p.next_token().asInput() == "'") {
418 context.check_layout(os);
419 begin_inset(os, "Quotes ");
427 else if (t.cat() == catLetter ||
428 t.cat() == catSpace ||
429 t.cat() == catOther ||
430 t.cat() == catAlign ||
431 t.cat() == catParameter) {
432 context.check_layout(os);
436 else if (t.cat() == catNewline) {
437 if (p.next_token().cat() == catNewline) {
438 // this should have been be done by
439 // the parser already
440 cerr << "what are we doing here?" << endl;
442 context.need_layout = true;
444 os << " "; // note the space
448 else if (t.cat() == catActive) {
449 context.check_layout(os);
450 if (t.character() == '~') {
451 if (context.layout->free_spacing)
454 os << "\\InsetSpace ~\n";
459 else if (t.cat() == catBegin) {
461 // special handling of size changes
462 context.check_layout(os);
463 bool const is_size = is_known(p.next_token().cs(), known_sizes);
464 Context newcontext(false, context.textclass);
465 // need_end_layout = false;
466 string const s = parse_text(p, FLAG_BRACE_LAST, outer, newcontext);
467 // need_end_layout = true;
468 if (s.empty() && p.next_token().character() == '`')
469 ; // ignore it in {}``
470 else if (is_size || s == "[" || s == "]" || s == "*")
473 handle_ert(os, "{", context);
475 handle_ert(os, "}", context);
479 else if (t.cat() == catEnd) {
480 if (flags & FLAG_BRACE_LAST) {
481 context.check_end_layout(os);
484 cerr << "stray '}' in text\n";
485 handle_ert(os, "}", context);
488 else if (t.cat() == catComment)
495 else if (t.cs() == "(") {
496 context.check_layout(os);
497 begin_inset(os, "Formula");
499 parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
504 else if (t.cs() == "[") {
505 context.check_layout(os);
506 begin_inset(os, "Formula");
508 parse_math(p, os, FLAG_EQUATION, MATH_MODE);
513 else if (t.cs() == "begin")
514 parse_environment(p, os, outer, context);
516 else if (t.cs() == "end") {
517 if (flags & FLAG_END) {
518 // eat environment name
519 string const name = p.getArg('{', '}');
520 if (name != active_environment())
521 cerr << "\\end{" + name + "} does not match \\begin{"
522 + active_environment() + "}\n";
523 active_environments.pop_back();
526 p.error("found 'end' unexpectedly");
529 else if (t.cs() == "item") {
530 // should be done automatically by Parser::tokenize
533 if (p.next_token().character() == '[') {
534 p.get_token(); // eat '['
535 Context newcontext(false, context.textclass);
536 s = parse_text(p, FLAG_BRACK_LAST, outer, newcontext);
538 context.need_layout = true;
539 context.has_item = true;
540 context.check_layout(os);
545 else if (t.cs() == "bibitem") {
546 context.need_layout = true;
547 context.has_item = true;
548 context.check_layout(os);
551 os << '{' << p.verbatim_item() << '}' << "\n";
554 else if (t.cs() == "def") {
555 string name = p.get_token().cs();
556 while (p.next_token().cat() != catBegin)
557 name += p.get_token().asString();
558 handle_ert(os, "\\def\\" + name + '{' + p.verbatim_item() + '}', context);
561 else if (t.cs() == "par") {
563 context.check_end_layout(os);
564 context.need_layout = true;
567 // Must attempt to parse "Section*" before "Section".
568 else if ((p.next_token().asInput() == "*") &&
569 // The single '=' is meant here.
570 (newlayout = findLayout(context.textclass,
571 t.cs() + '*')).get() &&
572 newlayout->isCommand()) {
574 output_command_layout(os, p, outer, context, newlayout);
577 // The single '=' is meant here.
578 else if ((newlayout = findLayout(context.textclass, t.cs())).get() &&
579 newlayout->isCommand()) {
580 output_command_layout(os, p, outer, context, newlayout);
583 else if (t.cs() == "includegraphics") {
584 map<string, string> opts = split_map(p.getArg('[', ']'));
585 string name = p.verbatim_item();
587 context.check_layout(os);
588 begin_inset(os, "Graphics ");
589 os << "\n\tfilename " << name << '\n';
590 if (opts.find("width") != opts.end())
592 << translate_len(opts["width"]) << '\n';
593 if (opts.find("height") != opts.end())
595 << translate_len(opts["height"]) << '\n';
599 else if (t.cs() == "footnote") {
600 context.check_layout(os);
601 begin_inset(os, "Foot\n");
602 os << "collapsed true\n";
603 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
607 else if (t.cs() == "marginpar") {
608 context.check_layout(os);
609 begin_inset(os, "Marginal\n");
610 os << "collapsed true\n";
611 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
615 else if (t.cs() == "ensuremath") {
616 context.check_layout(os);
617 Context newcontext(false, context.textclass);
618 string s = parse_text(p, FLAG_ITEM, false, newcontext);
619 if (s == "±" || s == "³" || s == "²" || s == "µ")
622 handle_ert(os, "\\ensuremath{" + s + "}",
626 else if (t.cs() == "hfill") {
627 context.check_layout(os);
632 else if (t.cs() == "makeindex" || t.cs() == "maketitle")
633 skip_braces(p); // swallow this
635 else if (t.cs() == "tableofcontents") {
636 context.check_layout(os);
637 begin_inset(os, "LatexCommand \\tableofcontents\n");
639 skip_braces(p); // swallow this
642 else if (t.cs() == "listoffigures") {
643 context.check_layout(os);
644 begin_inset(os, "FloatList figure\n");
646 skip_braces(p); // swallow this
649 else if (t.cs() == "listoftables") {
650 context.check_layout(os);
651 begin_inset(os, "FloatList table\n");
653 skip_braces(p); // swallow this
656 else if (t.cs() == "listof") {
657 string const name = p.get_token().asString();
658 if (context.textclass.floats().typeExist(name)) {
659 context.check_layout(os);
660 begin_inset(os, "FloatList ");
663 p.get_token(); // swallow second arg
665 handle_ert(os, "\\listof{" + name + "}", context);
668 else if (t.cs() == "textrm") {
669 context.check_layout(os);
670 os << "\n\\family roman \n";
671 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
672 os << "\n\\family default \n";
675 else if (t.cs() == "textsf") {
676 context.check_layout(os);
677 os << "\n\\family sans \n";
678 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
679 os << "\n\\family default \n";
682 else if (t.cs() == "texttt") {
683 context.check_layout(os);
684 os << "\n\\family typewriter \n";
685 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
686 os << "\n\\family default \n";
689 else if (t.cs() == "textit") {
690 context.check_layout(os);
691 os << "\n\\shape italic \n";
692 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
693 os << "\n\\shape default \n";
696 else if (t.cs() == "textsc") {
697 context.check_layout(os);
698 os << "\n\\noun on \n";
699 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
700 os << "\n\\noun default \n";
703 else if (t.cs() == "textbf") {
704 context.check_layout(os);
705 os << "\n\\series bold \n";
706 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
707 os << "\n\\series default \n";
710 else if (t.cs() == "underbar") {
711 context.check_layout(os);
712 os << "\n\\bar under \n";
713 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
714 os << "\n\\bar default \n";
717 else if (t.cs() == "emph" || t.cs() == "noun") {
718 context.check_layout(os);
719 os << "\n\\" << t.cs() << " on \n";
720 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
721 os << "\n\\" << t.cs() << " default \n";
724 else if (is_known(t.cs(), known_latex_commands)) {
725 context.check_layout(os);
726 begin_inset(os, "LatexCommand ");
727 os << '\\' << t.cs();
730 os << '{' << p.verbatim_item() << "}\n";
734 else if (is_known(t.cs(), known_quotes)) {
735 char const ** where = is_known(t.cs(), known_quotes);
736 begin_inset(os, "Quotes ");
737 os << known_coded_quotes[where - known_quotes];
742 else if (is_known(t.cs(), known_sizes)) {
743 char const ** where = is_known(t.cs(), known_sizes);
744 context.check_layout(os);
745 os << "\n\\size " << known_coded_sizes[where - known_sizes] << "\n";
748 else if (t.cs() == "LyX" || t.cs() == "TeX"
749 || t.cs() == "LaTeX") {
750 context.check_layout(os);
752 skip_braces(p); // eat {}
755 else if (t.cs() == "LaTeXe") {
756 context.check_layout(os);
758 skip_braces(p); // eat {}
761 else if (t.cs() == "ldots") {
762 context.check_layout(os);
764 os << "\\SpecialChar \\ldots{}\n";
767 else if (t.cs() == "lyxarrow") {
768 context.check_layout(os);
769 os << "\\SpecialChar \\menuseparator\n";
773 else if (t.cs() == "ldots") {
774 context.check_layout(os);
775 os << "\\SpecialChar \\ldots{}\n";
779 else if (t.cs() == "textcompwordmark") {
780 context.check_layout(os);
781 os << "\\SpecialChar \\textcompwordmark{}\n";
785 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
786 context.check_layout(os);
787 os << "\\SpecialChar \\@.\n";
791 else if (t.cs() == "-") {
792 context.check_layout(os);
793 os << "\\SpecialChar \\-\n";
796 else if (t.cs() == "textasciitilde") {
797 context.check_layout(os);
802 else if (t.cs() == "textasciicircum") {
803 context.check_layout(os);
808 else if (t.cs() == "textbackslash") {
809 context.check_layout(os);
810 os << "\n\\backslash \n";
814 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
815 || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
817 context.check_layout(os);
821 else if (t.cs() == "char") {
822 context.check_layout(os);
823 if (p.next_token().character() == '`') {
825 if (p.next_token().cs() == "\"") {
830 handle_ert(os, "\\char`", context);
833 handle_ert(os, "\\char", context);
837 else if (t.cs() == "\"") {
838 context.check_layout(os);
839 string const name = p.verbatim_item();
840 if (name == "a") os << 'ä';
841 else if (name == "o") os << 'ö';
842 else if (name == "u") os << 'ü';
843 else if (name == "A") os << 'Ä';
844 else if (name == "O") os << 'Ö';
845 else if (name == "U") os << 'Ü';
846 else handle_ert(os, "\"{" + name + "}", context);
849 else if (t.cs() == "=" || t.cs() == "H" || t.cs() == "c"
850 || t.cs() == "^" || t.cs() == "'" || t.cs() == "~") {
851 // we need the trim as the LyX parser chokes on such spaces
852 context.check_layout(os);
853 os << "\n\\i \\" << t.cs() << "{"
854 << trim(parse_text(p, FLAG_ITEM, outer, context), " ") << "}\n";
857 else if (t.cs() == "ss") {
858 context.check_layout(os);
862 else if (t.cs() == "i" || t.cs() == "j") {
863 context.check_layout(os);
864 os << "\\" << t.cs() << ' ';
867 else if (t.cs() == "\\") {
868 string const next = p.next_token().asInput();
870 handle_ert(os, "\\\\" + p.getOpt(), context);
871 else if (next == "*") {
873 handle_ert(os, "\\\\*" + p.getOpt(), context);
876 context.check_layout(os);
877 os << "\n\\newline \n";
881 else if (t.cs() == "input" || t.cs() == "include"
882 || t.cs() == "verbatiminput") {
883 string name = '\\' + t.cs();
884 if (t.cs() == "verbatiminput"
885 && p.next_token().asInput() == "*")
886 name += p.get_token().asInput();
887 context.check_layout(os);
888 begin_inset(os, "Include ");
889 os << name << '{' << p.getArg('{', '}') << "}\n";
890 os << "preview false\n";
893 else if (t.cs() == "fancyhead") {
894 context.check_layout(os);
898 ss << '{' << p.verbatim_item() << "}\n";
899 handle_ert(os, ss.str(), context);
903 //cerr << "#: " << t << " mode: " << mode << endl;
904 // heuristic: read up to next non-nested space
906 string s = t.asInput();
907 string z = p.verbatim_item();
908 while (p.good() && z != " " && z.size()) {
909 //cerr << "read: " << z << endl;
911 z = p.verbatim_item();
913 cerr << "found ERT: " << s << endl;
914 handle_ert(os, s + ' ', context);
916 context.check_layout(os);
917 handle_ert(os, t.asInput() + ' ', context);
920 if (flags & FLAG_LEAVE) {
921 flags &= ~FLAG_LEAVE;