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