]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/text.C
Georg Baum's vspace change
[lyx.git] / src / tex2lyx / text.C
1 /**
2  * \file tex2lyx/text.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  * \author Jean-Marc Lasgouttes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 // {[(
13
14 #include <config.h>
15
16 #include "tex2lyx.h"
17 #include "context.h"
18 #include "FloatList.h"
19 #include "lengthcommon.h"
20 #include "support/lstrings.h"
21 #include "support/tostr.h"
22 #include "support/filetools.h"
23
24 #include <iostream>
25 #include <map>
26 #include <sstream>
27 #include <vector>
28
29 using std::cerr;
30 using std::endl;
31
32 using std::map;
33 using std::ostream;
34 using std::ostringstream;
35 using std::istringstream;
36 using std::string;
37 using std::vector;
38
39 using lyx::support::rtrim;
40 using lyx::support::suffixIs;
41
42
43 // thin wrapper around parse_text using a string
44 string parse_text(Parser & p, unsigned flags, const bool outer,
45                   Context & context)
46 {
47         ostringstream os;
48         parse_text(p, os, flags, outer, context);
49         return os.str();
50 }
51
52 // parses a subdocument, usually useful in insets (whence the name)
53 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
54                 Context & context)
55 {
56         Context newcontext(true, context.textclass);
57         parse_text(p, os, flags, outer, newcontext);
58         newcontext.check_end_layout(os);
59 }
60
61
62 // parses a paragraph snippet, useful for example for \emph{...}
63 void parse_text_snippet(Parser & p, ostream & os, unsigned flags, bool outer,
64                 Context & context)
65 {
66         Context newcontext(false, context.textclass);
67         parse_text(p, os, flags, outer, newcontext);
68         // should not be needed
69         newcontext.check_end_layout(os);
70 }
71
72
73 namespace {
74
75 char const * known_latex_commands[] = { "ref", "cite", "label", "index",
76 "printindex", "pageref", "url", "vref", "vpageref", "prettyref", "eqref", 0 };
77
78 // LaTeX names for quotes
79 char const * known_quotes[] = { "glqq", "grqq", "quotedblbase",
80 "textquotedblleft", "quotesinglbase", "guilsinglleft", "guilsinglright", 0};
81
82 // the same as known_quotes with .lyx names
83 char const * known_coded_quotes[] = { "gld", "grd", "gld",
84 "grd", "gls", "fls", "frd", 0};
85
86 char const * known_sizes[] = { "tiny", "scriptsize", "footnotesize",
87 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
88
89 char const * known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
90 "small", "normal", "large", "larger", "largest",  "huge", "giant", 0};
91
92 // splits "x=z, y=b" into a map
93 map<string, string> split_map(string const & s)
94 {
95         map<string, string> res;
96         vector<string> v;
97         split(s, v);
98         for (size_t i = 0; i < v.size(); ++i) {
99                 size_t const pos   = v[i].find('=');
100                 string const index = v[i].substr(0, pos);
101                 string const value = v[i].substr(pos + 1, string::npos);
102                 res[trim(index)] = trim(value);
103         }
104         return res;
105 }
106
107
108 /*!
109  * Split a LaTeX length into value and unit.
110  * The latter can be a real unit like "pt", or a latex length variable
111  * like "\textwidth". The unit may contain additional stuff like glue
112  * lengths, but we don't care, because such lengths are ERT anyway.
113  * \return true if \param value and \param unit are valid.
114  */
115 bool splitLatexLength(string const & len, string & value, string & unit)
116 {
117         if (len.empty())
118                 return false;
119         const string::size_type i = len.find_first_not_of(" -+0123456789.,");
120         //'4,5' is a valid LaTeX length number. Change it to '4.5'
121         string const length = lyx::support::subst(len, ',', '.');
122         if (i == string::npos)
123                 return false;
124         if (i == 0) {
125                 if (len[0] == '\\') {
126                         // We had something like \textwidth without a factor
127                         value = "1.0";
128                 } else {
129                         return false;
130                 }
131         } else {
132                 value = trim(string(length, 0, i));
133         }
134         if (value == "-")
135                 value = "-1.0";
136         // 'cM' is a valid LaTeX length unit. Change it to 'cm'
137         if (lyx::support::contains(len, '\\'))
138                 unit = trim(string(len, i));
139         else
140                 unit = lyx::support::lowercase(trim(string(len, i)));
141         return true;
142 }
143
144
145 // A simple function to translate a latex length to something lyx can
146 // understand. Not perfect, but rather best-effort.
147 string translate_len(string const & length)
148 {
149         string unit;
150         string valstring;
151         // If the input is invalid, return what we have.
152         if (!splitLatexLength(length, valstring, unit))
153                 return length;
154         // LyX uses percent values
155         double value;
156         istringstream iss(valstring);
157         iss >> value;
158         value *= 100;
159         ostringstream oss;
160         oss << value;
161         string const percentval = oss.str();
162         // a normal length
163         if (unit.empty() || unit[0] != '\\')
164                 return valstring + unit;
165         const string::size_type i = unit.find(" ", i);
166         string const endlen = (i == string::npos) ? string() : string(unit, i);
167         if (unit == "\\textwidth")
168                 return percentval + "text%" + endlen;
169         else if (unit == "\\columnwidth")
170                 return percentval + "col%" + endlen;
171         else if (unit == "\\paperwidth")
172                 return percentval + "page%" + endlen;
173         else if (unit == "\\linewidth")
174                 return percentval + "line%" + endlen;
175         else if (unit == "\\paperheight")
176                 return percentval + "pheight%" + endlen;
177         else if (unit == "\\textheight")
178                 return percentval + "theight%" + endlen;
179         else
180                 return valstring + unit;
181 }
182
183
184 void begin_inset(ostream & os, string const & name)
185 {
186         os << "\n\\begin_inset " << name;
187 }
188
189
190 void end_inset(ostream & os)
191 {
192         os << "\n\\end_inset \n\n";
193 }
194
195
196 void skip_braces(Parser & p)
197 {
198         if (p.next_token().cat() != catBegin)
199                 return;
200         p.get_token();
201         if (p.next_token().cat() == catEnd) {
202                 p.get_token();
203                 return;
204         }
205         p.putback();
206 }
207
208
209 void handle_ert(ostream & os, string const & s, Context & context, bool check_layout = true)
210 {
211         if (check_layout) {
212                 // We must have a valid layout before outputting the ERT inset.
213                 context.check_layout(os);
214         }
215         Context newcontext(true, context.textclass);
216         begin_inset(os, "ERT");
217         os << "\nstatus Collapsed\n";
218         newcontext.check_layout(os);
219         for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
220                 if (*it == '\\')
221                         os << "\n\\backslash \n";
222                 else
223                         os << *it;
224         }
225         newcontext.check_end_layout(os);
226         end_inset(os);
227 }
228
229
230 void handle_comment(ostream & os, string const & s, Context & context)
231 {
232         // TODO: Handle this better
233         Context newcontext(true, context.textclass);
234         begin_inset(os, "ERT");
235         os << "\nstatus Collapsed\n";
236         newcontext.check_layout(os);
237         for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
238                 if (*it == '\\')
239                         os << "\n\\backslash \n";
240                 else
241                         os << *it;
242         }
243         // make sure that our comment is the last thing on the line
244         os << "\n\\newline";
245         newcontext.check_end_layout(os);
246         end_inset(os);
247 }
248
249
250 struct isLayout {
251         isLayout(string const name) : name_(name) {}
252         bool operator()(LyXLayout_ptr const & ptr) {
253                 return ptr.get() && ptr->latexname() == name_;
254         }
255 private:
256         string const name_;
257 };
258
259
260 LyXLayout_ptr findLayout(LyXTextClass const & textclass,
261                          string const & name)
262 {
263         LyXTextClass::const_iterator it  = textclass.begin();
264         LyXTextClass::const_iterator end = textclass.end();
265         it = std::find_if(it, end, isLayout(name));
266         return (it == end) ? LyXLayout_ptr() : *it;
267 }
268
269
270 void output_command_layout(ostream & os, Parser & p, bool outer,
271                            Context & parent_context,
272                            LyXLayout_ptr newlayout)
273 {
274         parent_context.check_end_layout(os);
275         Context context(true, parent_context.textclass, newlayout,
276                         parent_context.layout);
277         context.check_deeper(os);
278         context.check_layout(os);
279         if (context.layout->optionalargs > 0) {
280                 p.skip_spaces();
281                 if (p.next_token().character() == '[') {
282                         p.get_token(); // eat '['
283                         begin_inset(os, "OptArg\n");
284                         os << "collapsed true\n\n";
285                         parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
286                         end_inset(os);
287                 }
288         }
289         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
290         context.check_end_layout(os);
291         context.check_end_deeper(os);
292         // We don't need really a new paragraph, but
293         // we must make sure that the next item gets a \begin_layout.
294         parent_context.new_paragraph(os);
295 }
296
297
298 /*!
299  * Output a space if necessary.
300  * This function gets called for every whitespace token.
301  *
302  * We have three cases here:
303  * 1. A space must be suppressed. Example: The lyxcode case below
304  * 2. A space may be suppressed. Example: Spaces before "\par"
305  * 3. A space must not be suppressed. Example: A space between two words
306  *
307  * We currently handle only 1. and 3 and from 2. only the case of
308  * spaces before newlines as a side effect.
309  *
310  * 2. could be used to suppress as many spaces as possible. This has two effects:
311  * - Reimporting LyX generated LaTeX files changes almost no whitespace
312  * - Superflous whitespace from non LyX generated LaTeX files is removed.
313  * The drawback is that the logic inside the function becomes
314  * complicated, and that is the reason why it is not implemented.
315  */
316 void check_space(Parser const & p, ostream & os, Context & context)
317 {
318         Token const next = p.next_token();
319         Token const curr = p.curr_token();
320         // A space before a single newline and vice versa must be ignored
321         // LyX emits a newline before \end{lyxcode}.
322         // This newline must be ignored,
323         // otherwise LyX will add an additional protected space.
324         if (next.cat() == catSpace ||
325             next.cat() == catNewline ||
326             (next.cs() == "end" && context.layout->free_spacing && curr.cat() == catNewline)) {
327                 return;
328         }
329         context.check_layout(os);
330         os << ' ';
331 }
332
333
334 /*!
335  * Check wether \param command is a known command. If yes,
336  * handle the command with all arguments.
337  * \return true if the command was parsed, false otherwise.
338  */
339 bool parse_command(string const & command, Parser & p, ostream & os,
340                    bool outer, Context & context)
341 {
342         if (known_commands.find(command) != known_commands.end()) {
343                 vector<ArgumentType> const & template_arguments = known_commands[command];
344                 string ert = command;
345                 size_t no_arguments = template_arguments.size();
346                 for (size_t i = 0; i < no_arguments; ++i) {
347                         switch (template_arguments[i]) {
348                         case required:
349                                 // This argument contains regular LaTeX
350                                 handle_ert(os, ert + '{', context);
351                                 parse_text(p, os, FLAG_ITEM, outer, context);
352                                 ert = "}";
353                                 break;
354                         case verbatim:
355                                 // This argument may contain special characters
356                                 ert += '{' + p.verbatim_item() + '}';
357                                 break;
358                         case optional:
359                                 ert += p.getOpt();
360                                 break;
361                         }
362                 }
363                 handle_ert(os, ert, context);
364                 return true;
365         }
366         return false;
367 }
368
369
370 void parse_environment(Parser & p, ostream & os, bool outer,
371                        Context & parent_context)
372 {
373         LyXLayout_ptr newlayout;
374         string const name = p.getArg('{', '}');
375         const bool is_starred = suffixIs(name, '*');
376         string const unstarred_name = rtrim(name, "*");
377         active_environments.push_back(name);
378         p.skip_spaces();
379
380         if (is_math_env(name)) {
381                 parent_context.check_layout(os);
382                 begin_inset(os, "Formula ");
383                 os << "\\begin{" << name << "}";
384                 parse_math(p, os, FLAG_END, MATH_MODE);
385                 os << "\\end{" << name << "}";
386                 end_inset(os);
387         }
388
389         else if (name == "tabular") {
390                 parent_context.check_layout(os);
391                 begin_inset(os, "Tabular ");
392                 handle_tabular(p, os, parent_context);
393                 end_inset(os);
394         }
395
396         else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
397                 parent_context.check_layout(os);
398                 begin_inset(os, "Float " + unstarred_name + "\n");
399                 if (p.next_token().asInput() == "[") {
400                         os << "placement " << p.getArg('[', ']') << '\n';
401                 }
402                 os << "wide " << tostr(is_starred)
403                    << "\ncollapsed false\n\n";
404                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
405                 end_inset(os);
406                 // We don't need really a new paragraph, but
407                 // we must make sure that the next item gets a \begin_layout.
408                 parent_context.new_paragraph(os);
409         }
410
411         else if (name == "minipage") {
412                 string position = "1";
413                 string inner_pos = "0";
414                 string height = "0pt";
415                 string latex_position;
416                 string latex_inner_pos;
417                 string latex_height;
418                 if (p.next_token().asInput() == "[") {
419                         latex_position = p.getArg('[', ']');
420                         switch(latex_position[0]) {
421                         case 't': position = "0"; break;
422                         case 'c': position = "1"; break;
423                         case 'b': position = "2"; break;
424                         default:
425                                 cerr << "invalid position for minipage"
426                                      << endl;
427                                 break;
428                         }
429                         if (p.next_token().asInput() == "[") {
430                                 latex_height = p.getArg('[', ']');
431                                 height = translate_len(latex_height);
432
433                                 if (p.next_token().asInput() == "[") {
434                                         latex_inner_pos = p.getArg('[', ']');
435                                         switch(latex_inner_pos[0]) {
436                                         case 'c': inner_pos = "0"; break;
437                                         case 't': inner_pos = "1"; break;
438                                         case 'b': inner_pos = "2"; break;
439                                         case 's': inner_pos = "3"; break;
440                                         default:
441                                                 cerr << "invalid inner_pos for minipage"
442                                                      << endl;
443                                                 break;
444                                         }
445                                 }
446                         }
447                 }
448                 string width = translate_len(p.verbatim_item());
449                 if (width[0] == '\\') {
450                         // lyx can't handle length variables
451                         ostringstream ss;
452                         ss << "\\begin{minipage}";
453                         if (!latex_position.empty())
454                                 ss << '[' << latex_position << ']';
455                         if (!latex_height.empty())
456                                 ss << '[' << latex_height << ']';
457                         if (!latex_inner_pos.empty())
458                                 ss << '[' << latex_inner_pos << ']';
459                         ss << "{" << width << "}";
460                         handle_ert(os, ss.str(), parent_context);
461                         parent_context.new_paragraph(os);
462                         parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
463                         handle_ert(os, "\\end{minipage}", parent_context);
464                 } else {
465                         parent_context.check_layout(os);
466                         begin_inset(os, "Minipage\n");
467                         os << "position " << position << '\n';
468                         os << "inner_position " << inner_pos << '\n';
469                         os << "height \"" << height << "\"\n";
470                         os << "width \"" << width << "\"\n";
471                         os << "collapsed false\n\n";
472                         parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
473                         end_inset(os);
474                 }
475         }
476
477         // Alignment settings
478         else if (name == "center" || name == "flushleft" || name == "flushright" ||
479                  name == "centering" || name == "raggedright" || name == "raggedleft") {
480                 // We must begin a new paragraph if not already done
481                 if (! parent_context.atParagraphStart()) {
482                         parent_context.check_end_layout(os);
483                         parent_context.new_paragraph(os);
484                 }
485                 if (name == "flushleft" || name == "raggedright")
486                         parent_context.extra_stuff += "\\align left ";
487                 else if (name == "flushright" || name == "raggedleft")
488                         parent_context.extra_stuff += "\\align right ";
489                 else
490                         parent_context.extra_stuff += "\\align center ";
491                 parse_text(p, os, FLAG_END, outer, parent_context);
492                 // Just in case the environment is empty ..
493                 parent_context.extra_stuff.erase();
494                 // We must begin a new paragraph to reset the alignment
495                 parent_context.new_paragraph(os);
496         }
497
498         // The single '=' is meant here.
499         else if ((newlayout = findLayout(parent_context.textclass, name)).get() &&
500                    newlayout->isEnvironment()) {
501                 Context context(true, parent_context.textclass, newlayout,
502                                 parent_context.layout);
503                 parent_context.check_end_layout(os);
504                 switch (context.layout->latextype) {
505                 case  LATEX_LIST_ENVIRONMENT:
506                         context.extra_stuff = "\\labelwidthstring "
507                                 + p.verbatim_item() + '\n';
508                         p.skip_spaces();
509                         break;
510                 case  LATEX_BIB_ENVIRONMENT:
511                         p.verbatim_item(); // swallow next arg
512                         p.skip_spaces();
513                         break;
514                 default:
515                         break;
516                 }
517                 context.check_deeper(os);
518                 parse_text(p, os, FLAG_END, outer, context);
519                 context.check_end_layout(os);
520                 context.check_end_deeper(os);
521                 parent_context.new_paragraph(os);
522         }
523
524         else if (name == "appendix") {
525                 // This is no good latex style, but it works and is used in some documents...
526                 parent_context.check_end_layout(os);
527                 Context context(true, parent_context.textclass, parent_context.layout,
528                                 parent_context.layout);
529                 context.check_layout(os);
530                 os << "\\start_of_appendix\n";
531                 parse_text(p, os, FLAG_END, outer, context);
532                 context.check_end_layout(os);
533         }
534
535         else if (name == "comment") {
536                 parent_context.check_layout(os);
537                 begin_inset(os, "Comment\n");
538                 os << "collapsed false\n";
539                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
540                 end_inset(os);
541         }
542
543         else if (name == "tabbing") {
544                 // We need to remember that we have to handle '\=' specially
545                 handle_ert(os, "\\begin{" + name + "}", parent_context);
546                 parse_text_snippet(p, os, FLAG_END | FLAG_TABBING, outer, parent_context);
547                 handle_ert(os, "\\end{" + name + "}", parent_context);
548         }
549
550         else {
551                 handle_ert(os, "\\begin{" + name + "}", parent_context);
552                 parse_text_snippet(p, os, FLAG_END, outer, parent_context);
553                 handle_ert(os, "\\end{" + name + "}", parent_context);
554         }
555
556         active_environments.pop_back();
557         if (name != "math")
558                 p.skip_spaces();
559 }
560
561 } // anonymous namespace
562
563
564
565
566 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
567                 Context & context)
568 {
569         LyXLayout_ptr newlayout;
570         // Store the latest bibliographystyle (needed for bibtex inset)
571         string bibliographystyle;
572         while (p.good()) {
573                 Token const & t = p.get_token();
574
575 #ifdef FILEDEBUG
576                 cerr << "t: " << t << " flags: " << flags << "\n";
577 #endif
578
579                 if (flags & FLAG_ITEM) {
580                         if (t.cat() == catSpace)
581                                 continue;
582
583                         flags &= ~FLAG_ITEM;
584                         if (t.cat() == catBegin) {
585                                 // skip the brace and collect everything to the next matching
586                                 // closing brace
587                                 flags |= FLAG_BRACE_LAST;
588                                 continue;
589                         }
590
591                         // handle only this single token, leave the loop if done
592                         flags |= FLAG_LEAVE;
593                 }
594
595                 if (t.character() == ']' && (flags & FLAG_BRACK_LAST))
596                         return;
597
598                 //
599                 // cat codes
600                 //
601                 if (t.cat() == catMath) {
602                         // we are inside some text mode thingy, so opening new math is allowed
603                         context.check_layout(os);
604                         begin_inset(os, "Formula ");
605                         Token const & n = p.get_token();
606                         if (n.cat() == catMath && outer) {
607                                 // TeX's $$...$$ syntax for displayed math
608                                 os << "\\[";
609                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
610                                 os << "\\]";
611                                 p.get_token(); // skip the second '$' token
612                         } else {
613                                 // simple $...$  stuff
614                                 p.putback();
615                                 os << '$';
616                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
617                                 os << '$';
618                         }
619                         end_inset(os);
620                 }
621
622                 else if (t.cat() == catSuper || t.cat() == catSub)
623                         cerr << "catcode " << t << " illegal in text mode\n";
624
625                 // Basic support for english quotes. This should be
626                 // extended to other quotes, but is not so easy (a
627                 // left english quote is the same as a right german
628                 // quote...)
629                 else if (t.asInput() == "`"
630                          && p.next_token().asInput() == "`") {
631                         context.check_layout(os);
632                         begin_inset(os, "Quotes ");
633                         os << "eld";
634                         end_inset(os);
635                         p.get_token();
636                         skip_braces(p);
637                 }
638                 else if (t.asInput() == "'"
639                          && p.next_token().asInput() == "'") {
640                         context.check_layout(os);
641                         begin_inset(os, "Quotes ");
642                         os << "erd";
643                         end_inset(os);
644                         p.get_token();
645                         skip_braces(p);
646                 }
647
648                 else if (t.cat() == catSpace || (t.cat() == catNewline && t.cs().size() == 1))
649                         check_space(p, os, context);
650
651                 else if (t.cat() == catLetter ||
652                                t.cat() == catOther ||
653                                t.cat() == catAlign ||
654                                t.cat() == catParameter) {
655                         context.check_layout(os);
656                         os << t.character();
657                 }
658
659                 else if (t.cat() == catNewline || (t.cat() == catEscape && t.cs() == "par")) {
660                         p.skip_spaces();
661                         context.new_paragraph(os);
662                 }
663
664                 else if (t.cat() == catActive) {
665                         context.check_layout(os);
666                         if (t.character() == '~') {
667                                 if (context.layout->free_spacing)
668                                         os << ' ';
669                                 else
670                                         os << "\\InsetSpace ~\n";
671                         } else
672                                 os << t.character();
673                 }
674
675                 else if (t.cat() == catBegin) {
676                         // special handling of size changes
677                         context.check_layout(os);
678                         bool const is_size = is_known(p.next_token().cs(), known_sizes);
679                         Token const prev = p.prev_token();
680                         string const s = parse_text(p, FLAG_BRACE_LAST, outer, context);
681                         if (s.empty() && (p.next_token().character() == '`' ||
682                                           (prev.character() == '-' && p.next_token().character())))
683                                 ; // ignore it in {}`` or -{}-
684                         else if (is_size || s == "[" || s == "]" || s == "*")
685                                 os << s;
686                         else {
687                                 handle_ert(os, "{", context, false);
688                                 // s will end the current layout and begin a new one if necessary
689                                 os << s;
690                                 handle_ert(os, "}", context);
691                         }
692                 }
693
694                 else if (t.cat() == catEnd) {
695                         if (flags & FLAG_BRACE_LAST) {
696                                 return;
697                         }
698                         cerr << "stray '}' in text\n";
699                         handle_ert(os, "}", context);
700                 }
701
702                 else if (t.cat() == catComment) {
703                         context.check_layout(os);
704                         if (!t.cs().empty()) {
705                                 handle_comment(os, '%' + t.cs(), context);
706                                 if (p.next_token().cat() == catNewline) {
707                                         // A newline after a comment line starts a new paragraph
708                                         context.new_paragraph(os);
709                                         p.skip_spaces();
710                                 }
711                         } else {
712                                 // "%\n" combination
713                                 p.skip_spaces();
714                         }
715                 }
716
717                 //
718                 // control sequences
719                 //
720
721                 else if (t.cs() == "(") {
722                         context.check_layout(os);
723                         begin_inset(os, "Formula");
724                         os << " \\(";
725                         parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
726                         os << "\\)";
727                         end_inset(os);
728                 }
729
730                 else if (t.cs() == "[") {
731                         context.check_layout(os);
732                         begin_inset(os, "Formula");
733                         os << " \\[";
734                         parse_math(p, os, FLAG_EQUATION, MATH_MODE);
735                         os << "\\]";
736                         end_inset(os);
737                 }
738
739                 else if (t.cs() == "begin")
740                         parse_environment(p, os, outer, context);
741
742                 else if (t.cs() == "end") {
743                         if (flags & FLAG_END) {
744                                 // eat environment name
745                                 string const name = p.getArg('{', '}');
746                                 if (name != active_environment())
747                                         cerr << "\\end{" + name + "} does not match \\begin{"
748                                                 + active_environment() + "}\n";
749                                 return;
750                         }
751                         p.error("found 'end' unexpectedly");
752                 }
753
754                 else if (t.cs() == "item") {
755                         p.skip_spaces();
756                         string s;
757                         bool optarg = false;
758                         if (p.next_token().character() == '[') {
759                                 p.get_token(); // eat '['
760                                 Context newcontext(false, context.textclass);
761                                 s = parse_text(p, FLAG_BRACK_LAST, outer, newcontext);
762                                 optarg = true;
763                         }
764                         context.set_item();
765                         context.check_layout(os);
766                         if (optarg) {
767                                 if (context.layout->labeltype != LABEL_MANUAL) {
768                                         // lyx does not support \item[\mybullet] in itemize environments
769                                         handle_ert(os, "[", context);
770                                         os << s;
771                                         handle_ert(os, "]", context);
772                                 } else if (!s.empty()) {
773                                         // The space is needed to separate the item from the rest of the sentence.
774                                         os << s << ' ';
775                                         p.skip_spaces();
776                                 }
777                         }
778                 }
779
780                 else if (t.cs() == "bibitem") {
781                         context.set_item();
782                         context.check_layout(os);
783                         os << "\\bibitem ";
784                         os << p.getOpt();
785                         os << '{' << p.verbatim_item() << '}' << "\n";
786                 }
787
788                 else if (t.cs() == "def") {
789                         p.skip_spaces();
790                         context.check_layout(os);
791                         string name = p.get_token().cs();
792                         while (p.next_token().cat() != catBegin)
793                                 name += p.get_token().asString();
794                         handle_ert(os, "\\def\\" + name + '{' + p.verbatim_item() + '}', context);
795                 }
796
797                 else if (t.cs() == "noindent") {
798                         p.skip_spaces();
799                         context.extra_stuff += "\\noindent ";
800                 }
801
802                 else if (t.cs() == "appendix") {
803                         p.skip_spaces();
804                         context.extra_stuff += "\\start_of_appendix ";
805                 }
806
807                 // Must attempt to parse "Section*" before "Section".
808                 else if ((p.next_token().asInput() == "*") &&
809                          // The single '=' is meant here.
810                          (newlayout = findLayout(context.textclass,
811                                                  t.cs() + '*')).get() &&
812                          newlayout->isCommand()) {
813                         p.get_token();
814                         output_command_layout(os, p, outer, context, newlayout);
815                         p.skip_spaces();
816                 }
817
818                 // The single '=' is meant here.
819                 else if ((newlayout = findLayout(context.textclass, t.cs())).get() &&
820                          newlayout->isCommand()) {
821                         output_command_layout(os, p, outer, context, newlayout);
822                         p.skip_spaces();
823                 }
824
825                 else if (t.cs() == "includegraphics") {
826                         map<string, string> opts = split_map(p.getArg('[', ']'));
827                         string name = p.verbatim_item();
828
829                         context.check_layout(os);
830                         begin_inset(os, "Graphics ");
831                         os << "\n\tfilename " << name << '\n';
832                         if (opts.find("width") != opts.end())
833                                 os << "\twidth "
834                                    << translate_len(opts["width"]) << '\n';
835                         if (opts.find("height") != opts.end())
836                                 os << "\theight "
837                                    << translate_len(opts["height"]) << '\n';
838                         if (opts.find("scale") != opts.end()) {
839                                 istringstream iss(opts["scale"]);
840                                 double val;
841                                 iss >> val;
842                                 val = val*100;
843                                 os << "\tscale " << val << '\n';
844                         }
845                         if (opts.find("angle") != opts.end())
846                                 os << "\trotateAngle "
847                                    << opts["angle"] << '\n';
848                         if (opts.find("origin") != opts.end()) {
849                                 ostringstream ss;
850                                 string const opt = opts["origin"];
851                                 if (opt.find('l') != string::npos) ss << "left";
852                                 if (opt.find('r') != string::npos) ss << "right";
853                                 if (opt.find('c') != string::npos) ss << "center";
854                                 if (opt.find('t') != string::npos) ss << "Top";
855                                 if (opt.find('b') != string::npos) ss << "Bottom";
856                                 if (opt.find('B') != string::npos) ss << "Baseline";
857                                 if (!ss.str().empty())
858                                         os << "\trotateOrigin " << ss.str() << '\n';
859                                 else
860                                         cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
861                         }
862                         if (opts.find("keepaspectratio") != opts.end())
863                                 os << "\tkeepAspectRatio\n";
864                         if (opts.find("clip") != opts.end())
865                                 os << "\tclip\n";
866                         if (opts.find("draft") != opts.end())
867                                 os << "\tdraft\n";
868                         if (opts.find("bb") != opts.end())
869                                 os << "\tBoundingBox "
870                                    << opts["bb"] << '\n';
871                         int numberOfbbOptions = 0;
872                         if (opts.find("bbllx") != opts.end())
873                                 numberOfbbOptions++;
874                         if (opts.find("bblly") != opts.end())
875                                 numberOfbbOptions++;
876                         if (opts.find("bburx") != opts.end())
877                                 numberOfbbOptions++;
878                         if (opts.find("bbury") != opts.end())
879                                 numberOfbbOptions++;
880                         if (numberOfbbOptions == 4)
881                                 os << "\tBoundingBox "
882                                    << opts["bbllx"] << opts["bblly"]
883                                    << opts["bburx"] << opts["bbury"] << '\n';
884                         else if (numberOfbbOptions > 0)
885                                 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
886                         numberOfbbOptions = 0;
887                         if (opts.find("natwidth") != opts.end())
888                                 numberOfbbOptions++;
889                         if (opts.find("natheight") != opts.end())
890                                 numberOfbbOptions++;
891                         if (numberOfbbOptions == 2)
892                                 os << "\tBoundingBox 0bp 0bp "
893                                    << opts["natwidth"] << opts["natheight"] << '\n';
894                         else if (numberOfbbOptions > 0)
895                                 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
896                         ostringstream special;
897                         if (opts.find("hiresbb") != opts.end())
898                                 special << "hiresbb,";
899                         if (opts.find("trim") != opts.end())
900                                 special << "trim,";
901                         if (opts.find("viewport") != opts.end())
902                                 special << "viewport=" << opts["viewport"] << ',';
903                         if (opts.find("totalheight") != opts.end())
904                                 special << "totalheight=" << opts["totalheight"] << ',';
905                         if (opts.find("type") != opts.end())
906                                 special << "type=" << opts["type"] << ',';
907                         if (opts.find("ext") != opts.end())
908                                 special << "ext=" << opts["ext"] << ',';
909                         if (opts.find("read") != opts.end())
910                                 special << "read=" << opts["read"] << ',';
911                         if (opts.find("command") != opts.end())
912                                 special << "command=" << opts["command"] << ',';
913                         string s_special = special.str();
914                         if (!s_special.empty()) {
915                                 // We had special arguments. Remove the trailing ','.
916                                 os << "\tspecial " << s_special.substr(0, s_special.size() - 1) << '\n';
917                         }
918                         // TODO: Handle the unknown settings better.
919                         // Warn about invalid options.
920                         // Check wether some option was given twice.
921                         end_inset(os);
922                 }
923
924                 else if (t.cs() == "footnote") {
925                         p.skip_spaces();
926                         context.check_layout(os);
927                         begin_inset(os, "Foot\n");
928                         os << "collapsed true\n\n";
929                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
930                         end_inset(os);
931                 }
932
933                 else if (t.cs() == "marginpar") {
934                         p.skip_spaces();
935                         context.check_layout(os);
936                         begin_inset(os, "Marginal\n");
937                         os << "collapsed true\n\n";
938                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
939                         end_inset(os);
940                 }
941
942                 else if (t.cs() == "ensuremath") {
943                         p.skip_spaces();
944                         context.check_layout(os);
945                         Context newcontext(false, context.textclass);
946                         string s = parse_text(p, FLAG_ITEM, false, newcontext);
947                         if (s == "±" || s == "³" || s == "²" || s == "µ")
948                                 os << s;
949                         else
950                                 handle_ert(os, "\\ensuremath{" + s + "}",
951                                            context);
952                 }
953
954                 else if (t.cs() == "hfill") {
955                         context.check_layout(os);
956                         os << "\n\\hfill\n";
957                         skip_braces(p);
958                         p.skip_spaces();
959                 }
960
961                 else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
962                         p.skip_spaces();
963                         skip_braces(p); // swallow this
964                 }
965
966                 else if (t.cs() == "tableofcontents") {
967                         p.skip_spaces();
968                         context.check_layout(os);
969                         begin_inset(os, "LatexCommand \\tableofcontents\n");
970                         end_inset(os);
971                         skip_braces(p); // swallow this
972                 }
973
974                 else if (t.cs() == "listoffigures") {
975                         p.skip_spaces();
976                         context.check_layout(os);
977                         begin_inset(os, "FloatList figure\n");
978                         end_inset(os);
979                         skip_braces(p); // swallow this
980                 }
981
982                 else if (t.cs() == "listoftables") {
983                         p.skip_spaces();
984                         context.check_layout(os);
985                         begin_inset(os, "FloatList table\n");
986                         end_inset(os);
987                         skip_braces(p); // swallow this
988                 }
989
990                 else if (t.cs() == "listof") {
991                         p.skip_spaces(true);
992                         string const name = p.get_token().asString();
993                         if (context.textclass.floats().typeExist(name)) {
994                                 context.check_layout(os);
995                                 begin_inset(os, "FloatList ");
996                                 os << name << "\n";
997                                 end_inset(os);
998                                 p.get_token(); // swallow second arg
999                         } else
1000                                 handle_ert(os, "\\listof{" + name + "}", context);
1001                 }
1002
1003                 else if (t.cs() == "textrm") {
1004                         context.check_layout(os);
1005                         os << "\n\\family roman \n";
1006                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1007                         os << "\n\\family default \n";
1008                 }
1009
1010                 else if (t.cs() == "textsf") {
1011                         context.check_layout(os);
1012                         os << "\n\\family sans \n";
1013                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1014                         os << "\n\\family default \n";
1015                 }
1016
1017                 else if (t.cs() == "textsl") {
1018                         context.check_layout(os);
1019                         os << "\n\\shape slanted \n";
1020                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1021                         os << "\n\\shape default \n";
1022                 }
1023
1024                 else if (t.cs() == "texttt") {
1025                         context.check_layout(os);
1026                         os << "\n\\family typewriter \n";
1027                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1028                         os << "\n\\family default \n";
1029                 }
1030
1031                 else if (t.cs() == "textit") {
1032                         context.check_layout(os);
1033                         os << "\n\\shape italic \n";
1034                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1035                         os << "\n\\shape default \n";
1036                 }
1037
1038                 else if (t.cs() == "textsc") {
1039                         context.check_layout(os);
1040                         os << "\n\\noun on \n";
1041                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1042                         os << "\n\\noun default \n";
1043                 }
1044
1045                 else if (t.cs() == "textbf") {
1046                         context.check_layout(os);
1047                         os << "\n\\series bold \n";
1048                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1049                         os << "\n\\series default \n";
1050                 }
1051
1052                 else if (t.cs() == "underbar") {
1053                         context.check_layout(os);
1054                         os << "\n\\bar under \n";
1055                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1056                         os << "\n\\bar default \n";
1057                 }
1058
1059                 else if (t.cs() == "emph" || t.cs() == "noun") {
1060                         context.check_layout(os);
1061                         os << "\n\\" << t.cs() << " on \n";
1062                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
1063                         os << "\n\\" << t.cs() << " default \n";
1064                 }
1065
1066                 else if (is_known(t.cs(), known_latex_commands)) {
1067                         context.check_layout(os);
1068                         begin_inset(os, "LatexCommand ");
1069                         os << '\\' << t.cs();
1070                         os << p.getOpt();
1071                         os << p.getOpt();
1072                         os << '{' << p.verbatim_item() << "}\n";
1073                         end_inset(os);
1074                 }
1075
1076                 else if (is_known(t.cs(), known_quotes)) {
1077                         char const ** where = is_known(t.cs(), known_quotes);
1078                         context.check_layout(os);
1079                         begin_inset(os, "Quotes ");
1080                         os << known_coded_quotes[where - known_quotes];
1081                         end_inset(os);
1082                         skip_braces(p);
1083                 }
1084
1085                 else if (is_known(t.cs(), known_sizes)) {
1086                         char const ** where = is_known(t.cs(), known_sizes);
1087                         context.check_layout(os);
1088                         os << "\n\\size " << known_coded_sizes[where - known_sizes] << "\n";
1089                         p.skip_spaces();
1090                 }
1091
1092                 else if (t.cs() == "LyX" || t.cs() == "TeX"
1093                          || t.cs() == "LaTeX") {
1094                         context.check_layout(os);
1095                         os << t.cs();
1096                         skip_braces(p); // eat {}
1097                 }
1098
1099                 else if (t.cs() == "LaTeXe") {
1100                         context.check_layout(os);
1101                         os << "LaTeX2e";
1102                         skip_braces(p); // eat {}
1103                 }
1104
1105                 else if (t.cs() == "ldots") {
1106                         context.check_layout(os);
1107                         skip_braces(p);
1108                         os << "\\SpecialChar \\ldots{}\n";
1109                 }
1110
1111                 else if (t.cs() == "lyxarrow") {
1112                         context.check_layout(os);
1113                         os << "\\SpecialChar \\menuseparator\n";
1114                         skip_braces(p);
1115                 }
1116
1117                 else if (t.cs() == "textcompwordmark") {
1118                         context.check_layout(os);
1119                         os << "\\SpecialChar \\textcompwordmark{}\n";
1120                         skip_braces(p);
1121                 }
1122
1123                 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
1124                         context.check_layout(os);
1125                         os << "\\SpecialChar \\@.\n";
1126                         p.get_token();
1127                 }
1128
1129                 else if (t.cs() == "-") {
1130                         context.check_layout(os);
1131                         os << "\\SpecialChar \\-\n";
1132                 }
1133
1134                 else if (t.cs() == "textasciitilde") {
1135                         context.check_layout(os);
1136                         os << '~';
1137                         skip_braces(p);
1138                 }
1139
1140                 else if (t.cs() == "textasciicircum") {
1141                         context.check_layout(os);
1142                         os << '^';
1143                         skip_braces(p);
1144                 }
1145
1146                 else if (t.cs() == "textbackslash") {
1147                         context.check_layout(os);
1148                         os << "\n\\backslash \n";
1149                         skip_braces(p);
1150                 }
1151
1152                 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
1153                             || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
1154                             || t.cs() == "%") {
1155                         context.check_layout(os);
1156                         os << t.cs();
1157                 }
1158
1159                 else if (t.cs() == "char") {
1160                         context.check_layout(os);
1161                         if (p.next_token().character() == '`') {
1162                                 p.get_token();
1163                                 if (p.next_token().cs() == "\"") {
1164                                         p.get_token();
1165                                         os << '"';
1166                                         skip_braces(p);
1167                                 } else {
1168                                         handle_ert(os, "\\char`", context);
1169                                 }
1170                         } else {
1171                                 handle_ert(os, "\\char", context);
1172                         }
1173                 }
1174
1175                 else if (t.cs() == "\"") {
1176                         context.check_layout(os);
1177                         string const name = p.verbatim_item();
1178                              if (name == "a") os << 'ä';
1179                         else if (name == "o") os << 'ö';
1180                         else if (name == "u") os << 'ü';
1181                         else if (name == "A") os << 'Ä';
1182                         else if (name == "O") os << 'Ö';
1183                         else if (name == "U") os << 'Ü';
1184                         else handle_ert(os, "\"{" + name + "}", context);
1185                 }
1186
1187                 // Problem: \= creates a tabstop inside the tabbing environment
1188                 // and else an accent. In the latter case we really would want
1189                 // \={o} instead of \= o.
1190                 else if (t.cs() == "=" && (flags & FLAG_TABBING))
1191                         handle_ert(os, t.asInput(), context);
1192
1193                 else if (t.cs() == "H" || t.cs() == "c" || t.cs() == "^" || t.cs() == "'"
1194                       || t.cs() == "~" || t.cs() == "." || t.cs() == "=") {
1195                         // we need the trim as the LyX parser chokes on such spaces
1196                         context.check_layout(os);
1197                         os << "\n\\i \\" << t.cs() << "{"
1198                            << trim(parse_text(p, FLAG_ITEM, outer, context), " ") << "}\n";
1199                 }
1200
1201                 else if (t.cs() == "ss") {
1202                         context.check_layout(os);
1203                         os << "ß";
1204                 }
1205
1206                 else if (t.cs() == "i" || t.cs() == "j") {
1207                         context.check_layout(os);
1208                         os << "\\" << t.cs() << ' ';
1209                 }
1210
1211                 else if (t.cs() == "\\") {
1212                         context.check_layout(os);
1213                         string const next = p.next_token().asInput();
1214                         if (next == "[")
1215                                 handle_ert(os, "\\\\" + p.getOpt(), context);
1216                         else if (next == "*") {
1217                                 p.get_token();
1218                                 handle_ert(os, "\\\\*" + p.getOpt(), context);
1219                         }
1220                         else {
1221                                 os << "\n\\newline \n";
1222                         }
1223                 }
1224
1225                 else if (t.cs() == "input" || t.cs() == "include"
1226                          || t.cs() == "verbatiminput") {
1227                         string name = '\\' + t.cs();
1228                         if (t.cs() == "verbatiminput"
1229                             && p.next_token().asInput() == "*")
1230                                 name += p.get_token().asInput();
1231                         context.check_layout(os);
1232                         begin_inset(os, "Include ");
1233                         string filename(p.getArg('{', '}'));
1234                         string lyxname(lyx::support::ChangeExtension(filename, ".lyx"));
1235                         if (tex2lyx(filename, lyxname)) {
1236                                 os << name << '{' << lyxname << "}\n";
1237                         } else {
1238                                 os << name << '{' << filename << "}\n";
1239                         }
1240                         os << "preview false\n";
1241                         end_inset(os);
1242                 }
1243
1244                 else if (t.cs() == "fancyhead") {
1245                         context.check_layout(os);
1246                         ostringstream ss;
1247                         ss << "\\fancyhead";
1248                         ss << p.getOpt();
1249                         ss << '{' << p.verbatim_item() << "}\n";
1250                         handle_ert(os, ss.str(), context);
1251                 }
1252
1253                 else if (t.cs() == "bibliographystyle") {
1254                         // store new bibliographystyle
1255                         bibliographystyle = p.verbatim_item();
1256                         // output new bibliographystyle.
1257                         // This is only necessary if used in some other macro than \bibliography.
1258                         handle_ert(os, "\\bibliographystyle{" + bibliographystyle + "}", context);
1259                 }
1260
1261                 else if (t.cs() == "bibliography") {
1262                         context.check_layout(os);
1263                         begin_inset(os, "LatexCommand ");
1264                         os << "\\bibtex";
1265                         // Do we have a bibliographystyle set?
1266                         if (!bibliographystyle.empty()) {
1267                                 os << '[' << bibliographystyle << ']';
1268                         }
1269                         os << '{' << p.verbatim_item() << "}\n";
1270                         end_inset(os);
1271                 }
1272
1273                 else if (t.cs() == "smallskip" ||
1274                          t.cs() == "medskip" ||
1275                          t.cs() == "bigskip" ||
1276                          t.cs() == "vfill") {
1277                         context.check_layout(os);
1278                         begin_inset(os, "VSpace ");
1279                         os << t.cs();
1280                         end_inset(os);
1281                 }
1282
1283                 else if (t.cs() == "vspace") {
1284                         bool starred = false;
1285                         if (p.next_token().asInput() == "*") {
1286                                 p.get_token();
1287                                 starred = true;
1288                         }
1289                         string const length = p.verbatim_item();
1290                         string unit;
1291                         string valstring;
1292                         bool valid = splitLatexLength(length, valstring, unit);
1293                         bool known_vspace = false;
1294                         bool known_unit = false;
1295                         double value;
1296                         if (valid) {
1297                                 istringstream iss(valstring);
1298                                 iss >> value;
1299                                 if (value == 1.0) {
1300                                         if (unit == "\\smallskipamount") {
1301                                                 unit = "smallskip";
1302                                                 known_vspace = true;
1303                                         } else if (unit == "\\medskipamount") {
1304                                                 unit = "medskip";
1305                                                 known_vspace = true;
1306                                         } else if (unit == "\\bigskipamount") {
1307                                                 unit = "bigskip";
1308                                                 known_vspace = true;
1309                                         } else if (unit == "\\fill") {
1310                                                 unit = "vfill";
1311                                                 known_vspace = true;
1312                                         }
1313                                 } else {
1314                                         switch (unitFromString(unit)) {
1315                                         case LyXLength::SP:
1316                                         case LyXLength::PT:
1317                                         case LyXLength::BP:
1318                                         case LyXLength::DD:
1319                                         case LyXLength::MM:
1320                                         case LyXLength::PC:
1321                                         case LyXLength::CC:
1322                                         case LyXLength::CM:
1323                                         case LyXLength::IN:
1324                                         case LyXLength::EX:
1325                                         case LyXLength::EM:
1326                                         case LyXLength::MU:
1327                                                 known_unit = true;
1328                                                 break;
1329                                         default:
1330                                                 break;
1331                                         }
1332                                 }
1333                         }
1334
1335                         if (known_unit || known_vspace) {
1336                                 // Literal length or known variable
1337                                 context.check_layout(os);
1338                                 begin_inset(os, "VSpace ");
1339                                 if (known_unit)
1340                                         os << value;
1341                                 os << unit;
1342                                 if (starred)
1343                                         os << '*';
1344                                 end_inset(os);
1345                         } else {
1346                                 // LyX can't handle other length variables in Inset VSpace
1347                                 string name = t.asInput();
1348                                 if (starred)
1349                                         name += '*';
1350                                 if (valid) {
1351                                         if (value == 1.0)
1352                                                 handle_ert(os, name + '{' + unit + '}', context);
1353                                         else if (value == -1.0)
1354                                                 handle_ert(os, name + "{-" + unit + '}', context);
1355                                         else
1356                                                 handle_ert(os, name + '{' + valstring + unit + '}', context);
1357                                 } else
1358                                         handle_ert(os, name + '{' + length + '}', context);
1359                         }
1360                 }
1361
1362                 else {
1363                         //cerr << "#: " << t << " mode: " << mode << endl;
1364                         // heuristic: read up to next non-nested space
1365                         /*
1366                         string s = t.asInput();
1367                         string z = p.verbatim_item();
1368                         while (p.good() && z != " " && z.size()) {
1369                                 //cerr << "read: " << z << endl;
1370                                 s += z;
1371                                 z = p.verbatim_item();
1372                         }
1373                         cerr << "found ERT: " << s << endl;
1374                         handle_ert(os, s + ' ', context);
1375                         */
1376                         string name = t.asInput();
1377                         if (p.next_token().asInput() == "*") {
1378                                 // Starred commands like \vspace*{}
1379                                 p.get_token();                          // Eat '*'
1380                                 name += '*';
1381                         }
1382                         if (! parse_command(name, p, os, outer, context))
1383                                 handle_ert(os, name, context);
1384                 }
1385
1386                 if (flags & FLAG_LEAVE) {
1387                         flags &= ~FLAG_LEAVE;
1388                         break;
1389                 }
1390         }
1391 }
1392
1393
1394 // }])