]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/preamble.cpp
tex2lyx:
[lyx.git] / src / tex2lyx / preamble.cpp
1 /**
2  * \file preamble.cpp
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 Uwe Stöhr
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
18 #include "LayoutFile.h"
19 #include "Layout.h"
20 #include "Lexer.h"
21 #include "TextClass.h"
22
23 #include "support/convert.h"
24 #include "support/FileName.h"
25 #include "support/filetools.h"
26 #include "support/lstrings.h"
27
28 #include "support/regex.h"
29
30 #include <algorithm>
31 #include <iostream>
32 #include <sstream>
33 #include <string>
34 #include <vector>
35 #include <map>
36
37 using namespace std;
38 using namespace lyx::support;
39
40
41 namespace lyx {
42
43 // special columntypes
44 extern map<char, int> special_columns;
45
46 map<string, vector<string> > used_packages;
47 const char * const modules_placeholder = "\001modules\001";
48
49 // needed to handle encodings with babel
50 bool one_language = true;
51 string h_inputencoding = "auto";
52 string h_paragraph_separation    = "indent";
53
54 namespace {
55
56 //add this to known_languages when updating to lyxformat 266:
57 // "armenian" (needs special handling since not supported by standard babel)
58 //add these to known_languages when updating to lyxformat 268:
59 //"chinese-simplified", "chinese-traditional", "japanese", "korean"
60 // Both changes require first that support for non-babel languages (CJK,
61 // armtex) is added.
62 // add turkmen for lyxformat 383
63 /**
64  * known babel language names (including synonyms)
65  * not in standard babel: arabic, arabtex, armenian, belarusian, serbian-latin, thai
66  * not yet supported by LyX: kurmanji
67  * please keep this in sync with known_coded_languages line by line!
68  */
69 const char * const known_languages[] = {"acadian", "afrikaans", "albanian",
70 "american", "arabic", "arabtex", "austrian", "bahasa", "bahasai", "bahasam",
71 "basque", "belarusian", "brazil", "brazilian", "breton", "british", "bulgarian",
72 "canadian", "canadien", "catalan", "croatian", "czech", "danish", "dutch",
73 "english", "esperanto", "estonian", "farsi", "finnish", "francais", "french",
74 "frenchb", "frenchle", "frenchpro", "galician", "german", "germanb", "greek",
75 "hebrew", "hungarian", "icelandic", "indon", "indonesian", "interlingua",
76 "irish", "italian", "kazakh", "latin", "latvian", "lithuanian", "lowersorbian",
77 "lsorbian", "magyar", "malay", "meyalu", "mongolian", "naustrian", "newzealand",
78 "ngerman", "ngermanb", "norsk", "nynorsk", "polutonikogreek", "polish",
79 "portuges", "portuguese", "romanian", "russian", "russianb", "samin",
80 "scottish", "serbian", "serbian-latin", "slovak", "slovene", "spanish",
81 "swedish", "thai", "turkish", "turkmen", "ukraineb", "ukrainian",
82 "uppersorbian", "UKenglish", "USenglish", "usorbian", "vietnam", "welsh",
83 0};
84
85 /**
86  * the same as known_languages with .lyx names
87  * please keep this in sync with known_languages line by line!
88  */
89 const char * const known_coded_languages[] = {"french", "afrikaans", "albanian",
90 "american", "arabic_arabi", "arabic_arabtex", "austrian", "bahasa", "bahasa", "bahasam",
91 "basque", "belarusian", "brazilian", "brazilian", "breton", "british", "bulgarian",
92 "canadian", "canadien", "catalan", "croatian", "czech", "danish", "dutch",
93 "english", "esperanto", "estonian", "farsi", "finnish", "french", "french",
94 "french", "french", "french", "galician", "german", "german", "greek",
95 "hebrew", "magyar", "icelandic", "bahasa", "bahasa", "interlingua",
96 "irish", "italian", "kazakh", "latin", "latvian", "lithuanian", "lowersorbian",
97 "lowersorbian", "magyar", "bahasam", "bahasam", "mongolian", "naustrian", "english",
98 "ngerman", "ngerman", "norsk", "nynorsk", "polutonikogreek", "polish",
99 "portuguese", "portuguese", "romanian", "russian", "russian", "samin",
100 "scottish", "serbian", "serbian-latin", "slovak", "slovene", "spanish",
101 "swedish", "thai", "turkish", "turkmen", "ukrainian", "ukrainian",
102 "uppersorbian", "uppersorbian", "english", "english", "vietnamese", "welsh",
103 0};
104
105 /// languages with english quotes (.lyx names)
106 const char * const known_english_quotes_languages[] = {"american", "bahasa",
107 "bahasam", "brazilian", "canadian", "chinese-simplified", "english",
108 "esperanto", "hebrew", "irish", "korean", "portuguese", "scottish", "thai", 0};
109
110 /// languages with french quotes (.lyx names)
111 const char * const known_french_quotes_languages[] = {"albanian",
112 "arabic_arabi", "arabic_arabtex", "basque", "canadien", "catalan", "french",
113 "galician", "greek", "italian", "norsk", "nynorsk", "polutonikogreek",
114 "russian", "spanish", "spanish-mexico", "turkish", "turkmen", "ukrainian",
115 "vietnamese", 0};
116
117 /// languages with german quotes (.lyx names)
118 const char * const known_german_quotes_languages[] = {"austrian", "bulgarian",
119 "czech", "german", "icelandic", "lithuanian", "lowersorbian", "naustrian",
120 "ngerman", "serbian", "serbian-latin", "slovak", "slovene", "uppersorbian", 0};
121
122 /// languages with polish quotes (.lyx names)
123 const char * const known_polish_quotes_languages[] = {"afrikaans", "croatian",
124 "dutch", "estonian", "magyar", "polish", "romanian", 0};
125
126 /// languages with swedish quotes (.lyx names)
127 const char * const known_swedish_quotes_languages[] = {"finnish",
128 "swedish", 0};
129
130 char const * const known_fontsizes[] = { "10pt", "11pt", "12pt", 0 };
131
132 const char * const known_roman_fonts[] = { "ae", "bookman", "charter",
133 "cmr", "fourier", "lmodern", "mathpazo", "mathptmx", "newcent", 0};
134
135 const char * const known_sans_fonts[] = { "avant", "berasans", "cmbr", "cmss",
136 "helvet", "lmss", 0};
137
138 const char * const known_typewriter_fonts[] = { "beramono", "cmtl", "cmtt",
139 "courier", "lmtt", "luximono", "fourier", "lmodern", "mathpazo", "mathptmx",
140 "newcent", 0};
141
142 const char * const known_paper_sizes[] = { "a0paper", "b0paper", "c0paper",
143 "a1paper", "b1paper", "c1paper", "a2paper", "b2paper", "c2paper", "a3paper",
144 "b3paper", "c3paper", "a4paper", "b4paper", "c4paper", "a5paper", "b5paper",
145 "c5paper", "a6paper", "b6paper", "c6paper", "executivepaper", "legalpaper",
146 "letterpaper", "b0j", "b1j", "b2j", "b3j", "b4j", "b5j", "b6j", 0};
147
148 const char * const known_class_paper_sizes[] = { "a4paper", "a5paper",
149 "executivepaper", "legalpaper", "letterpaper", 0};
150
151 const char * const known_paper_margins[] = { "lmargin", "tmargin", "rmargin", 
152 "bmargin", "headheight", "headsep", "footskip", "columnsep", 0};
153
154 const char * const known_coded_paper_margins[] = { "leftmargin", "topmargin",
155 "rightmargin", "bottommargin", "headheight", "headsep", "footskip",
156 "columnsep", 0};
157
158 /// commands that can start an \if...\else...\endif sequence
159 const char * const known_if_commands[] = {"if", "ifarydshln", "ifbraket",
160 "ifcancel", "ifcolortbl", "ifeurosym", "ifmarginnote", "ifmmode", "ifpdf",
161 "ifsidecap", "ifupgreek", 0};
162
163 /// conditional commands with three arguments like \@ifundefined{}{}{}
164 const char * const known_if_3arg_commands[] = {"@ifundefined", "IfFileExists",
165 0};
166
167 // default settings
168 ostringstream h_preamble;
169 string h_textclass               = "article";
170 string h_use_default_options     = "false";
171 string h_options;
172 string h_language                = "english";
173 string h_language_package        = "default";
174 string h_fontencoding            = "default";
175 string h_font_roman              = "default";
176 string h_font_sans               = "default";
177 string h_font_typewriter         = "default";
178 string h_font_default_family     = "default";
179 string h_font_sc                 = "false";
180 string h_font_osf                = "false";
181 string h_font_sf_scale           = "100";
182 string h_font_tt_scale           = "100";
183 string h_graphics                = "default";
184 string h_float_placement;
185 string h_paperfontsize           = "default";
186 string h_spacing                 = "single";
187 string h_use_hyperref            = "0";
188 string h_pdf_title;
189 string h_pdf_author;
190 string h_pdf_subject;
191 string h_pdf_keywords;
192 string h_pdf_bookmarks           = "1";
193 string h_pdf_bookmarksnumbered   = "0";
194 string h_pdf_bookmarksopen       = "0";
195 string h_pdf_bookmarksopenlevel  = "1";
196 string h_pdf_breaklinks          = "0";
197 string h_pdf_pdfborder           = "0";
198 string h_pdf_colorlinks          = "0";
199 string h_pdf_backref             = "section";
200 string h_pdf_pdfusetitle         = "1";
201 string h_pdf_pagemode;
202 string h_pdf_quoted_options;
203 string h_papersize               = "default";
204 string h_use_geometry            = "false";
205 string h_use_amsmath             = "1";
206 string h_use_esint               = "1";
207 string h_use_mhchem              = "0";
208 string h_use_mathdots            = "0";
209 string h_use_undertilde          = "0";
210 string h_cite_engine             = "basic";
211 string h_use_bibtopic            = "false";
212 string h_paperorientation        = "portrait";
213 string h_suppress_date           = "false";
214 string h_use_refstyle            = "0";
215 string h_backgroundcolor;
216 string h_boxbgcolor;
217 string h_fontcolor;
218 string h_notefontcolor;
219 string h_secnumdepth             = "3";
220 string h_tocdepth                = "3";
221 string h_defskip                 = "medskip";
222 string h_paragraph_indentation   = "default";
223 string h_quotes_language         = "english";
224 string h_papercolumns            = "1";
225 string h_papersides;
226 string h_paperpagestyle          = "default";
227 string h_listings_params;
228 string h_tracking_changes        = "false";
229 string h_output_changes          = "false";
230 string h_html_math_output        = "0";
231 string h_html_css_as_file        = "0";
232 string h_html_be_strict          = "false";
233 string h_margins;
234
235
236 // returns true if at least one of the options in what has been found
237 bool handle_opt(vector<string> & opts, char const * const * what, string & target)
238 {
239         if (opts.empty())
240                 return false;
241
242         bool found = false;
243         // the last language option is the document language (for babel and LyX)
244         // the last size option is the document font size
245         vector<string>::iterator it;
246         vector<string>::iterator position = opts.begin();
247         for (; *what; ++what) {
248                 it = find(opts.begin(), opts.end(), *what);
249                 if (it != opts.end()) {
250                         if (it >= position) {
251                                 found = true;
252                                 target = *what;
253                                 position = it;
254                         }
255                 }
256         }
257         return found;
258 }
259
260
261 void delete_opt(vector<string> & opts, char const * const * what)
262 {
263         if (opts.empty())
264                 return;
265
266         // remove found options from the list
267         // do this after handle_opt to avoid potential memory leaks
268         vector<string>::iterator it;
269         for (; *what; ++what) {
270                 it = find(opts.begin(), opts.end(), *what);
271                 if (it != opts.end())
272                         opts.erase(it);
273         }
274 }
275
276
277 /*!
278  * Split a package options string (keyval format) into a vector.
279  * Example input:
280  *   authorformat=smallcaps,
281  *   commabeforerest,
282  *   titleformat=colonsep,
283  *   bibformat={tabular,ibidem,numbered}
284  */
285 vector<string> split_options(string const & input)
286 {
287         vector<string> options;
288         string option;
289         Parser p(input);
290         while (p.good()) {
291                 Token const & t = p.get_token();
292                 if (t.asInput() == ",") {
293                         options.push_back(trimSpaceAndEol(option));
294                         option.erase();
295                 } else if (t.asInput() == "=") {
296                         option += '=';
297                         p.skip_spaces(true);
298                         if (p.next_token().asInput() == "{")
299                                 option += '{' + p.getArg('{', '}') + '}';
300                 } else if (t.cat() != catSpace)
301                         option += t.asInput();
302         }
303
304         if (!option.empty())
305                 options.push_back(trimSpaceAndEol(option));
306
307         return options;
308 }
309
310
311 /*!
312  * Retrieve a keyval option "name={value with=sign}" named \p name from
313  * \p options and return the value.
314  * The found option is also removed from \p options.
315  */
316 string process_keyval_opt(vector<string> & options, string name)
317 {
318         for (size_t i = 0; i < options.size(); ++i) {
319                 vector<string> option;
320                 split(options[i], option, '=');
321                 if (option.size() < 2)
322                         continue;
323                 if (option[0] == name) {
324                         options.erase(options.begin() + i);
325                         option.erase(option.begin());
326                         return join(option, "=");
327                 }
328         }
329         return "";
330 }
331
332
333 /*!
334  * Add package \p name with options \p options to used_packages.
335  * Remove options from \p options that we don't want to output.
336  */
337 void add_package(string const & name, vector<string> & options)
338 {
339         // every package inherits the global options
340         if (used_packages.find(name) == used_packages.end())
341                 used_packages[name] = split_options(h_options);
342
343         vector<string> & v = used_packages[name];
344         v.insert(v.end(), options.begin(), options.end());
345         if (name == "jurabib") {
346                 // Don't output the order argument (see the cite command
347                 // handling code in text.cpp).
348                 vector<string>::iterator end =
349                         remove(options.begin(), options.end(), "natbiborder");
350                 end = remove(options.begin(), end, "jurabiborder");
351                 options.erase(end, options.end());
352         }
353 }
354
355
356 // Given is a string like "scaled=0.9", return 0.9 * 100
357 string const scale_as_percentage(string const & scale)
358 {
359         string::size_type pos = scale.find('=');
360         if (pos != string::npos) {
361                 string value = scale.substr(pos + 1);
362                 if (isStrDbl(value))
363                         return convert<string>(100 * convert<double>(value));
364         }
365         // If the input string didn't match our expectations.
366         // return the default value "100"
367         return "100";
368 }
369
370
371 string remove_braces(string const & value)
372 {
373         if (value.empty())
374                 return value;
375         if (value[0] == '{' && value[value.length()-1] == '}')
376                 return value.substr(1, value.length()-2);
377         return value;
378 }
379
380
381 void handle_hyperref(vector<string> & options)
382 {
383         // FIXME swallow inputencoding changes that might surround the
384         //       hyperref setup if it was written by LyX
385         h_use_hyperref = "1";
386         // swallow "unicode=true", since LyX does always write that
387         vector<string>::iterator it =
388                 find(options.begin(), options.end(), "unicode=true");
389         if (it != options.end())
390                 options.erase(it);
391         it = find(options.begin(), options.end(), "pdfusetitle");
392         if (it != options.end()) {
393                 h_pdf_pdfusetitle = "1";
394                 options.erase(it);
395         }
396         string bookmarks = process_keyval_opt(options, "bookmarks");
397         if (bookmarks == "true")
398                 h_pdf_bookmarks = "1";
399         else if (bookmarks == "false")
400                 h_pdf_bookmarks = "0";
401         if (h_pdf_bookmarks == "1") {
402                 string bookmarksnumbered =
403                         process_keyval_opt(options, "bookmarksnumbered");
404                 if (bookmarksnumbered == "true")
405                         h_pdf_bookmarksnumbered = "1";
406                 else if (bookmarksnumbered == "false")
407                         h_pdf_bookmarksnumbered = "0";
408                 string bookmarksopen =
409                         process_keyval_opt(options, "bookmarksopen");
410                 if (bookmarksopen == "true")
411                         h_pdf_bookmarksopen = "1";
412                 else if (bookmarksopen == "false")
413                         h_pdf_bookmarksopen = "0";
414                 if (h_pdf_bookmarksopen == "1") {
415                         string bookmarksopenlevel =
416                                 process_keyval_opt(options, "bookmarksopenlevel");
417                         if (!bookmarksopenlevel.empty())
418                                 h_pdf_bookmarksopenlevel = bookmarksopenlevel;
419                 }
420         }
421         string breaklinks = process_keyval_opt(options, "breaklinks");
422         if (breaklinks == "true")
423                 h_pdf_breaklinks = "1";
424         else if (breaklinks == "false")
425                 h_pdf_breaklinks = "0";
426         string pdfborder = process_keyval_opt(options, "pdfborder");
427         if (pdfborder == "{0 0 0}")
428                 h_pdf_pdfborder = "1";
429         else if (pdfborder == "{0 0 1}")
430                 h_pdf_pdfborder = "0";
431         string backref = process_keyval_opt(options, "backref");
432         if (!backref.empty())
433                 h_pdf_backref = backref;
434         string colorlinks = process_keyval_opt(options, "colorlinks");
435         if (colorlinks == "true")
436                 h_pdf_colorlinks = "1";
437         else if (colorlinks == "false")
438                 h_pdf_colorlinks = "0";
439         string pdfpagemode = process_keyval_opt(options, "pdfpagemode");
440         if (!pdfpagemode.empty())
441                 h_pdf_pagemode = pdfpagemode;
442         string pdftitle = process_keyval_opt(options, "pdftitle");
443         if (!pdftitle.empty()) {
444                 h_pdf_title = remove_braces(pdftitle);
445         }
446         string pdfauthor = process_keyval_opt(options, "pdfauthor");
447         if (!pdfauthor.empty()) {
448                 h_pdf_author = remove_braces(pdfauthor);
449         }
450         string pdfsubject = process_keyval_opt(options, "pdfsubject");
451         if (!pdfsubject.empty())
452                 h_pdf_subject = remove_braces(pdfsubject);
453         string pdfkeywords = process_keyval_opt(options, "pdfkeywords");
454         if (!pdfkeywords.empty())
455                 h_pdf_keywords = remove_braces(pdfkeywords);
456         if (!options.empty()) {
457                 if (!h_pdf_quoted_options.empty())
458                         h_pdf_quoted_options += ',';
459                 h_pdf_quoted_options += join(options, ",");
460                 options.clear();
461         }
462 }
463
464
465 void handle_package(Parser &p, string const & name, string const & opts,
466                     bool in_lyx_preamble)
467 {
468         vector<string> options = split_options(opts);
469         add_package(name, options);
470         string scale;
471
472         // roman fonts
473         if (is_known(name, known_roman_fonts)) {
474                 h_font_roman = name;
475                 p.skip_spaces();
476         }
477
478         if (name == "fourier") {
479                 h_font_roman = "utopia";
480                 // when font uses real small capitals
481                 if (opts == "expert")
482                         h_font_sc = "true";
483         }
484
485         if (name == "mathpazo")
486                 h_font_roman = "palatino";
487
488         if (name == "mathptmx")
489                 h_font_roman = "times";
490
491         // sansserif fonts
492         if (is_known(name, known_sans_fonts)) {
493                 h_font_sans = name;
494                 if (!opts.empty()) {
495                         scale = opts;
496                         h_font_sf_scale = scale_as_percentage(scale);
497                 }
498         }
499
500         // typewriter fonts
501         if (is_known(name, known_typewriter_fonts)) {
502                 h_font_typewriter = name;
503                 if (!opts.empty()) {
504                         scale = opts;
505                         h_font_tt_scale = scale_as_percentage(scale);
506                 }
507         }
508
509         // font uses old-style figure
510         if (name == "eco")
511                 h_font_osf = "true";
512
513         else if (name == "amsmath" || name == "amssymb")
514                 h_use_amsmath = "2";
515
516         else if (name == "esint")
517                 h_use_esint = "2";
518
519         else if (name == "mhchem")
520                 h_use_mhchem = "2";
521
522         else if (name == "mathdots")
523                 h_use_mathdots = "2";
524
525         else if (name == "undertilde")
526                 h_use_undertilde = "2";
527
528         else if (name == "babel" && !opts.empty()) {
529                 // check if more than one option was used - used later for inputenc
530                 // in case inputenc is parsed before babel, set the encoding to auto
531                 if (options.begin() != options.end() - 1) {
532                         one_language = false;
533                         h_inputencoding = "auto";
534                 }
535                 // babel takes the last language of the option of its \usepackage
536                 // call as document language. If there is no such language option, the
537                 // last language in the documentclass options is used.
538                 handle_opt(options, known_languages, h_language);
539                 delete_opt(options, known_languages);
540         }
541
542         else if (name == "fontenc") {
543                 h_fontencoding = getStringFromVector(options, ",");
544                 /* We could do the following for better round trip support,
545                  * but this makes the document less portable, so I skip it:
546                 if (h_fontencoding == lyxrc.fontenc)
547                         h_fontencoding = "global";
548                  */
549                 options.clear();
550         }
551
552         else if (name == "inputenc" || name == "luainputenc") {
553                 // h_inputencoding is only set when there is not more than one
554                 // inputenc option because otherwise h_inputencoding must be
555                 // set to "auto" (the default encoding of the document language)
556                 // Therefore check for the "," character.
557                 // It is also only set when there is not more then one babel
558                 // language option but this is handled in the routine for babel.
559                 if (opts.find(",") == string::npos && one_language == true)
560                         h_inputencoding = opts;
561                 if (!options.empty())
562                         p.setEncoding(options.back());
563                 options.clear();
564         }
565
566         else if (name == "makeidx")
567                 ; // ignore this
568
569         else if (name == "prettyref")
570                 ; // ignore this
571
572         else if (name == "varioref")
573                 ; // ignore this
574
575         else if (name == "verbatim")
576                 ; // ignore this
577
578         else if (name == "nomencl")
579                 ; // ignore this
580
581         else if (name == "textcomp")
582                 ; // ignore this
583
584         else if (name == "url")
585                 ; // ignore this
586
587         else if (name == "subscript")
588                 ; // ignore this
589
590         else if (name == "color") {
591                 // with the following command this package is only loaded when needed for
592                 // undefined colors, since we only support the predefined colors
593                 h_preamble << "\\@ifundefined{definecolor}\n {\\usepackage{color}}{}\n";
594         }
595
596         else if (name == "graphicx")
597                 ; // ignore this
598
599         else if (name == "setspace")
600                 ; // ignore this
601
602 #if 0
603         // do not ignore as long as we don't support all commands (e.g. \xout is missing)
604         else if (name == "ulem")
605                 ; // ignore this
606 #endif
607
608         else if (name == "geometry")
609                 ; // Ignore this, the geometry settings are made by the \geometry
610                   // command. This command is handled below.
611
612         else if (is_known(name, known_languages))
613                 h_language = name;
614
615         else if (name == "natbib") {
616                 h_cite_engine = "natbib_authoryear";
617                 vector<string>::iterator it =
618                         find(options.begin(), options.end(), "authoryear");
619                 if (it != options.end())
620                         options.erase(it);
621                 else {
622                         it = find(options.begin(), options.end(), "numbers");
623                         if (it != options.end()) {
624                                 h_cite_engine = "natbib_numerical";
625                                 options.erase(it);
626                         }
627                 }
628         }
629
630         else if (name == "jurabib")
631                 h_cite_engine = "jurabib";
632
633         else if (name == "hyperref")
634                 handle_hyperref(options);
635
636         else if (!in_lyx_preamble) {
637                 if (options.empty())
638                         h_preamble << "\\usepackage{" << name << "}";
639                 else {
640                         h_preamble << "\\usepackage[" << opts << "]{" 
641                                    << name << "}";
642                         options.clear();
643                 }
644         }
645
646         // We need to do something with the options...
647         if (!options.empty())
648                 cerr << "Ignoring options '" << join(options, ",")
649                      << "' of package " << name << '.' << endl;
650
651         // remove the whitespace
652         p.skip_spaces();
653 }
654
655
656 void handle_if(Parser & p, bool in_lyx_preamble)
657 {
658         while (p.good()) {
659                 Token t = p.get_token();
660                 if (t.cat() == catEscape &&
661                     is_known(t.cs(), known_if_commands))
662                         handle_if(p, in_lyx_preamble);
663                 else {
664                         if (!in_lyx_preamble)
665                                 h_preamble << t.asInput();
666                         if (t.cat() == catEscape && t.cs() == "fi")
667                                 return;
668                 }
669         }
670 }
671
672
673 void end_preamble(ostream & os, TextClass const & /*textclass*/)
674 {
675         // translate from babel to LyX names
676         h_language = babel2lyx(h_language);
677
678         // set the quote language
679         // LyX only knows the following quotes languages:
680         // english, swedish, german, polish, french and danish
681         // (quotes for "japanese" and "chinese-traditional" are missing because
682         //  they wouldn't be useful: http://www.lyx.org/trac/ticket/6383)
683         // conversion list taken from
684         // http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
685         // (quotes for kazakh and interlingua are unknown)
686         // danish
687         if (h_language == "danish")
688                 h_quotes_language = "danish";
689         // french
690         else if (is_known(h_language, known_french_quotes_languages))
691                 h_quotes_language = "french";
692         // german
693         else if (is_known(h_language, known_german_quotes_languages))
694                 h_quotes_language = "german";
695         // polish
696         else if (is_known(h_language, known_polish_quotes_languages))
697                 h_quotes_language = "polish";
698         // swedish
699         else if (is_known(h_language, known_swedish_quotes_languages))
700                 h_quotes_language = "swedish";
701         //english
702         else if (is_known(h_language, known_english_quotes_languages))
703                 h_quotes_language = "english";
704
705         // output the LyX file settings
706         os << "#LyX file created by tex2lyx " << PACKAGE_VERSION << "\n"
707            << "\\lyxformat " << LYX_FORMAT << '\n'
708            << "\\begin_document\n"
709            << "\\begin_header\n"
710            << "\\textclass " << h_textclass << "\n";
711         if (!h_preamble.str().empty())
712                 os << "\\begin_preamble\n" << h_preamble.str() << "\n\\end_preamble\n";
713         if (!h_options.empty())
714                 os << "\\options " << h_options << "\n";
715         os << "\\use_default_options " << h_use_default_options << "\n"
716            << modules_placeholder
717            << "\\language " << h_language << "\n"
718            << "\\language_package " << h_language_package << "\n"
719            << "\\inputencoding " << h_inputencoding << "\n"
720            << "\\fontencoding " << h_fontencoding << "\n"
721            << "\\font_roman " << h_font_roman << "\n"
722            << "\\font_sans " << h_font_sans << "\n"
723            << "\\font_typewriter " << h_font_typewriter << "\n"
724            << "\\font_default_family " << h_font_default_family << "\n"
725            << "\\font_sc " << h_font_sc << "\n"
726            << "\\font_osf " << h_font_osf << "\n"
727            << "\\font_sf_scale " << h_font_sf_scale << "\n"
728            << "\\font_tt_scale " << h_font_tt_scale << "\n"
729            << "\\graphics " << h_graphics << "\n";
730         if (!h_float_placement.empty())
731                 os << "\\float_placement " << h_float_placement << "\n";
732         os << "\\paperfontsize " << h_paperfontsize << "\n"
733            << "\\spacing " << h_spacing << "\n"
734            << "\\use_hyperref " << h_use_hyperref << '\n';
735         if (h_use_hyperref == "1") {
736                 if (!h_pdf_title.empty())
737                         os << "\\pdf_title \"" << h_pdf_title << "\"\n";
738                 if (!h_pdf_author.empty())
739                         os << "\\pdf_author \"" << h_pdf_author << "\"\n";
740                 if (!h_pdf_subject.empty())
741                         os << "\\pdf_subject \"" << h_pdf_subject << "\"\n";
742                 if (!h_pdf_keywords.empty())
743                         os << "\\pdf_keywords \"" << h_pdf_keywords << "\"\n";
744                 os << "\\pdf_bookmarks " << h_pdf_bookmarks << "\n"
745                       "\\pdf_bookmarksnumbered " << h_pdf_bookmarksnumbered << "\n"
746                       "\\pdf_bookmarksopen " << h_pdf_bookmarksopen << "\n"
747                       "\\pdf_bookmarksopenlevel " << h_pdf_bookmarksopenlevel << "\n"
748                       "\\pdf_breaklinks " << h_pdf_breaklinks << "\n"
749                       "\\pdf_pdfborder " << h_pdf_pdfborder << "\n"
750                       "\\pdf_colorlinks " << h_pdf_colorlinks << "\n"
751                       "\\pdf_backref " << h_pdf_backref << "\n"
752                       "\\pdf_pdfusetitle " << h_pdf_pdfusetitle << '\n';
753                 if (!h_pdf_pagemode.empty())
754                         os << "\\pdf_pagemode " << h_pdf_pagemode << '\n';
755                 if (!h_pdf_quoted_options.empty())
756                         os << "\\pdf_quoted_options \"" << h_pdf_quoted_options << "\"\n";
757         }
758         os << "\\papersize " << h_papersize << "\n"
759            << "\\use_geometry " << h_use_geometry << "\n"
760            << "\\use_amsmath " << h_use_amsmath << "\n"
761            << "\\use_esint " << h_use_esint << "\n"
762            << "\\use_mhchem " << h_use_mhchem << "\n"
763            << "\\use_mathdots " << h_use_mathdots << "\n"
764            << "\\use_undertilde " << h_use_undertilde << "\n"
765            << "\\cite_engine " << h_cite_engine << "\n"
766            << "\\use_bibtopic " << h_use_bibtopic << "\n"
767            << "\\paperorientation " << h_paperorientation << '\n'
768            << "\\suppress_date " << h_suppress_date << '\n'
769            << "\\use_refstyle " << h_use_refstyle << '\n';
770         if (!h_fontcolor.empty())
771                 os << "\\fontcolor " << h_fontcolor << '\n';
772         if (!h_notefontcolor.empty())
773                 os << "\\notefontcolor " << h_notefontcolor << '\n';
774         if (!h_backgroundcolor.empty())
775                 os << "\\backgroundcolor " << h_backgroundcolor << '\n';
776         if (!h_boxbgcolor.empty())
777                 os << "\\boxbgcolor " << h_boxbgcolor << '\n';
778         os << h_margins
779            << "\\secnumdepth " << h_secnumdepth << "\n"
780            << "\\tocdepth " << h_tocdepth << "\n"
781            << "\\paragraph_separation " << h_paragraph_separation << "\n";
782         if (h_paragraph_separation == "skip")
783                 os << "\\defskip " << h_defskip << "\n";
784         else
785                 os << "\\paragraph_indentation " << h_paragraph_indentation << "\n";
786         os << "\\quotes_language " << h_quotes_language << "\n"
787            << "\\papercolumns " << h_papercolumns << "\n"
788            << "\\papersides " << h_papersides << "\n"
789            << "\\paperpagestyle " << h_paperpagestyle << "\n";
790         if (!h_listings_params.empty())
791                 os << "\\listings_params " << h_listings_params << "\n";
792         os << "\\tracking_changes " << h_tracking_changes << "\n"
793            << "\\output_changes " << h_output_changes << "\n"
794            << "\\html_math_output " << h_html_math_output << "\n"
795            << "\\html_css_as_file " << h_html_css_as_file << "\n"
796            << "\\html_be_strict " << h_html_be_strict << "\n"
797            << "\\end_header\n\n"
798            << "\\begin_body\n";
799         // clear preamble for subdocuments
800         h_preamble.str("");
801 }
802
803 } // anonymous namespace
804
805
806 void parse_preamble(Parser & p, ostream & os, 
807         string const & forceclass, TeX2LyXDocClass & tc)
808 {
809         // initialize fixed types
810         special_columns['D'] = 3;
811         bool is_full_document = false;
812         bool is_lyx_file = false;
813         bool in_lyx_preamble = false;
814
815         // determine whether this is a full document or a fragment for inclusion
816         while (p.good()) {
817                 Token const & t = p.get_token();
818
819                 if (t.cat() == catEscape && t.cs() == "documentclass") {
820                         is_full_document = true;
821                         break;
822                 }
823         }
824         p.reset();
825
826         while (is_full_document && p.good()) {
827                 Token const & t = p.get_token();
828
829 #ifdef FILEDEBUG
830                 cerr << "t: " << t << "\n";
831 #endif
832
833                 //
834                 // cat codes
835                 //
836                 if (!in_lyx_preamble &&
837                     (t.cat() == catLetter ||
838                      t.cat() == catSuper ||
839                      t.cat() == catSub ||
840                      t.cat() == catOther ||
841                      t.cat() == catMath ||
842                      t.cat() == catActive ||
843                      t.cat() == catBegin ||
844                      t.cat() == catEnd ||
845                      t.cat() == catAlign ||
846                      t.cat() == catParameter))
847                         h_preamble << t.cs();
848
849                 else if (!in_lyx_preamble && 
850                          (t.cat() == catSpace || t.cat() == catNewline))
851                         h_preamble << t.asInput();
852
853                 else if (t.cat() == catComment) {
854                         static regex const islyxfile("%% LyX .* created this file");
855                         static regex const usercommands("User specified LaTeX commands");
856
857                         string const comment = t.asInput();
858
859                         // magically switch encoding default if it looks like XeLaTeX
860                         static string const magicXeLaTeX =
861                                 "% This document must be compiled with XeLaTeX ";
862                         if (comment.size() > magicXeLaTeX.size() 
863                                   && comment.substr(0, magicXeLaTeX.size()) == magicXeLaTeX
864                                   && h_inputencoding == "auto") {
865                                 cerr << "XeLaTeX comment found, switching to UTF8\n";
866                                 h_inputencoding = "utf8";
867                         }
868                         smatch sub;
869                         if (regex_search(comment, sub, islyxfile)) {
870                                 is_lyx_file = true;
871                                 in_lyx_preamble = true;
872                         } else if (is_lyx_file
873                                    && regex_search(comment, sub, usercommands))
874                                 in_lyx_preamble = false;
875                         else if (!in_lyx_preamble)
876                                 h_preamble << t.asInput();
877                 }
878
879                 else if (t.cs() == "pagestyle")
880                         h_paperpagestyle = p.verbatim_item();
881
882                 else if (t.cs() == "makeatletter") {
883                         // LyX takes care of this
884                         p.setCatCode('@', catLetter);
885                 }
886
887                 else if (t.cs() == "makeatother") {
888                         // LyX takes care of this
889                         p.setCatCode('@', catOther);
890                 }
891
892                 else if (t.cs() == "newcommand" || t.cs() == "newcommandx"
893                       || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
894                       || t.cs() == "providecommand" || t.cs() == "providecommandx"
895                                 || t.cs() == "DeclareRobustCommand"
896                       || t.cs() == "DeclareRobustCommandx"
897                                 || t.cs() == "ProvideTextCommandDefault"
898                                 || t.cs() == "DeclareMathAccent") {
899                         bool star = false;
900                         if (p.next_token().character() == '*') {
901                                 p.get_token();
902                                 star = true;
903                         }
904                         string const name = p.verbatim_item();
905                         string const opt1 = p.getFullOpt();
906                         string const opt2 = p.getFullOpt();
907                         string const body = p.verbatim_item();
908                         // font settings
909                         if (name == "\\rmdefault")
910                                 if (is_known(body, known_roman_fonts))
911                                         h_font_roman = body;
912                         if (name == "\\sfdefault")
913                                 if (is_known(body, known_sans_fonts))
914                                         h_font_sans = body;
915                         if (name == "\\ttdefault")
916                                 if (is_known(body, known_typewriter_fonts))
917                                         h_font_typewriter = body;
918                         if (name == "\\familydefault") {
919                                 string family = body;
920                                 // remove leading "\"
921                                 h_font_default_family = family.erase(0,1);
922                         }
923
924                         // Add the command to the known commands
925                         add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
926
927                         // only non-lyxspecific stuff
928                         if (!in_lyx_preamble) {
929                                 ostringstream ss;
930                                 ss << '\\' << t.cs();
931                                 if (star)
932                                         ss << '*';
933                                 ss << '{' << name << '}' << opt1 << opt2
934                                    << '{' << body << "}";
935                                 h_preamble << ss.str();
936 /*
937                                 ostream & out = in_preamble ? h_preamble : os;
938                                 out << "\\" << t.cs() << "{" << name << "}"
939                                     << opts << "{" << body << "}";
940 */
941                         }
942                 }
943
944                 else if (t.cs() == "documentclass") {
945                         vector<string>::iterator it;
946                         vector<string> opts = split_options(p.getArg('[', ']'));
947                         handle_opt(opts, known_fontsizes, h_paperfontsize);
948                         delete_opt(opts, known_fontsizes);
949                         // delete "pt" at the end
950                         string::size_type i = h_paperfontsize.find("pt");
951                         if (i != string::npos)
952                                 h_paperfontsize.erase(i);
953                         // The documentclass options are always parsed before the options
954                         // of the babel call so that a language cannot overwrite the babel
955                         // options.
956                         handle_opt(opts, known_languages, h_language);
957                         delete_opt(opts, known_languages);
958
959                         // paper orientation
960                         if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
961                                 h_paperorientation = "landscape";
962                                 opts.erase(it);
963                         }
964                         // paper sides
965                         if ((it = find(opts.begin(), opts.end(), "oneside"))
966                                  != opts.end()) {
967                                 h_papersides = "1";
968                                 opts.erase(it);
969                         }
970                         if ((it = find(opts.begin(), opts.end(), "twoside"))
971                                  != opts.end()) {
972                                 h_papersides = "2";
973                                 opts.erase(it);
974                         }
975                         // paper columns
976                         if ((it = find(opts.begin(), opts.end(), "onecolumn"))
977                                  != opts.end()) {
978                                 h_papercolumns = "1";
979                                 opts.erase(it);
980                         }
981                         if ((it = find(opts.begin(), opts.end(), "twocolumn"))
982                                  != opts.end()) {
983                                 h_papercolumns = "2";
984                                 opts.erase(it);
985                         }
986                         // paper sizes
987                         // some size options are know to any document classes, other sizes
988                         // are handled by the \geometry command of the geometry package
989                         handle_opt(opts, known_class_paper_sizes, h_papersize);
990                         delete_opt(opts, known_class_paper_sizes);
991                         // the remaining options
992                         h_options = join(opts, ",");
993                         // FIXME This does not work for classes that have a
994                         //       different name in LyX than in LaTeX
995                         h_textclass = p.getArg('{', '}');
996                 }
997
998                 else if (t.cs() == "usepackage") {
999                         string const options = p.getArg('[', ']');
1000                         string const name = p.getArg('{', '}');
1001                         vector<string> vecnames;
1002                         split(name, vecnames, ',');
1003                         vector<string>::const_iterator it  = vecnames.begin();
1004                         vector<string>::const_iterator end = vecnames.end();
1005                         for (; it != end; ++it)
1006                                 handle_package(p, trimSpaceAndEol(*it), options, 
1007                                                in_lyx_preamble);
1008                 }
1009
1010                 else if (t.cs() == "inputencoding") {
1011                         string const encoding = p.getArg('{','}');
1012                         h_inputencoding = encoding;
1013                         p.setEncoding(encoding);
1014                 }
1015
1016                 else if (t.cs() == "newenvironment") {
1017                         string const name = p.getArg('{', '}');
1018                         string const opt1 = p.getFullOpt();
1019                         string const opt2 = p.getFullOpt();
1020                         string const beg = p.verbatim_item();
1021                         string const end = p.verbatim_item();
1022                         if (!in_lyx_preamble) {
1023                                 h_preamble << "\\newenvironment{" << name
1024                                            << '}' << opt1 << opt2 << '{'
1025                                            << beg << "}{" << end << '}';
1026                         }
1027                         add_known_environment(name, opt1, !opt2.empty(),
1028                                               from_utf8(beg), from_utf8(end));
1029
1030                 }
1031
1032                 else if (t.cs() == "def") {
1033                         string name = p.get_token().cs();
1034                         while (p.next_token().cat() != catBegin)
1035                                 name += p.get_token().cs();
1036                         if (!in_lyx_preamble)
1037                                 h_preamble << "\\def\\" << name << '{'
1038                                            << p.verbatim_item() << "}";
1039                 }
1040
1041                 else if (t.cs() == "newcolumntype") {
1042                         string const name = p.getArg('{', '}');
1043                         trimSpaceAndEol(name);
1044                         int nargs = 0;
1045                         string opts = p.getOpt();
1046                         if (!opts.empty()) {
1047                                 istringstream is(string(opts, 1));
1048                                 is >> nargs;
1049                         }
1050                         special_columns[name[0]] = nargs;
1051                         h_preamble << "\\newcolumntype{" << name << "}";
1052                         if (nargs)
1053                                 h_preamble << "[" << nargs << "]";
1054                         h_preamble << "{" << p.verbatim_item() << "}";
1055                 }
1056
1057                 else if (t.cs() == "setcounter") {
1058                         string const name = p.getArg('{', '}');
1059                         string const content = p.getArg('{', '}');
1060                         if (name == "secnumdepth")
1061                                 h_secnumdepth = content;
1062                         else if (name == "tocdepth")
1063                                 h_tocdepth = content;
1064                         else
1065                                 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
1066                 }
1067
1068                 else if (t.cs() == "setlength") {
1069                         string const name = p.verbatim_item();
1070                         string const content = p.verbatim_item();
1071                         // the paragraphs are only not indented when \parindent is set to zero
1072                         if (name == "\\parindent" && content != "") {
1073                                 if (content[0] == '0')
1074                                         h_paragraph_separation = "skip";
1075                                 else
1076                                         h_paragraph_indentation = translate_len(content);
1077                         } else if (name == "\\parskip") {
1078                                 if (content == "\\smallskipamount")
1079                                         h_defskip = "smallskip";
1080                                 else if (content == "\\medskipamount")
1081                                         h_defskip = "medskip";
1082                                 else if (content == "\\bigskipamount")
1083                                         h_defskip = "bigskip";
1084                                 else
1085                                         h_defskip = content;
1086                         } else
1087                                 h_preamble << "\\setlength{" << name << "}{" << content << "}";
1088                 }
1089
1090                 else if (t.cs() == "onehalfspacing")
1091                         h_spacing = "onehalf";
1092
1093                 else if (t.cs() == "doublespacing")
1094                         h_spacing = "double";
1095
1096                 else if (t.cs() == "setstretch")
1097                         h_spacing = "other " + p.verbatim_item();
1098
1099                 else if (t.cs() == "begin") {
1100                         string const name = p.getArg('{', '}');
1101                         if (name == "document")
1102                                 break;
1103                         h_preamble << "\\begin{" << name << "}";
1104                 }
1105
1106                 else if (t.cs() == "geometry") {
1107                         h_use_geometry = "true";
1108                         vector<string> opts = split_options(p.getArg('{', '}'));
1109                         vector<string>::iterator it;
1110                         // paper orientation
1111                         if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
1112                                 h_paperorientation = "landscape";
1113                                 opts.erase(it);
1114                         }
1115                         // paper size
1116                         handle_opt(opts, known_paper_sizes, h_papersize);
1117                         delete_opt(opts, known_paper_sizes);
1118                         // page margins
1119                         char const * const * margin = known_paper_margins;
1120                         int k = -1;
1121                         for (; *margin; ++margin) {
1122                                 k += 1;
1123                                 // search for the "=" in e.g. "lmargin=2cm" to get the value
1124                                 for(size_t i = 0; i != opts.size(); i++) {
1125                                         if (opts.at(i).find(*margin) != string::npos) {
1126                                                 string::size_type pos = opts.at(i).find("=");
1127                                                 string value = opts.at(i).substr(pos + 1);
1128                                                 string name = known_coded_paper_margins[k];
1129                                                 h_margins += "\\" + name + " " + value + "\n";
1130                                         }
1131                                 }
1132                         }
1133                 }
1134
1135                 else if (t.cs() == "definecolor") {
1136                         string const color = p.getArg('{', '}');
1137                         string const space = p.getArg('{', '}');
1138                         string const value = p.getArg('{', '}');
1139                         if (color == "document_fontcolor" && space == "rgb") {
1140                                 RGBColor c(RGBColorFromLaTeX(value));
1141                                 h_fontcolor = X11hexname(c);
1142                         } else if (color == "note_fontcolor" && space == "rgb") {
1143                                 RGBColor c(RGBColorFromLaTeX(value));
1144                                 h_notefontcolor = X11hexname(c);
1145                         } else if (color == "page_backgroundcolor" && space == "rgb") {
1146                                 RGBColor c(RGBColorFromLaTeX(value));
1147                                 h_backgroundcolor = X11hexname(c);
1148                         } else if (color == "shadecolor" && space == "rgb") {
1149                                 RGBColor c(RGBColorFromLaTeX(value));
1150                                 h_boxbgcolor = X11hexname(c);
1151                         } else {
1152                                 h_preamble << "\\definecolor{" << color
1153                                            << "}{" << space << "}{" << value
1154                                            << '}';
1155                         }
1156                 }
1157
1158                 else if (t.cs() == "jurabibsetup") {
1159                         // FIXME p.getArg('{', '}') is most probably wrong (it
1160                         //       does not handle nested braces).
1161                         //       Use p.verbatim_item() instead.
1162                         vector<string> jurabibsetup =
1163                                 split_options(p.getArg('{', '}'));
1164                         // add jurabibsetup to the jurabib package options
1165                         add_package("jurabib", jurabibsetup);
1166                         if (!jurabibsetup.empty()) {
1167                                 h_preamble << "\\jurabibsetup{"
1168                                            << join(jurabibsetup, ",") << '}';
1169                         }
1170                 }
1171
1172                 else if (t.cs() == "hypersetup") {
1173                         vector<string> hypersetup =
1174                                 split_options(p.verbatim_item());
1175                         // add hypersetup to the hyperref package options
1176                         handle_hyperref(hypersetup);
1177                         if (!hypersetup.empty()) {
1178                                 h_preamble << "\\hypersetup{"
1179                                            << join(hypersetup, ",") << '}';
1180                         }
1181                 }
1182
1183                 else if (is_known(t.cs(), known_if_3arg_commands)) {
1184                         // prevent misparsing of \usepackage if it is used
1185                         // as an argument (see e.g. our own output of
1186                         // \@ifundefined above)
1187                         string const arg1 = p.verbatim_item();
1188                         string const arg2 = p.verbatim_item();
1189                         string const arg3 = p.verbatim_item();
1190                         if (!in_lyx_preamble) {
1191                                 h_preamble << t.asInput()
1192                                            << '{' << arg1 << '}'
1193                                            << '{' << arg2 << '}'
1194                                            << '{' << arg3 << '}';
1195                         }
1196                 }
1197
1198                 else if (is_known(t.cs(), known_if_commands)) {
1199                         // must not parse anything in conditional code, since
1200                         // LyX would output the parsed contents unconditionally
1201                         if (!in_lyx_preamble)
1202                                 h_preamble << t.asInput();
1203                         handle_if(p, in_lyx_preamble);
1204                 }
1205
1206                 else if (!t.cs().empty() && !in_lyx_preamble)
1207                         h_preamble << '\\' << t.cs();
1208         }
1209
1210         // remove the whitespace
1211         p.skip_spaces();
1212
1213         // Force textclass if the user wanted it
1214         if (!forceclass.empty())
1215                 h_textclass = forceclass;
1216         if (noweb_mode && !prefixIs(h_textclass, "literate-"))
1217                 h_textclass.insert(0, "literate-");
1218         tc.setName(h_textclass);
1219         if (!tc.load()) {
1220                 cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
1221                 exit(EXIT_FAILURE);
1222         }
1223         if (h_papersides.empty()) {
1224                 ostringstream ss;
1225                 ss << tc.sides();
1226                 h_papersides = ss.str();
1227         }
1228         end_preamble(os, tc);
1229 }
1230
1231
1232 /// translates a babel language name to a LyX language name
1233 string babel2lyx(string const & language)
1234 {
1235         char const * const * where = is_known(language, known_languages);
1236         if (where)
1237                 return known_coded_languages[where - known_languages];
1238         return language;
1239 }
1240
1241 // }])
1242
1243
1244 } // namespace lyx