]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/text.C
a1817e04101378bdf64d94cd40b32d6985814ec1
[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 "support/lstrings.h"
20 #include "support/tostr.h"
21 #include "support/filetools.h"
22
23 #include <iostream>
24 #include <map>
25 #include <sstream>
26 #include <vector>
27
28 using std::cerr;
29 using std::endl;
30
31 using std::map;
32 using std::ostream;
33 using std::ostringstream;
34 using std::istringstream;
35 using std::string;
36 using std::vector;
37
38 using lyx::support::rtrim;
39 using lyx::support::suffixIs;
40
41
42 // thin wrapper around parse_text using a string
43 string parse_text(Parser & p, unsigned flags, const bool outer,
44                   Context & context)
45 {
46         ostringstream os;
47         parse_text(p, os, flags, outer, context);
48         return os.str();
49 }
50
51 // parses a subdocument, usually useful in insets (whence the name)
52 void parse_text_in_inset(Parser & p, ostream & os, unsigned flags, bool outer,
53                 Context & context)
54 {
55         Context newcontext(true, context.textclass);
56         parse_text(p, os, flags, outer, newcontext);
57         newcontext.check_end_layout(os);
58 }
59
60
61 // parses a paragraph snippet, useful for example for \emph{...}
62 void parse_text_snippet(Parser & p, ostream & os, unsigned flags, bool outer,
63                 Context & context)
64 {
65         Context newcontext(false, context.textclass);
66         parse_text(p, os, flags, outer, newcontext);
67         // should not be needed
68         newcontext.check_end_layout(os);
69 }
70
71
72 namespace {
73
74 char const * known_latex_commands[] = { "ref", "cite", "label", "index",
75 "printindex", "pageref", "url", "vref", "vpageref", "prettyref", "eqref", 0 };
76
77 // LaTeX names for quotes
78 char const * known_quotes[] = { "glqq", "grqq", "quotedblbase",
79 "textquotedblleft", "quotesinglbase", "guilsinglleft", "guilsinglright", 0};
80
81 // the same as known_quotes with .lyx names
82 char const * known_coded_quotes[] = { "gld", "grd", "gld",
83 "grd", "gls", "fls", "frd", 0};
84
85 char const * known_sizes[] = { "tiny", "scriptsize", "footnotesize",
86 "small", "normalsize", "large", "Large", "LARGE", "huge", "Huge", 0};
87
88 char const * known_coded_sizes[] = { "tiny", "scriptsize", "footnotesize",
89 "small", "normal", "large", "larger", "largest",  "huge", "giant", 0};
90
91 // splits "x=z, y=b" into a map
92 map<string, string> split_map(string const & s)
93 {
94         map<string, string> res;
95         vector<string> v;
96         split(s, v);
97         for (size_t i = 0; i < v.size(); ++i) {
98                 size_t const pos   = v[i].find('=');
99                 string const index = v[i].substr(0, pos);
100                 string const value = v[i].substr(pos + 1, string::npos);
101                 res[trim(index)] = trim(value);
102         }
103         return res;
104 }
105
106 // A simple function to translate a latex length to something lyx can
107 // understand. Not perfect, but rather best-effort.
108 string translate_len(string const & len)
109 {
110         const string::size_type i = len.find_first_not_of(" -0123456789.,");
111         //'4,5' is a valid LaTeX length number. Change it to '4.5'
112         string const length = lyx::support::subst(len, ',', '.');
113         // a normal length
114         if (i == string::npos || len[i]  != '\\')
115                 return length;
116         double val;
117         if (i == 0) {
118                 // We had something like \textwidth without a factor
119                 val = 100;
120         } else {
121                 istringstream iss(string(length, 0, i));
122                 iss >> val;
123                 val = val * 100;
124         }
125         ostringstream oss;
126         oss << val;
127         string const valstring = oss.str();
128         const string::size_type i2 = length.find(" ", i);
129         string const unit = string(len, i, i2 - i);
130         string const endlen = (i2 == string::npos) ? string() : string(len, i2);
131         if (unit == "\\textwidth")
132                 return valstring + "text%" + endlen;
133         else if (unit == "\\columnwidth")
134                 return valstring + "col%" + endlen;
135         else if (unit == "\\paperwidth")
136                 return valstring + "page%" + endlen;
137         else if (unit == "\\linewidth")
138                 return valstring + "line%" + endlen;
139         else if (unit == "\\paperheight")
140                 return valstring + "pheight%" + endlen;
141         else if (unit == "\\textheight")
142                 return valstring + "theight%" + endlen;
143         else
144                 return length;
145 }
146
147
148 void begin_inset(ostream & os, string const & name)
149 {
150         os << "\n\\begin_inset " << name;
151 }
152
153
154 void end_inset(ostream & os)
155 {
156         os << "\n\\end_inset \n\n";
157 }
158
159
160 void skip_braces(Parser & p)
161 {
162         if (p.next_token().cat() != catBegin)
163                 return;
164         p.get_token();
165         if (p.next_token().cat() == catEnd) {
166                 p.get_token();
167                 return;
168         }
169         p.putback();
170 }
171
172
173 void handle_ert(ostream & os, string const & s, Context const & context)
174 {
175         Context newcontext(true, context.textclass);
176         begin_inset(os, "ERT");
177         os << "\nstatus Collapsed\n";
178         newcontext.check_layout(os);
179         for (string::const_iterator it = s.begin(), et = s.end(); it != et; ++it) {
180                 if (*it == '\\')
181                         os << "\n\\backslash \n";
182                 else
183                         os << *it;
184         }
185         newcontext.check_end_layout(os);
186         end_inset(os);
187 }
188
189
190 struct isLayout {
191         isLayout(string const name) : name_(name) {}
192         bool operator()(LyXLayout_ptr const & ptr) {
193                 return ptr.get() && ptr->latexname() == name_;
194         }
195 private:
196         string const name_;
197 };
198
199
200 LyXLayout_ptr findLayout(LyXTextClass const & textclass,
201                          string const & name)
202 {
203         LyXTextClass::const_iterator it  = textclass.begin();
204         LyXTextClass::const_iterator end = textclass.end();
205         it = std::find_if(it, end, isLayout(name));
206         return (it == end) ? LyXLayout_ptr() : *it;
207 }
208
209
210 void output_command_layout(ostream & os, Parser & p, bool outer,
211                            Context & parent_context,
212                            LyXLayout_ptr newlayout)
213 {
214         parent_context.check_end_layout(os);
215         Context context(true, parent_context.textclass, newlayout,
216                         parent_context.layout);
217         context.check_deeper(os);
218         context.check_layout(os);
219         if (context.layout->optionalargs > 0) {
220                 if (p.next_token().character() == '[') {
221                         p.get_token(); // eat '['
222                         begin_inset(os, "OptArg\n");
223                         os << "collapsed true\n";
224                         parse_text_in_inset(p, os, FLAG_BRACK_LAST, outer, context);
225                         end_inset(os);
226                 }
227         }
228         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
229         context.check_end_layout(os);
230         context.check_end_deeper(os);
231 }
232
233
234 void parse_environment(Parser & p, ostream & os, bool outer,
235                        Context & parent_context)
236 {
237         LyXLayout_ptr newlayout;
238         string const name = p.getArg('{', '}');
239         const bool is_starred = suffixIs(name, '*');
240         string const unstarred_name = rtrim(name, "*");
241         active_environments.push_back(name);
242         if (is_math_env(name)) {
243                 parent_context.check_layout(os);
244                 begin_inset(os, "Formula ");
245                 os << "\\begin{" << name << "}";
246                 parse_math(p, os, FLAG_END, MATH_MODE);
247                 os << "\\end{" << name << "}";
248                 end_inset(os);
249         }
250
251         else if (name == "tabular") {
252                 parent_context.check_layout(os);
253                 begin_inset(os, "Tabular ");
254                 handle_tabular(p, os, parent_context);
255                 end_inset(os);
256         }
257
258         else if (parent_context.textclass.floats().typeExist(unstarred_name)) {
259                 parent_context.check_layout(os);
260                 begin_inset(os, "Float " + unstarred_name + "\n");
261                 if (p.next_token().asInput() == "[") {
262                         os << "placement " << p.getArg('[', ']') << '\n';
263                 }
264                 os << "wide " << tostr(is_starred)
265                    << "\ncollapsed false\n";
266                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
267                 end_inset(os);
268         }
269
270         else if (name == "minipage") {
271                 parent_context.check_layout(os);
272                 string position = "1";
273                 string inner_pos = "0";
274                 string height = "0pt";
275                 string latex_position;
276                 string latex_inner_pos;
277                 string latex_height;
278                 if (p.next_token().asInput() == "[") {
279                         latex_position = p.getArg('[', ']');
280                         switch(latex_position[0]) {
281                         case 't': position = "0"; break;
282                         case 'c': position = "1"; break;
283                         case 'b': position = "2"; break;
284                         default:
285                                 cerr << "invalid position for minipage"
286                                      << endl;
287                                 break;
288                         }
289                         if (p.next_token().asInput() == "[") {
290                                 latex_height = p.getArg('[', ']');
291                                 height = translate_len(latex_height);
292
293                                 if (p.next_token().asInput() == "[") {
294                                         latex_inner_pos = p.getArg('[', ']');
295                                         switch(latex_inner_pos[0]) {
296                                         case 't': inner_pos = "0"; break;
297                                         case 'c': inner_pos = "1"; break;
298                                         case 'b': inner_pos = "2"; break;
299                                         case 's': inner_pos = "3"; break;
300                                         default:
301                                                 cerr << "invalid inner_pos for minipage"
302                                                      << endl;
303                                                 break;
304                                         }
305                                 }
306                         }
307                 }
308                 string width = translate_len(p.verbatim_item());
309                 if (width[0] == '\\') {
310                         // lyx can't handle length variables
311                         ostringstream ss;
312                         ss << "\\begin{minipage}";
313                         if (latex_position.size())
314                                 ss << '[' << latex_position << ']';
315                         if (latex_height.size())
316                                 ss << '[' << latex_height << ']';
317                         if (latex_inner_pos.size())
318                                 ss << '[' << latex_inner_pos << ']';
319                         ss << "{" << width << "}";
320                         handle_ert(os, ss.str(), parent_context);
321                         parent_context.check_end_layout(os);
322                         parent_context.need_layout = true;
323                         parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
324                         handle_ert(os, "\\end{minipage}", parent_context);
325                 } else {
326                         begin_inset(os, "Minipage\n");
327                         os << "position " << position << '\n';
328                         os << "inner_position " << inner_pos << '\n';
329                         os << "height \"" << height << "\"\n";
330                         os << "width \"" << width << "\"\n";
331                         os << "collapsed false\n\n";
332                         parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
333                         end_inset(os);
334                 }
335
336         }
337
338         else if (name == "center") {
339                 parse_text(p, os, FLAG_END, outer, parent_context);
340         }
341
342         // The single '=' is meant here.
343         else if ((newlayout = findLayout(parent_context.textclass, name)).get() &&
344                    newlayout->isEnvironment()) {
345                 Context context(true, parent_context.textclass, newlayout,
346                                 parent_context.layout);
347                 parent_context.check_end_layout(os);
348                 switch (context.layout->latextype) {
349                 case  LATEX_LIST_ENVIRONMENT:
350                         context.extra_stuff = "\\labelwidthstring "
351                                 + p.verbatim_item() + '\n';
352                         break;
353                 case  LATEX_BIB_ENVIRONMENT:
354                         p.verbatim_item(); // swallow next arg
355                         break;
356                 default:
357                         break;
358                 }
359                 context.check_deeper(os);
360                 parse_text(p, os, FLAG_END, outer, context);
361                 context.check_end_layout(os);
362                 context.check_end_deeper(os);
363         }
364
365         else if (name == "appendix") {
366                 // This is no good latex style, but it works and is used in some documents...
367                 parent_context.check_end_layout(os);
368                 Context context(true, parent_context.textclass, parent_context.layout,
369                                 parent_context.layout);
370                 context.check_layout(os);
371                 os << "\\start_of_appendix\n";
372                 parse_text(p, os, FLAG_END, outer, context);
373                 context.check_end_layout(os);
374         }
375
376         else if (name == "comment") {
377                 parent_context.check_layout(os);
378                 begin_inset(os, "Comment\n");
379                 os << "collapsed false\n";
380                 parse_text_in_inset(p, os, FLAG_END, outer, parent_context);
381                 end_inset(os);
382         }
383
384         else if (name == "tabbing") {
385                 // We need to remember that we have to handle '\=' specially
386                 parent_context.check_layout(os);
387                 handle_ert(os, "\\begin{" + name + "}", parent_context);
388                 parse_text_snippet(p, os, FLAG_END | FLAG_TABBING, outer, parent_context);
389                 handle_ert(os, "\\end{" + name + "}", parent_context);
390         }
391
392         else {
393                 parent_context.check_layout(os);
394                 handle_ert(os, "\\begin{" + name + "}", parent_context);
395                 parse_text_snippet(p, os, FLAG_END, outer, parent_context);
396                 handle_ert(os, "\\end{" + name + "}", parent_context);
397         }
398         active_environments.pop_back();
399 }
400
401 } // anonymous namespace
402
403
404
405
406 void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
407                 Context & context)
408 {
409         LyXLayout_ptr newlayout;
410         // Store the latest bibliographystyle (needed for bibtex inset)
411         string bibliographystyle;
412         while (p.good()) {
413                 Token const & t = p.get_token();
414
415 #ifdef FILEDEBUG
416                 cerr << "t: " << t << " flags: " << flags << "\n";
417 #endif
418
419                 if (flags & FLAG_ITEM) {
420                         if (t.cat() == catSpace)
421                                 continue;
422
423                         flags &= ~FLAG_ITEM;
424                         if (t.cat() == catBegin) {
425                                 // skip the brace and collect everything to the next matching
426                                 // closing brace
427                                 flags |= FLAG_BRACE_LAST;
428                                 continue;
429                         }
430
431                         // handle only this single token, leave the loop if done
432                         flags |= FLAG_LEAVE;
433                 }
434
435                 if (t.character() == ']' && (flags & FLAG_BRACK_LAST))
436                         return;
437
438                 //
439                 // cat codes
440                 //
441                 if (t.cat() == catMath) {
442                         // we are inside some text mode thingy, so opening new math is allowed
443                         context.check_layout(os);
444                         begin_inset(os, "Formula ");
445                         Token const & n = p.get_token();
446                         if (n.cat() == catMath && outer) {
447                                 // TeX's $$...$$ syntax for displayed math
448                                 os << "\\[";
449                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
450                                 os << "\\]";
451                                 p.get_token(); // skip the second '$' token
452                         } else {
453                                 // simple $...$  stuff
454                                 p.putback();
455                                 os << '$';
456                                 parse_math(p, os, FLAG_SIMPLE, MATH_MODE);
457                                 os << '$';
458                         }
459                         end_inset(os);
460                 }
461
462                 else if (t.cat() == catSuper || t.cat() == catSub)
463                         cerr << "catcode " << t << " illegal in text mode\n";
464
465                 // Basic support for english quotes. This should be
466                 // extended to other quotes, but is not so easy (a
467                 // left english quote is the same as a right german
468                 // quote...)
469                 else if (t.asInput() == "`"
470                          && p.next_token().asInput() == "`") {
471                         context.check_layout(os);
472                         begin_inset(os, "Quotes ");
473                         os << "eld";
474                         end_inset(os);
475                         p.get_token();
476                         skip_braces(p);
477                 }
478                 else if (t.asInput() == "'"
479                          && p.next_token().asInput() == "'") {
480                         context.check_layout(os);
481                         begin_inset(os, "Quotes ");
482                         os << "erd";
483                         end_inset(os);
484                         p.get_token();
485                         skip_braces(p);
486                 }
487
488
489                 else if (t.cat() == catLetter ||
490                                t.cat() == catSpace ||
491                                t.cat() == catOther ||
492                                t.cat() == catAlign ||
493                                t.cat() == catParameter) {
494                         context.check_layout(os);
495                         os << t.character();
496                 }
497
498                 else if (t.cat() == catNewline) {
499                         if (p.next_token().cat() == catNewline) {
500                                 // this should have been be done by
501                                 // the parser already
502                                 cerr << "what are we doing here?" << endl;
503                                 p.get_token();
504                                 context.need_layout = true;
505                         } else {
506                                 os << " "; // note the space
507                         }
508                 }
509
510                 else if (t.cat() == catActive) {
511                         context.check_layout(os);
512                         if (t.character() == '~') {
513                                 if (context.layout->free_spacing)
514                                         os << ' ';
515                                 else
516                                         os << "\\InsetSpace ~\n";
517                         } else
518                                 os << t.character();
519                 }
520
521                 else if (t.cat() == catBegin) {
522 // FIXME???
523                         // special handling of size changes
524                         context.check_layout(os);
525                         bool const is_size = is_known(p.next_token().cs(), known_sizes);
526                         Context newcontext(false, context.textclass);
527 //                      need_end_layout = false;
528                         string const s = parse_text(p, FLAG_BRACE_LAST, outer, newcontext);
529 //                      need_end_layout = true;
530                         if (s.empty() && p.next_token().character() == '`')
531                                 ; // ignore it in  {}``
532                         else if (is_size || s == "[" || s == "]" || s == "*")
533                                 os << s;
534                         else {
535                                 handle_ert(os, "{", context);
536                                 os << s;
537                                 handle_ert(os, "}", context);
538                         }
539                 }
540
541                 else if (t.cat() == catEnd) {
542                         if (flags & FLAG_BRACE_LAST) {
543                                 context.check_end_layout(os);
544                                 return;
545                         }
546                         cerr << "stray '}' in text\n";
547                         handle_ert(os, "}", context);
548                 }
549
550                 else if (t.cat() == catComment)
551                         handle_comment(p);
552
553                 //
554                 // control sequences
555                 //
556
557                 else if (t.cs() == "(") {
558                         context.check_layout(os);
559                         begin_inset(os, "Formula");
560                         os << " \\(";
561                         parse_math(p, os, FLAG_SIMPLE2, MATH_MODE);
562                         os << "\\)";
563                         end_inset(os);
564                 }
565
566                 else if (t.cs() == "[") {
567                         context.check_layout(os);
568                         begin_inset(os, "Formula");
569                         os << " \\[";
570                         parse_math(p, os, FLAG_EQUATION, MATH_MODE);
571                         os << "\\]";
572                         end_inset(os);
573                 }
574
575                 else if (t.cs() == "begin")
576                         parse_environment(p, os, outer, context);
577
578                 else if (t.cs() == "end") {
579                         if (flags & FLAG_END) {
580                                 // eat environment name
581                                 string const name = p.getArg('{', '}');
582                                 if (name != active_environment())
583                                         cerr << "\\end{" + name + "} does not match \\begin{"
584                                                 + active_environment() + "}\n";
585                                 return;
586                         }
587                         p.error("found 'end' unexpectedly");
588                 }
589
590                 else if (t.cs() == "item") {
591                         // should be done automatically by Parser::tokenize
592                         //p.skip_spaces();
593                         string s;
594                         bool optarg = false;
595                         if (p.next_token().character() == '[') {
596                                 p.get_token(); // eat '['
597                                 Context newcontext(false, context.textclass);
598                                 s = parse_text(p, FLAG_BRACK_LAST, outer, newcontext);
599                                 optarg = true;
600                         }
601                         context.need_layout = true;
602                         context.has_item = true;
603                         context.check_layout(os);
604                         if (optarg) {
605                                 if (active_environment() == "itemize") {
606                                         // lyx does not support \item[\mybullet] in itemize environments
607                                         handle_ert(os, "[", context);
608                                         os << s;
609                                         handle_ert(os, "]", context);
610                                 } else if (s.size()) {
611                                         // The space is needed to separate the item from the rest of the sentence.
612                                         os << s << ' ';
613                                 }
614                         }
615                 }
616
617                 else if (t.cs() == "bibitem") {
618                         context.need_layout = true;
619                         context.has_item = true;
620                         context.check_layout(os);
621                         os << "\\bibitem ";
622                         os << p.getOpt();
623                         os << '{' << p.verbatim_item() << '}' << "\n";
624                 }
625
626                 else if (t.cs() == "def") {
627                         context.check_layout(os);
628                         string name = p.get_token().cs();
629                         while (p.next_token().cat() != catBegin)
630                                 name += p.get_token().asString();
631                         handle_ert(os, "\\def\\" + name + '{' + p.verbatim_item() + '}', context);
632                 }
633
634                 else if (t.cs() == "par") {
635                         p.skip_spaces();
636                         context.check_end_layout(os);
637                         context.need_layout = true;
638                 }
639
640                 else if (t.cs() == "appendix") {
641                         context.check_end_layout(os);
642                         Context newcontext(true, context.textclass, context.layout,
643                                         context.layout);
644                         newcontext.check_layout(os);
645                         os << "\\start_of_appendix\n";
646                         parse_text(p, os, FLAG_END, outer, newcontext);
647                         newcontext.check_end_layout(os);
648                 }
649
650                 // Must attempt to parse "Section*" before "Section".
651                 else if ((p.next_token().asInput() == "*") &&
652                          // The single '=' is meant here.
653                          (newlayout = findLayout(context.textclass,
654                                                  t.cs() + '*')).get() &&
655                          newlayout->isCommand()) {
656                         p.get_token();
657                         output_command_layout(os, p, outer, context, newlayout);
658                 }
659
660                 // The single '=' is meant here.
661                 else if ((newlayout = findLayout(context.textclass, t.cs())).get() &&
662                          newlayout->isCommand()) {
663                         output_command_layout(os, p, outer, context, newlayout);
664                 }
665
666                 else if (t.cs() == "includegraphics") {
667                         map<string, string> opts = split_map(p.getArg('[', ']'));
668                         string name = p.verbatim_item();
669
670                         context.check_layout(os);
671                         begin_inset(os, "Graphics ");
672                         os << "\n\tfilename " << name << '\n';
673                         if (opts.find("width") != opts.end())
674                                 os << "\twidth "
675                                    << translate_len(opts["width"]) << '\n';
676                         if (opts.find("height") != opts.end())
677                                 os << "\theight "
678                                    << translate_len(opts["height"]) << '\n';
679                         if (opts.find("scale") != opts.end()) {
680                                 istringstream iss(opts["scale"]);
681                                 double val;
682                                 iss >> val;
683                                 val = val*100;
684                                 os << "\tscale " << val << '\n';
685                         }
686                         if (opts.find("angle") != opts.end())
687                                 os << "\trotateAngle "
688                                    << opts["angle"] << '\n';
689                         if (opts.find("origin") != opts.end()) {
690                                 ostringstream ss;
691                                 string const opt = opts["origin"];
692                                 if (opt.find('l') != string::npos) ss << "left";
693                                 if (opt.find('r') != string::npos) ss << "right";
694                                 if (opt.find('c') != string::npos) ss << "center";
695                                 if (opt.find('t') != string::npos) ss << "Top";
696                                 if (opt.find('b') != string::npos) ss << "Bottom";
697                                 if (opt.find('B') != string::npos) ss << "Baseline";
698                                 if (ss.str().size())
699                                         os << "\trotateOrigin " << ss.str() << '\n';
700                                 else
701                                         cerr << "Warning: Ignoring unknown includegraphics origin argument '" << opt << "'\n";
702                         }
703                         if (opts.find("keepaspectratio") != opts.end())
704                                 os << "\tkeepAspectRatio\n";
705                         if (opts.find("clip") != opts.end())
706                                 os << "\tclip\n";
707                         if (opts.find("draft") != opts.end())
708                                 os << "\tdraft\n";
709                         if (opts.find("bb") != opts.end())
710                                 os << "\tBoundingBox "
711                                    << opts["bb"] << '\n';
712                         int numberOfbbOptions = 0;
713                         if (opts.find("bbllx") != opts.end())
714                                 numberOfbbOptions++;
715                         if (opts.find("bblly") != opts.end())
716                                 numberOfbbOptions++;
717                         if (opts.find("bburx") != opts.end())
718                                 numberOfbbOptions++;
719                         if (opts.find("bbury") != opts.end())
720                                 numberOfbbOptions++;
721                         if (numberOfbbOptions == 4)
722                                 os << "\tBoundingBox "
723                                    << opts["bbllx"] << opts["bblly"]
724                                    << opts["bburx"] << opts["bbury"] << '\n';
725                         else if (numberOfbbOptions > 0)
726                                 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
727                         numberOfbbOptions = 0;
728                         if (opts.find("natwidth") != opts.end())
729                                 numberOfbbOptions++;
730                         if (opts.find("natheight") != opts.end())
731                                 numberOfbbOptions++;
732                         if (numberOfbbOptions == 2)
733                                 os << "\tBoundingBox 0bp 0bp "
734                                    << opts["natwidth"] << opts["natheight"] << '\n';
735                         else if (numberOfbbOptions > 0)
736                                 cerr << "Warning: Ignoring incomplete includegraphics boundingbox arguments.\n";
737                         ostringstream special;
738                         if (opts.find("hiresbb") != opts.end())
739                                 special << "hiresbb,";
740                         if (opts.find("trim") != opts.end())
741                                 special << "trim,";
742                         if (opts.find("viewport") != opts.end())
743                                 special << "viewport=" << opts["viewport"] << ',';
744                         if (opts.find("totalheight") != opts.end())
745                                 special << "totalheight=" << opts["totalheight"] << ',';
746                         if (opts.find("type") != opts.end())
747                                 special << "type=" << opts["type"] << ',';
748                         if (opts.find("ext") != opts.end())
749                                 special << "ext=" << opts["ext"] << ',';
750                         if (opts.find("read") != opts.end())
751                                 special << "read=" << opts["read"] << ',';
752                         if (opts.find("command") != opts.end())
753                                 special << "command=" << opts["command"] << ',';
754                         string s_special = special.str();
755                         if (s_special.size()) {
756                                 // We had special arguments. Remove the trailing ','.
757                                 os << "\tspecial " << s_special.substr(0, s_special.size() - 1) << '\n';
758                         }
759                         // TODO: Handle the unknown settings better.
760                         // Warn about invalid options.
761                         // Check wether some option was given twice.
762                         end_inset(os);
763                 }
764
765                 else if (t.cs() == "footnote") {
766                         context.check_layout(os);
767                         begin_inset(os, "Foot\n");
768                         os << "collapsed true\n";
769                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
770                         end_inset(os);
771                 }
772
773                 else if (t.cs() == "marginpar") {
774                         context.check_layout(os);
775                         begin_inset(os, "Marginal\n");
776                         os << "collapsed true\n";
777                         parse_text_in_inset(p, os, FLAG_ITEM, false, context);
778                         end_inset(os);
779                 }
780
781                 else if (t.cs() == "ensuremath") {
782                         context.check_layout(os);
783                         Context newcontext(false, context.textclass);
784                         string s = parse_text(p, FLAG_ITEM, false, newcontext);
785                         if (s == "±" || s == "³" || s == "²" || s == "µ")
786                                 os << s;
787                         else
788                                 handle_ert(os, "\\ensuremath{" + s + "}",
789                                            context);
790                 }
791
792                 else if (t.cs() == "hfill") {
793                         context.check_layout(os);
794                         os << "\n\\hfill\n";
795                         skip_braces(p);
796                 }
797
798                 else if (t.cs() == "makeindex" || t.cs() == "maketitle")
799                         skip_braces(p); // swallow this
800
801                 else if (t.cs() == "tableofcontents") {
802                         context.check_layout(os);
803                         begin_inset(os, "LatexCommand \\tableofcontents\n");
804                         end_inset(os);
805                         skip_braces(p); // swallow this
806                 }
807
808                 else if (t.cs() == "listoffigures") {
809                         context.check_layout(os);
810                         begin_inset(os, "FloatList figure\n");
811                         end_inset(os);
812                         skip_braces(p); // swallow this
813                 }
814
815                 else if (t.cs() == "listoftables") {
816                         context.check_layout(os);
817                         begin_inset(os, "FloatList table\n");
818                         end_inset(os);
819                         skip_braces(p); // swallow this
820                 }
821
822                 else if (t.cs() == "listof") {
823                         string const name = p.get_token().asString();
824                         if (context.textclass.floats().typeExist(name)) {
825                                 context.check_layout(os);
826                                 begin_inset(os, "FloatList ");
827                                 os << name << "\n";
828                                 end_inset(os);
829                                 p.get_token(); // swallow second arg
830                         } else
831                                 handle_ert(os, "\\listof{" + name + "}", context);
832                 }
833
834                 else if (t.cs() == "textrm") {
835                         context.check_layout(os);
836                         os << "\n\\family roman \n";
837                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
838                         os << "\n\\family default \n";
839                 }
840
841                 else if (t.cs() == "textsf") {
842                         context.check_layout(os);
843                         os << "\n\\family sans \n";
844                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
845                         os << "\n\\family default \n";
846                 }
847
848                 else if (t.cs() == "textsl") {
849                         context.check_layout(os);
850                         os << "\n\\shape slanted \n";
851                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
852                         os << "\n\\shape default \n";
853                 }
854
855                 else if (t.cs() == "texttt") {
856                         context.check_layout(os);
857                         os << "\n\\family typewriter \n";
858                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
859                         os << "\n\\family default \n";
860                 }
861
862                 else if (t.cs() == "textit") {
863                         context.check_layout(os);
864                         os << "\n\\shape italic \n";
865                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
866                         os << "\n\\shape default \n";
867                 }
868
869                 else if (t.cs() == "textsc") {
870                         context.check_layout(os);
871                         os << "\n\\noun on \n";
872                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
873                         os << "\n\\noun default \n";
874                 }
875
876                 else if (t.cs() == "textbf") {
877                         context.check_layout(os);
878                         os << "\n\\series bold \n";
879                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
880                         os << "\n\\series default \n";
881                 }
882
883                 else if (t.cs() == "underbar") {
884                         context.check_layout(os);
885                         os << "\n\\bar under \n";
886                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
887                         os << "\n\\bar default \n";
888                 }
889
890                 else if (t.cs() == "emph" || t.cs() == "noun") {
891                         context.check_layout(os);
892                         os << "\n\\" << t.cs() << " on \n";
893                         parse_text_snippet(p, os, FLAG_ITEM, outer, context);
894                         os << "\n\\" << t.cs() << " default \n";
895                 }
896
897                 else if (is_known(t.cs(), known_latex_commands)) {
898                         context.check_layout(os);
899                         begin_inset(os, "LatexCommand ");
900                         os << '\\' << t.cs();
901                         os << p.getOpt();
902                         os << p.getOpt();
903                         os << '{' << p.verbatim_item() << "}\n";
904                         end_inset(os);
905                 }
906
907                 else if (is_known(t.cs(), known_quotes)) {
908                         char const ** where = is_known(t.cs(), known_quotes);
909                         begin_inset(os, "Quotes ");
910                         os << known_coded_quotes[where - known_quotes];
911                         end_inset(os);
912                         skip_braces(p);
913                 }
914
915                 else if (is_known(t.cs(), known_sizes)) {
916                         char const ** where = is_known(t.cs(), known_sizes);
917                         context.check_layout(os);
918                         os << "\n\\size " << known_coded_sizes[where - known_sizes] << "\n";
919                 }
920
921                 else if (t.cs() == "LyX" || t.cs() == "TeX"
922                          || t.cs() == "LaTeX") {
923                         context.check_layout(os);
924                         os << t.cs();
925                         skip_braces(p); // eat {}
926                 }
927
928                 else if (t.cs() == "LaTeXe") {
929                         context.check_layout(os);
930                         os << "LaTeX2e";
931                         skip_braces(p); // eat {}
932                 }
933
934                 else if (t.cs() == "ldots") {
935                         context.check_layout(os);
936                         skip_braces(p);
937                         os << "\\SpecialChar \\ldots{}\n";
938                 }
939
940                 else if (t.cs() == "lyxarrow") {
941                         context.check_layout(os);
942                         os << "\\SpecialChar \\menuseparator\n";
943                         skip_braces(p);
944                 }
945
946                 else if (t.cs() == "textcompwordmark") {
947                         context.check_layout(os);
948                         os << "\\SpecialChar \\textcompwordmark{}\n";
949                         skip_braces(p);
950                 }
951
952                 else if (t.cs() == "@" && p.next_token().asInput() == ".") {
953                         context.check_layout(os);
954                         os << "\\SpecialChar \\@.\n";
955                         p.get_token();
956                 }
957
958                 else if (t.cs() == "-") {
959                         context.check_layout(os);
960                         os << "\\SpecialChar \\-\n";
961                 }
962
963                 else if (t.cs() == "textasciitilde") {
964                         context.check_layout(os);
965                         os << '~';
966                         skip_braces(p);
967                 }
968
969                 else if (t.cs() == "textasciicircum") {
970                         context.check_layout(os);
971                         os << '^';
972                         skip_braces(p);
973                 }
974
975                 else if (t.cs() == "textbackslash") {
976                         context.check_layout(os);
977                         os << "\n\\backslash \n";
978                         skip_braces(p);
979                 }
980
981                 else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
982                             || t.cs() == "$" || t.cs() == "{" || t.cs() == "}"
983                             || t.cs() == "%") {
984                         context.check_layout(os);
985                         os << t.cs();
986                 }
987
988                 else if (t.cs() == "char") {
989                         context.check_layout(os);
990                         if (p.next_token().character() == '`') {
991                                 p.get_token();
992                                 if (p.next_token().cs() == "\"") {
993                                         p.get_token();
994                                         os << '"';
995                                         skip_braces(p);
996                                 } else {
997                                         handle_ert(os, "\\char`", context);
998                                 }
999                         } else {
1000                                 handle_ert(os, "\\char", context);
1001                         }
1002                 }
1003
1004                 else if (t.cs() == "\"") {
1005                         context.check_layout(os);
1006                         string const name = p.verbatim_item();
1007                              if (name == "a") os << 'ä';
1008                         else if (name == "o") os << 'ö';
1009                         else if (name == "u") os << 'ü';
1010                         else if (name == "A") os << 'Ä';
1011                         else if (name == "O") os << 'Ö';
1012                         else if (name == "U") os << 'Ü';
1013                         else handle_ert(os, "\"{" + name + "}", context);
1014                 }
1015
1016                 // Problem: \= creates a tabstop inside the tabbing environment
1017                 // and else an accent. In the latter case we really would want
1018                 // \={o} instead of \= o.
1019                 else if (t.cs() == "H" || t.cs() == "c" || t.cs() == "^" || t.cs() == "'"
1020                       || t.cs() == "~" || t.cs() == "." || (t.cs() == "=" && ! (flags & FLAG_TABBING))) {
1021                         // we need the trim as the LyX parser chokes on such spaces
1022                         context.check_layout(os);
1023                         os << "\n\\i \\" << t.cs() << "{"
1024                            << trim(parse_text(p, FLAG_ITEM, outer, context), " ") << "}\n";
1025                 }
1026
1027                 else if (t.cs() == "ss") {
1028                         context.check_layout(os);
1029                         os << "ß";
1030                 }
1031
1032                 else if (t.cs() == "i" || t.cs() == "j") {
1033                         context.check_layout(os);
1034                         os << "\\" << t.cs() << ' ';
1035                 }
1036
1037                 else if (t.cs() == "\\") {
1038                         context.check_layout(os);
1039                         string const next = p.next_token().asInput();
1040                         if (next == "[")
1041                                 handle_ert(os, "\\\\" + p.getOpt(), context);
1042                         else if (next == "*") {
1043                                 p.get_token();
1044                                 handle_ert(os, "\\\\*" + p.getOpt(), context);
1045                         }
1046                         else {
1047                                 os << "\n\\newline \n";
1048                         }
1049                 }
1050
1051                 else if (t.cs() == "input" || t.cs() == "include"
1052                          || t.cs() == "verbatiminput") {
1053                         string name = '\\' + t.cs();
1054                         if (t.cs() == "verbatiminput"
1055                             && p.next_token().asInput() == "*")
1056                                 name += p.get_token().asInput();
1057                         context.check_layout(os);
1058                         begin_inset(os, "Include ");
1059                         string filename(p.getArg('{', '}'));
1060                         string lyxname(lyx::support::ChangeExtension(filename, ".lyx"));
1061                         if (tex2lyx(filename, lyxname)) {
1062                                 os << name << '{' << lyxname << "}\n";
1063                         } else {
1064                                 os << name << '{' << filename << "}\n";
1065                         }
1066                         os << "preview false\n";
1067                         end_inset(os);
1068                 }
1069
1070                 else if (t.cs() == "fancyhead") {
1071                         context.check_layout(os);
1072                         ostringstream ss;
1073                         ss << "\\fancyhead";
1074                         ss << p.getOpt();
1075                         ss << '{' << p.verbatim_item() << "}\n";
1076                         handle_ert(os, ss.str(), context);
1077                 }
1078
1079                 else if (t.cs() == "bibliographystyle") {
1080                         // store new bibliographystyle
1081                         bibliographystyle = p.verbatim_item();
1082                         // output new bibliographystyle.
1083                         // This is only necessary if used in some other macro than \bibliography.
1084                         handle_ert(os, "\\bibliographystyle{" + bibliographystyle + "}", context);
1085                 }
1086
1087                 else if (t.cs() == "bibliography") {
1088                         context.check_layout(os);
1089                         begin_inset(os, "LatexCommand ");
1090                         os << "\\bibtex";
1091                         // Do we have a bibliographystyle set?
1092                         if (bibliographystyle.size()) {
1093                                 os << '[' << bibliographystyle << ']';
1094                         }
1095                         os << '{' << p.verbatim_item() << "}\n";
1096                         end_inset(os);
1097                 }
1098
1099                 else if (t.cs() == "psfrag") {
1100                         // psfrag{ps-text}[ps-pos][tex-pos]{tex-text}
1101                         // TODO: Generalize this!
1102                         string arguments = p.getArg('{', '}');
1103                         arguments += '}';
1104                         arguments += p.getOpt();
1105                         arguments += p.getOpt();
1106                         p.skip_spaces();
1107                         handle_ert(os, "\\psfrag{" + arguments, context);
1108                 }
1109
1110                 else {
1111                         //cerr << "#: " << t << " mode: " << mode << endl;
1112                         // heuristic: read up to next non-nested space
1113                         /*
1114                         string s = t.asInput();
1115                         string z = p.verbatim_item();
1116                         while (p.good() && z != " " && z.size()) {
1117                                 //cerr << "read: " << z << endl;
1118                                 s += z;
1119                                 z = p.verbatim_item();
1120                         }
1121                         cerr << "found ERT: " << s << endl;
1122                         handle_ert(os, s + ' ', context);
1123                         */
1124                         context.check_layout(os);
1125                         handle_ert(os, t.asInput() + ' ', context);
1126                 }
1127
1128                 if (flags & FLAG_LEAVE) {
1129                         flags &= ~FLAG_LEAVE;
1130                         break;
1131                 }
1132         }
1133 }
1134
1135
1136 // }])