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"
31 using std::ostringstream;
32 using std::istringstream;
36 using lyx::support::rtrim;
37 using lyx::support::suffixIs;
40 // thin wrapper around parse_text using a string
41 string parse_text(Parser & p, unsigned flags, const bool outer,
45 parse_text(p, os, flags, outer, context);
49 // parses a subdocument, usually useful in insets (whence the name)
50 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
53 Context newcontext(true, context.textclass);
54 parse_text(p, os, flags, outer, newcontext);
55 newcontext.check_end_layout(os);
59 // parses a paragraph snippet, useful for example for \emph{...}
60 void parse_text_snippet(Parser & p, ostream & os, unsigned flags, bool outer,
63 Context newcontext(false, context.textclass);
64 parse_text(p, os, flags, outer, newcontext);
65 // should not be needed
66 newcontext.check_end_layout(os);
72 char const * known_latex_commands[] = { "ref", "cite", "label", "index",
73 "printindex", "pageref", "url", "vref", "vpageref", "prettyref", "eqref", 0 };
75 // LaTeX names for quotes
76 char const * known_quotes[] = { "glqq", "grqq", "quotedblbase",
77 "textquotedblleft", "quotesinglbase", "guilsinglleft", "guilsinglright", 0};
79 // the same as known_quotes with .lyx names
80 char const * known_coded_quotes[] = { "gld", "grd", "gld",
81 "grd", "gls", "fls", "frd", 0};
83 char const * known_sizes[] = { "tiny", "scriptsize", "footnotesize",
84 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
86 char const * known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
87 "small", "normal", "large", "larger", "largest", "huge", "giant", 0};
89 // splits "x=z, y=b" into a map
90 map<string, string> split_map(string const & s)
92 map<string, string> res;
95 for (size_t i = 0; i < v.size(); ++i) {
96 size_t const pos = v[i].find('=');
97 string const index = v[i].substr(0, pos);
98 string const value = v[i].substr(pos + 1, string::npos);
99 res[trim(index)] = trim(value);
104 // A simple function to translate a latex length to something lyx can
105 // understand. Not perfect, but rather best-effort.
106 string translate_len(string const & len)
108 const string::size_type i = len.find_first_not_of(" -01234567890.");
110 if (i == string::npos || len[i] != '\\')
112 istringstream iss(string(len, 0, i));
118 string const valstring = oss.str();
119 const string::size_type i2 = len.find(" ", i);
120 string const unit = string(len, i, i2 - i);
121 string const endlen = (i2 == string::npos) ? string() : string(len, i2);
122 if (unit == "\\textwidth")
123 return valstring + "text%" + endlen;
124 else if (unit == "\\columnwidth")
125 return valstring + "col%" + endlen;
126 else if (unit == "\\paperwidth")
127 return valstring + "page%" + endlen;
128 else if (unit == "\\linewidth")
129 return valstring + "line%" + endlen;
130 else if (unit == "\\paperheight")
131 return valstring + "pheight%" + endlen;
132 else if (unit == "\\textheight")
133 return valstring + "theight%" + endlen;
139 void begin_inset(ostream & os, string const & name)
141 os << "\n\\begin_inset " << name;
145 void end_inset(ostream & os)
147 os << "\n\\end_inset \n\n";
151 void skip_braces(Parser & p)
153 if (p.next_token().cat() != catBegin)
156 if (p.next_token().cat() == catEnd) {
164 void handle_ert(ostream & os, string const & s, Context const & context)
166 Context newcontext(true, context.textclass);
167 begin_inset(os, "ERT");
168 os << "\nstatus Collapsed\n";
169 newcontext.check_layout(os);
170 for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
172 os << "\n\\backslash \n";
176 newcontext.check_end_layout(os);
182 isLayout(string const name) : name_(name) {}
183 bool operator()(LyXLayout_ptr const & ptr) {
184 return ptr.get() && ptr->latexname() == name_;
191 LyXLayout_ptr findLayout(LyXTextClass const & textclass,
194 LyXTextClass::const_iterator it = textclass.begin();
195 LyXTextClass::const_iterator end = textclass.end();
196 it = std::find_if(it, end, isLayout(name));
197 return (it == end) ? LyXLayout_ptr() : *it;
201 void output_command_layout(ostream & os, Parser & p, bool outer,
202 Context & parent_context,
203 LyXLayout_ptr newlayout)
205 parent_context.check_end_layout(os);
206 Context context(true, parent_context.textclass, newlayout,
207 parent_context.layout);
208 context.check_deeper(os);
209 context.check_layout(os);
210 if (context.layout->optionalargs > 0) {
212 if (p.next_token().character() == '[') {
213 p.get_token(); // eat '['
214 begin_inset(os, "OptArg\n");
215 os << "collapsed true\n";
216 parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
220 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
221 context.check_end_layout(os);
222 context.check_end_deeper(os);
226 void parse_environment(Parser & p, ostream & os, bool outer,
227 Context & parent_context)
229 LyXLayout_ptr newlayout;
230 string const name = p.getArg('{', '}');
231 const bool is_starred = suffixIs(name, '*');
232 string const unstarred_name = rtrim(name, "*");
233 active_environments.push_back(name);
234 if (is_math_env(name)) {
235 parent_context.check_layout(os);
236 begin_inset(os, "Formula ");
237 os << "\\begin{" << name << "}";
238 parse_math(p, os, FLAG_END, MATH_MODE);
239 os << "\\end{" << name << "}";
243 else if (name == "tabular") {
244 parent_context.check_layout(os);
245 begin_inset(os, "Tabular ");
246 handle_tabular(p, os, parent_context);
250 else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
251 parent_context.check_layout(os);
252 begin_inset(os, "Float " + unstarred_name + "\n");
253 if (p.next_token().asInput() == "[") {
254 os << "placement " << p.getArg('[', ']') << '\n';
256 os << "wide " << tostr(is_starred)
257 << "\ncollapsed false\n";
258 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
262 else if (name == "minipage") {
263 parent_context.check_layout(os);
264 begin_inset(os, "Minipage\n");
265 string position = "1";
266 string inner_pos = "0";
268 if (p.next_token().asInput() == "[") {
269 switch(p.getArg('[', ']')[0]) {
270 case 't': position = "0"; break;
271 case 'c': position = "1"; break;
272 case 'b': position = "2"; break;
274 cerr << "invalid position for minipage"
278 if (p.next_token().asInput() == "[") {
279 height = translate_len(p.getArg('[', ']'));
281 if (p.next_token().asInput() == "[") {
282 switch(p.getArg('[', ']')[0]) {
283 case 't': inner_pos = "0"; break;
284 case 'c': inner_pos = "1"; break;
285 case 'b': inner_pos = "2"; break;
286 case 's': inner_pos = "3"; break;
288 cerr << "invalid inner_pos for minipage"
295 os << "position " << position << '\n';
296 os << "inner_position " << inner_pos << '\n';
297 os << "height \"" << height << "\"\n";
298 os << "width \"" << translate_len(p.verbatim_item()) << "\"\n";
299 os << "collapsed false\n";
300 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
305 else if (name == "center") {
306 parse_text(p, os, FLAG_END, outer, parent_context);
307 // The single '=' is meant here.
310 else if ((newlayout = findLayout(parent_context.textclass, name)).get() &&
311 newlayout->isEnvironment()) {
312 Context context(true, parent_context.textclass, newlayout,
313 parent_context.layout);
314 parent_context.check_end_layout(os);
315 switch (context.layout->latextype) {
316 case LATEX_LIST_ENVIRONMENT:
317 context.extra_stuff = "\\labelwidthstring "
318 + p.verbatim_item() + '\n';
320 case LATEX_BIB_ENVIRONMENT:
321 p.verbatim_item(); // swallow next arg
326 context.check_deeper(os);
327 parse_text(p, os, FLAG_END, outer, context);
328 context.check_end_layout(os);
329 context.check_end_deeper(os);
333 parent_context.check_layout(os);
334 handle_ert(os, "\\begin{" + name + "}", parent_context);
335 parse_text_snippet(p, os, FLAG_END, outer, parent_context);
336 handle_ert(os, "\\end{" + name + "}", parent_context);
340 } // anonymous namespace
345 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
348 LyXLayout_ptr newlayout;
350 Token const & t = p.get_token();
353 cerr << "t: " << t << " flags: " << flags << "\n";
356 if (flags & FLAG_ITEM) {
357 if (t.cat() == catSpace)
361 if (t.cat() == catBegin) {
362 // skip the brace and collect everything to the next matching
364 flags |= FLAG_BRACE_LAST;
368 // handle only this single token, leave the loop if done
372 if (t.character() == ']' && (flags & FLAG_BRACK_LAST))
378 if (t.cat() == catMath) {
379 // we are inside some text mode thingy, so opening new math is allowed
380 context.check_layout(os);
381 begin_inset(os, "Formula ");
382 Token const & n = p.get_token();
383 if (n.cat() == catMath && outer) {
384 // TeX's $$...$$ syntax for displayed math
386 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
388 p.get_token(); // skip the second '$' token
390 // simple $...$ stuff
393 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
399 else if (t.cat() == catSuper || t.cat() == catSub)
400 cerr << "catcode " << t << " illegal in text mode\n";
402 // Basic support for english quotes. This should be
403 // extended to other quotes, but is not so easy (a
404 // left english quote is the same as a right german
406 else if (t.asInput() == "`"
407 && p.next_token().asInput() == "`") {
408 context.check_layout(os);
409 begin_inset(os, "Quotes ");
415 else if (t.asInput() == "'"
416 && p.next_token().asInput() == "'") {
417 context.check_layout(os);
418 begin_inset(os, "Quotes ");
426 else if (t.cat() == catLetter ||
427 t.cat() == catSpace ||
428 t.cat() == catOther ||
429 t.cat() == catAlign ||
430 t.cat() == catParameter) {
431 context.check_layout(os);
435 else if (t.cat() == catNewline) {
436 if (p.next_token().cat() == catNewline) {
437 // this should have been be done by
438 // the parser already
439 cerr << "what are we doing here?" << endl;
441 context.need_layout = true;
443 os << " "; // note the space
447 else if (t.cat() == catActive) {
448 context.check_layout(os);
449 if (t.character() == '~') {
450 if (context.layout->free_spacing)
453 os << "\\InsetSpace ~\n";
458 else if (t.cat() == catBegin) {
460 // special handling of size changes
461 context.check_layout(os);
462 bool const is_size = is_known(p.next_token().cs(), known_sizes);
463 Context newcontext(false, context.textclass);
464 // need_end_layout = false;
465 string const s = parse_text(p, FLAG_BRACE_LAST, outer, newcontext);
466 // need_end_layout = true;
467 if (s.empty() && p.next_token().character() == '`')
468 ; // ignore it in {}``
469 else if (is_size || s == "[" || s == "]" || s == "*")
472 handle_ert(os, "{", context);
474 handle_ert(os, "}", context);
478 else if (t.cat() == catEnd) {
479 if (flags & FLAG_BRACE_LAST) {
480 context.check_end_layout(os);
483 cerr << "stray '}' in text\n";
484 handle_ert(os, "}", context);
487 else if (t.cat() == catComment)
494 else if (t.cs() == "(") {
495 context.check_layout(os);
496 begin_inset(os, "Formula");
498 parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
503 else if (t.cs() == "[") {
504 context.check_layout(os);
505 begin_inset(os, "Formula");
507 parse_math(p, os, FLAG_EQUATION, MATH_MODE);
512 else if (t.cs() == "begin")
513 parse_environment(p, os, outer, context);
515 else if (t.cs() == "end") {
516 if (flags & FLAG_END) {
517 // eat environment name
518 string const name = p.getArg('{', '}');
519 if (name != active_environment())
520 cerr << "\\end{" + name + "} does not match \\begin{"
521 + active_environment() + "}\n";
522 active_environments.pop_back();
525 p.error("found 'end' unexpectedly");
528 else if (t.cs() == "item") {
529 // should be done automatically by Parser::tokenize
532 if (p.next_token().character() == '[') {
533 p.get_token(); // eat '['
534 Context newcontext(false, context.textclass);
535 s = parse_text(p, FLAG_BRACK_LAST, outer, newcontext);
537 context.need_layout = true;
538 context.has_item = true;
539 context.check_layout(os);
544 else if (t.cs() == "bibitem") {
545 context.need_layout = true;
546 context.has_item = true;
547 context.check_layout(os);
550 os << '{' << p.verbatim_item() << '}' << "\n";
553 else if (t.cs() == "def") {
554 string name = p.get_token().cs();
555 while (p.next_token().cat() != catBegin)
556 name += p.get_token().asString();
557 handle_ert(os, "\\def\\" + name + '{' + p.verbatim_item() + '}', context);
560 else if (t.cs() == "par") {
562 context.check_end_layout(os);
563 context.need_layout = true;
566 // Must attempt to parse "Section*" before "Section".
567 else if ((p.next_token().asInput() == "*") &&
568 // The single '=' is meant here.
569 (newlayout = findLayout(context.textclass,
570 t.cs() + '*')).get() &&
571 newlayout->isCommand()) {
573 output_command_layout(os, p, outer, context, newlayout);
576 // The single '=' is meant here.
577 else if ((newlayout = findLayout(context.textclass, t.cs())).get() &&
578 newlayout->isCommand()) {
579 output_command_layout(os, p, outer, context, newlayout);
582 else if (t.cs() == "includegraphics") {
583 map<string, string> opts = split_map(p.getArg('[', ']'));
584 string name = p.verbatim_item();
586 context.check_layout(os);
587 begin_inset(os, "Graphics ");
588 os << "\n\tfilename " << name << '\n';
589 if (opts.find("width") != opts.end())
591 << translate_len(opts["width"]) << '\n';
592 if (opts.find("height") != opts.end())
594 << translate_len(opts["height"]) << '\n';
598 else if (t.cs() == "footnote") {
599 context.check_layout(os);
600 begin_inset(os, "Foot\n");
601 os << "collapsed true\n";
602 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
606 else if (t.cs() == "marginpar") {
607 context.check_layout(os);
608 begin_inset(os, "Marginal\n");
609 os << "collapsed true\n";
610 parse_text_in_inset(p, os, FLAG_ITEM, false, context);
614 else if (t.cs() == "ensuremath") {
615 context.check_layout(os);
616 Context newcontext(false, context.textclass);
617 string s = parse_text(p, FLAG_ITEM, false, newcontext);
618 if (s == "±" || s == "³" || s == "²" || s == "µ")
621 handle_ert(os, "\\ensuremath{" + s + "}",
625 else if (t.cs() == "hfill") {
626 context.check_layout(os);
631 else if (t.cs() == "makeindex" || t.cs() == "maketitle")
632 skip_braces(p); // swallow this
634 else if (t.cs() == "tableofcontents") {
635 context.check_layout(os);
636 begin_inset(os, "LatexCommand \\tableofcontents\n");
638 skip_braces(p); // swallow this
641 else if (t.cs() == "listoffigures") {
642 context.check_layout(os);
643 begin_inset(os, "FloatList figure\n");
645 skip_braces(p); // swallow this
648 else if (t.cs() == "listoftables") {
649 context.check_layout(os);
650 begin_inset(os, "FloatList table\n");
652 skip_braces(p); // swallow this
655 else if (t.cs() == "listof") {
656 string const name = p.get_token().asString();
657 if (context.textclass.floats().typeExist(name)) {
658 context.check_layout(os);
659 begin_inset(os, "FloatList ");
662 p.get_token(); // swallow second arg
664 handle_ert(os, "\\listof{" + name + "}", context);
667 else if (t.cs() == "textrm") {
668 context.check_layout(os);
669 os << "\n\\family roman \n";
670 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
671 os << "\n\\family default \n";
674 else if (t.cs() == "textsf") {
675 context.check_layout(os);
676 os << "\n\\family sans \n";
677 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
678 os << "\n\\family default \n";
681 else if (t.cs() == "texttt") {
682 context.check_layout(os);
683 os << "\n\\family typewriter \n";
684 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
685 os << "\n\\family default \n";
688 else if (t.cs() == "textit") {
689 context.check_layout(os);
690 os << "\n\\shape italic \n";
691 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
692 os << "\n\\shape default \n";
695 else if (t.cs() == "textsc") {
696 context.check_layout(os);
697 os << "\n\\noun on \n";
698 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
699 os << "\n\\noun default \n";
702 else if (t.cs() == "textbf") {
703 context.check_layout(os);
704 os << "\n\\series bold \n";
705 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
706 os << "\n\\series default \n";
709 else if (t.cs() == "underbar") {
710 context.check_layout(os);
711 os << "\n\\bar under \n";
712 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
713 os << "\n\\bar default \n";
716 else if (t.cs() == "emph" || t.cs() == "noun") {
717 context.check_layout(os);
718 os << "\n\\" << t.cs() << " on \n";
719 parse_text_snippet(p, os, FLAG_ITEM, outer, context);
720 os << "\n\\" << t.cs() << " default \n";
723 else if (is_known(t.cs(), known_latex_commands)) {
724 context.check_layout(os);
725 begin_inset(os, "LatexCommand ");
726 os << '\\' << t.cs();
729 os << '{' << p.verbatim_item() << "}\n";
733 else if (is_known(t.cs(), known_quotes)) {
734 char const ** where = is_known(t.cs(), known_quotes);
735 begin_inset(os, "Quotes ");
736 os << known_coded_quotes[where - known_quotes];
741 else if (is_known(t.cs(), known_sizes)) {
742 char const ** where = is_known(t.cs(), known_sizes);
743 context.check_layout(os);
744 os << "\n\\size " << known_coded_sizes[where - known_sizes] << "\n";
747 else if (t.cs() == "LyX" || t.cs() == "TeX"
748 || t.cs() == "LaTeX") {
749 context.check_layout(os);
751 skip_braces(p); // eat {}
754 else if (t.cs() == "LaTeXe") {
755 context.check_layout(os);
757 skip_braces(p); // eat {}
760 else if (t.cs() == "ldots") {
761 context.check_layout(os);
763 os << "\\SpecialChar \\ldots{}\n";
766 else if (t.cs() == "lyxarrow") {
767 context.check_layout(os);
768 os << "\\SpecialChar \\menuseparator\n";
772 else if (t.cs() == "ldots") {
773 context.check_layout(os);
774 os << "\\SpecialChar \\ldots{}\n";
778 else if (t.cs() == "textcompwordmark") {
779 context.check_layout(os);
780 os << "\\SpecialChar \\textcompwordmark{}\n";
784 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
785 context.check_layout(os);
786 os << "\\SpecialChar \\@.\n";
790 else if (t.cs() == "-") {
791 context.check_layout(os);
792 os << "\\SpecialChar \\-\n";
795 else if (t.cs() == "textasciitilde") {
796 context.check_layout(os);
801 else if (t.cs() == "textasciicircum") {
802 context.check_layout(os);
807 else if (t.cs() == "textbackslash") {
808 context.check_layout(os);
809 os << "\n\\backslash \n";
813 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
814 || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
816 context.check_layout(os);
820 else if (t.cs() == "char") {
821 context.check_layout(os);
822 if (p.next_token().character() == '`') {
824 if (p.next_token().cs() == "\"") {
829 handle_ert(os, "\\char`", context);
832 handle_ert(os, "\\char", context);
836 else if (t.cs() == "\"") {
837 context.check_layout(os);
838 string const name = p.verbatim_item();
839 if (name == "a") os << 'ä';
840 else if (name == "o") os << 'ö';
841 else if (name == "u") os << 'ü';
842 else if (name == "A") os << 'Ä';
843 else if (name == "O") os << 'Ö';
844 else if (name == "U") os << 'Ü';
845 else handle_ert(os, "\"{" + name + "}", context);
848 else if (t.cs() == "=" || t.cs() == "H" || t.cs() == "c"
849 || t.cs() == "^" || t.cs() == "'" || t.cs() == "~") {
850 // we need the trim as the LyX parser chokes on such spaces
851 context.check_layout(os);
852 os << "\n\\i \\" << t.cs() << "{"
853 << trim(parse_text(p, FLAG_ITEM, outer, context), " ") << "}\n";
856 else if (t.cs() == "ss") {
857 context.check_layout(os);
861 else if (t.cs() == "i" || t.cs() == "j") {
862 context.check_layout(os);
863 os << "\\" << t.cs() << ' ';
866 else if (t.cs() == "\\") {
867 string const next = p.next_token().asInput();
869 handle_ert(os, "\\\\" + p.getOpt(), context);
870 else if (next == "*") {
872 handle_ert(os, "\\\\*" + p.getOpt(), context);
875 context.check_layout(os);
876 os << "\n\\newline \n";
880 else if (t.cs() == "input" || t.cs() == "include"
881 || t.cs() == "verbatiminput") {
882 string name = '\\' + t.cs();
883 if (t.cs() == "verbatiminput"
884 && p.next_token().asInput() == "*")
885 name += p.get_token().asInput();
886 context.check_layout(os);
887 begin_inset(os, "Include ");
888 os << name << '{' << p.getArg('{', '}') << "}\n";
889 os << "preview false\n";
892 else if (t.cs() == "fancyhead") {
893 context.check_layout(os);
897 ss << '{' << p.verbatim_item() << "}\n";
898 handle_ert(os, ss.str(), context);
902 //cerr << "#: " << t << " mode: " << mode << endl;
903 // heuristic: read up to next non-nested space
905 string s = t.asInput();
906 string z = p.verbatim_item();
907 while (p.good() && z != " " && z.size()) {
908 //cerr << "read: " << z << endl;
910 z = p.verbatim_item();
912 cerr << "found ERT: " << s << endl;
913 handle_ert(os, s + ' ', context);
915 context.check_layout(os);
916 handle_ert(os, t.asInput() + ' ', context);
919 if (flags & FLAG_LEAVE) {
920 flags &= ~FLAG_LEAVE;