3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
9 * Full author contact details are available in file CREDITS.
18 #include "LayoutFile.h"
21 #include "TextClass.h"
23 #include "support/convert.h"
24 #include "support/FileName.h"
25 #include "support/filetools.h"
26 #include "support/lstrings.h"
28 #include "support/regex.h"
38 using namespace lyx::support;
43 // special columntypes
44 extern map<char, int> special_columns;
46 map<string, vector<string> > used_packages;
47 const char * const modules_placeholder = "\001modules\001";
49 // needed to handle encodings with babel
50 bool one_language = true;
51 string h_inputencoding = "auto";
52 string h_paragraph_separation = "indent";
54 // necessary to avoid that our preamble stuff is added at each tex2lyx run
55 // which would pollute the preamble when doing roundtrips
56 bool ifundefined_color_set = false;
60 //add this to known_languages when updating to lyxformat 266:
61 // "armenian" (needs special handling since not supported by standard babel)
62 //add these to known_languages when updating to lyxformat 268:
63 //"chinese-simplified", "chinese-traditional", "japanese", "korean"
64 // Both changes require first that support for non-babel languages (CJK,
66 // add turkmen for lyxformat 383
68 * known babel language names (including synonyms)
69 * not in standard babel: arabic, arabtex, armenian, belarusian, serbian-latin, thai
70 * not yet supported by LyX: kurmanji
71 * please keep this in sync with known_coded_languages line by line!
73 const char * const known_languages[] = {"acadian", "afrikaans", "albanian",
74 "american", "arabic", "arabtex", "austrian", "bahasa", "bahasai", "bahasam",
75 "basque", "belarusian", "brazil", "brazilian", "breton", "british", "bulgarian",
76 "canadian", "canadien", "catalan", "croatian", "czech", "danish", "dutch",
77 "english", "esperanto", "estonian", "farsi", "finnish", "francais", "french",
78 "frenchb", "frenchle", "frenchpro", "galician", "german", "germanb", "greek",
79 "hebrew", "hungarian", "icelandic", "indon", "indonesian", "interlingua",
80 "irish", "italian", "kazakh", "latin", "latvian", "lithuanian", "lowersorbian",
81 "lsorbian", "magyar", "malay", "meyalu", "mongolian", "naustrian", "newzealand",
82 "ngerman", "ngermanb", "norsk", "nynorsk", "polutonikogreek", "polish",
83 "portuges", "portuguese", "romanian", "russian", "russianb", "samin",
84 "scottish", "serbian", "serbian-latin", "slovak", "slovene", "spanish",
85 "swedish", "thai", "turkish", "turkmen", "ukraineb", "ukrainian",
86 "uppersorbian", "UKenglish", "USenglish", "usorbian", "vietnam", "welsh",
90 * the same as known_languages with .lyx names
91 * please keep this in sync with known_languages line by line!
93 const char * const known_coded_languages[] = {"french", "afrikaans", "albanian",
94 "american", "arabic_arabi", "arabic_arabtex", "austrian", "bahasa", "bahasa", "bahasam",
95 "basque", "belarusian", "brazilian", "brazilian", "breton", "british", "bulgarian",
96 "canadian", "canadien", "catalan", "croatian", "czech", "danish", "dutch",
97 "english", "esperanto", "estonian", "farsi", "finnish", "french", "french",
98 "french", "french", "french", "galician", "german", "german", "greek",
99 "hebrew", "magyar", "icelandic", "bahasa", "bahasa", "interlingua",
100 "irish", "italian", "kazakh", "latin", "latvian", "lithuanian", "lowersorbian",
101 "lowersorbian", "magyar", "bahasam", "bahasam", "mongolian", "naustrian", "english",
102 "ngerman", "ngerman", "norsk", "nynorsk", "polutonikogreek", "polish",
103 "portuguese", "portuguese", "romanian", "russian", "russian", "samin",
104 "scottish", "serbian", "serbian-latin", "slovak", "slovene", "spanish",
105 "swedish", "thai", "turkish", "turkmen", "ukrainian", "ukrainian",
106 "uppersorbian", "uppersorbian", "english", "english", "vietnamese", "welsh",
109 /// languages with english quotes (.lyx names)
110 const char * const known_english_quotes_languages[] = {"american", "bahasa",
111 "bahasam", "brazilian", "canadian", "chinese-simplified", "english",
112 "esperanto", "hebrew", "irish", "korean", "portuguese", "scottish", "thai", 0};
114 /// languages with french quotes (.lyx names)
115 const char * const known_french_quotes_languages[] = {"albanian",
116 "arabic_arabi", "arabic_arabtex", "basque", "canadien", "catalan", "french",
117 "galician", "greek", "italian", "norsk", "nynorsk", "polutonikogreek",
118 "russian", "spanish", "spanish-mexico", "turkish", "turkmen", "ukrainian",
121 /// languages with german quotes (.lyx names)
122 const char * const known_german_quotes_languages[] = {"austrian", "bulgarian",
123 "czech", "german", "icelandic", "lithuanian", "lowersorbian", "naustrian",
124 "ngerman", "serbian", "serbian-latin", "slovak", "slovene", "uppersorbian", 0};
126 /// languages with polish quotes (.lyx names)
127 const char * const known_polish_quotes_languages[] = {"afrikaans", "croatian",
128 "dutch", "estonian", "magyar", "polish", "romanian", 0};
130 /// languages with swedish quotes (.lyx names)
131 const char * const known_swedish_quotes_languages[] = {"finnish",
134 /// known language packages from the times before babel
135 const char * const known_old_language_packages[] = {"french", "frenchle",
136 "frenchpro", "german", "ngerman", "pmfrench", 0};
138 char const * const known_fontsizes[] = { "10pt", "11pt", "12pt", 0 };
140 const char * const known_roman_fonts[] = { "ae", "beraserif", "bookman",
141 "ccfonts", "chancery", "charter", "cmr", "fourier", "lmodern", "mathpazo",
142 "mathptmx", "newcent", "utopia", 0};
144 const char * const known_sans_fonts[] = { "avant", "berasans", "cmbr", "cmss",
145 "helvet", "lmss", 0};
147 const char * const known_typewriter_fonts[] = { "beramono", "cmtl", "cmtt",
148 "courier", "lmtt", "luximono", "fourier", "lmodern", "mathpazo", "mathptmx",
151 const char * const known_paper_sizes[] = { "a0paper", "b0paper", "c0paper",
152 "a1paper", "b1paper", "c1paper", "a2paper", "b2paper", "c2paper", "a3paper",
153 "b3paper", "c3paper", "a4paper", "b4paper", "c4paper", "a5paper", "b5paper",
154 "c5paper", "a6paper", "b6paper", "c6paper", "executivepaper", "legalpaper",
155 "letterpaper", "b0j", "b1j", "b2j", "b3j", "b4j", "b5j", "b6j", 0};
157 const char * const known_class_paper_sizes[] = { "a4paper", "a5paper",
158 "executivepaper", "legalpaper", "letterpaper", 0};
160 const char * const known_paper_margins[] = { "lmargin", "tmargin", "rmargin",
161 "bmargin", "headheight", "headsep", "footskip", "columnsep", 0};
163 const char * const known_coded_paper_margins[] = { "leftmargin", "topmargin",
164 "rightmargin", "bottommargin", "headheight", "headsep", "footskip",
167 /// commands that can start an \if...\else...\endif sequence
168 const char * const known_if_commands[] = {"if", "ifarydshln", "ifbraket",
169 "ifcancel", "ifcolortbl", "ifeurosym", "ifmarginnote", "ifmmode", "ifpdf",
170 "ifsidecap", "ifupgreek", 0};
172 /// conditional commands with three arguments like \@ifundefined{}{}{}
173 const char * const known_if_3arg_commands[] = {"@ifundefined", "IfFileExists",
177 ostringstream h_preamble;
178 string h_textclass = "article";
179 string h_use_default_options = "false";
181 string h_language = "english";
182 string h_language_package = "none";
183 string h_fontencoding = "default";
184 string h_font_roman = "default";
185 string h_font_sans = "default";
186 string h_font_typewriter = "default";
187 string h_font_default_family = "default";
188 string h_font_sc = "false";
189 string h_font_osf = "false";
190 string h_font_sf_scale = "100";
191 string h_font_tt_scale = "100";
192 string h_graphics = "default";
193 string h_float_placement;
194 string h_paperfontsize = "default";
195 string h_spacing = "single";
196 string h_use_hyperref = "0";
199 string h_pdf_subject;
200 string h_pdf_keywords;
201 string h_pdf_bookmarks = "1";
202 string h_pdf_bookmarksnumbered = "0";
203 string h_pdf_bookmarksopen = "0";
204 string h_pdf_bookmarksopenlevel = "1";
205 string h_pdf_breaklinks = "0";
206 string h_pdf_pdfborder = "0";
207 string h_pdf_colorlinks = "0";
208 string h_pdf_backref = "section";
209 string h_pdf_pdfusetitle = "1";
210 string h_pdf_pagemode;
211 string h_pdf_quoted_options;
212 string h_papersize = "default";
213 string h_use_geometry = "false";
214 string h_use_amsmath = "1";
215 string h_use_esint = "1";
216 string h_use_mhchem = "0";
217 string h_use_mathdots = "0";
218 string h_use_undertilde = "0";
219 string h_cite_engine = "basic";
220 string h_use_bibtopic = "false";
221 string h_paperorientation = "portrait";
222 string h_suppress_date = "false";
223 string h_use_refstyle = "0";
224 string h_backgroundcolor;
227 string h_notefontcolor;
228 string h_secnumdepth = "3";
229 string h_tocdepth = "3";
230 string h_defskip = "medskip";
231 string h_paragraph_indentation = "default";
232 string h_quotes_language = "english";
233 string h_papercolumns = "1";
235 string h_paperpagestyle = "default";
236 string h_listings_params;
237 string h_tracking_changes = "false";
238 string h_output_changes = "false";
239 string h_html_math_output = "0";
240 string h_html_css_as_file = "0";
241 string h_html_be_strict = "false";
245 // returns true if at least one of the options in what has been found
246 bool handle_opt(vector<string> & opts, char const * const * what, string & target)
252 // the last language option is the document language (for babel and LyX)
253 // the last size option is the document font size
254 vector<string>::iterator it;
255 vector<string>::iterator position = opts.begin();
256 for (; *what; ++what) {
257 it = find(opts.begin(), opts.end(), *what);
258 if (it != opts.end()) {
259 if (it >= position) {
270 void delete_opt(vector<string> & opts, char const * const * what)
275 // remove found options from the list
276 // do this after handle_opt to avoid potential memory leaks
277 vector<string>::iterator it;
278 for (; *what; ++what) {
279 it = find(opts.begin(), opts.end(), *what);
280 if (it != opts.end())
287 * Split a package options string (keyval format) into a vector.
289 * authorformat=smallcaps,
291 * titleformat=colonsep,
292 * bibformat={tabular,ibidem,numbered}
294 vector<string> split_options(string const & input)
296 vector<string> options;
300 Token const & t = p.get_token();
301 if (t.asInput() == ",") {
302 options.push_back(trimSpaceAndEol(option));
304 } else if (t.asInput() == "=") {
307 if (p.next_token().asInput() == "{")
308 option += '{' + p.getArg('{', '}') + '}';
309 } else if (t.cat() != catSpace)
310 option += t.asInput();
314 options.push_back(trimSpaceAndEol(option));
321 * Retrieve a keyval option "name={value with=sign}" named \p name from
322 * \p options and return the value.
323 * The found option is also removed from \p options.
325 string process_keyval_opt(vector<string> & options, string name)
327 for (size_t i = 0; i < options.size(); ++i) {
328 vector<string> option;
329 split(options[i], option, '=');
330 if (option.size() < 2)
332 if (option[0] == name) {
333 options.erase(options.begin() + i);
334 option.erase(option.begin());
335 return join(option, "=");
343 * Add package \p name with options \p options to used_packages.
344 * Remove options from \p options that we don't want to output.
346 void add_package(string const & name, vector<string> & options)
348 // every package inherits the global options
349 if (used_packages.find(name) == used_packages.end())
350 used_packages[name] = split_options(h_options);
352 vector<string> & v = used_packages[name];
353 v.insert(v.end(), options.begin(), options.end());
354 if (name == "jurabib") {
355 // Don't output the order argument (see the cite command
356 // handling code in text.cpp).
357 vector<string>::iterator end =
358 remove(options.begin(), options.end(), "natbiborder");
359 end = remove(options.begin(), end, "jurabiborder");
360 options.erase(end, options.end());
365 // Given is a string like "scaled=0.9", return 0.9 * 100
366 string const scale_as_percentage(string const & scale)
368 string::size_type pos = scale.find('=');
369 if (pos != string::npos) {
370 string value = scale.substr(pos + 1);
372 return convert<string>(100 * convert<double>(value));
374 // If the input string didn't match our expectations.
375 // return the default value "100"
380 string remove_braces(string const & value)
384 if (value[0] == '{' && value[value.length()-1] == '}')
385 return value.substr(1, value.length()-2);
390 void handle_hyperref(vector<string> & options)
392 // FIXME swallow inputencoding changes that might surround the
393 // hyperref setup if it was written by LyX
394 h_use_hyperref = "1";
395 // swallow "unicode=true", since LyX does always write that
396 vector<string>::iterator it =
397 find(options.begin(), options.end(), "unicode=true");
398 if (it != options.end())
400 it = find(options.begin(), options.end(), "pdfusetitle");
401 if (it != options.end()) {
402 h_pdf_pdfusetitle = "1";
405 string bookmarks = process_keyval_opt(options, "bookmarks");
406 if (bookmarks == "true")
407 h_pdf_bookmarks = "1";
408 else if (bookmarks == "false")
409 h_pdf_bookmarks = "0";
410 if (h_pdf_bookmarks == "1") {
411 string bookmarksnumbered =
412 process_keyval_opt(options, "bookmarksnumbered");
413 if (bookmarksnumbered == "true")
414 h_pdf_bookmarksnumbered = "1";
415 else if (bookmarksnumbered == "false")
416 h_pdf_bookmarksnumbered = "0";
417 string bookmarksopen =
418 process_keyval_opt(options, "bookmarksopen");
419 if (bookmarksopen == "true")
420 h_pdf_bookmarksopen = "1";
421 else if (bookmarksopen == "false")
422 h_pdf_bookmarksopen = "0";
423 if (h_pdf_bookmarksopen == "1") {
424 string bookmarksopenlevel =
425 process_keyval_opt(options, "bookmarksopenlevel");
426 if (!bookmarksopenlevel.empty())
427 h_pdf_bookmarksopenlevel = bookmarksopenlevel;
430 string breaklinks = process_keyval_opt(options, "breaklinks");
431 if (breaklinks == "true")
432 h_pdf_breaklinks = "1";
433 else if (breaklinks == "false")
434 h_pdf_breaklinks = "0";
435 string pdfborder = process_keyval_opt(options, "pdfborder");
436 if (pdfborder == "{0 0 0}")
437 h_pdf_pdfborder = "1";
438 else if (pdfborder == "{0 0 1}")
439 h_pdf_pdfborder = "0";
440 string backref = process_keyval_opt(options, "backref");
441 if (!backref.empty())
442 h_pdf_backref = backref;
443 string colorlinks = process_keyval_opt(options, "colorlinks");
444 if (colorlinks == "true")
445 h_pdf_colorlinks = "1";
446 else if (colorlinks == "false")
447 h_pdf_colorlinks = "0";
448 string pdfpagemode = process_keyval_opt(options, "pdfpagemode");
449 if (!pdfpagemode.empty())
450 h_pdf_pagemode = pdfpagemode;
451 string pdftitle = process_keyval_opt(options, "pdftitle");
452 if (!pdftitle.empty()) {
453 h_pdf_title = remove_braces(pdftitle);
455 string pdfauthor = process_keyval_opt(options, "pdfauthor");
456 if (!pdfauthor.empty()) {
457 h_pdf_author = remove_braces(pdfauthor);
459 string pdfsubject = process_keyval_opt(options, "pdfsubject");
460 if (!pdfsubject.empty())
461 h_pdf_subject = remove_braces(pdfsubject);
462 string pdfkeywords = process_keyval_opt(options, "pdfkeywords");
463 if (!pdfkeywords.empty())
464 h_pdf_keywords = remove_braces(pdfkeywords);
465 if (!options.empty()) {
466 if (!h_pdf_quoted_options.empty())
467 h_pdf_quoted_options += ',';
468 h_pdf_quoted_options += join(options, ",");
474 void handle_package(Parser &p, string const & name, string const & opts,
475 bool in_lyx_preamble)
477 vector<string> options = split_options(opts);
478 add_package(name, options);
482 if (is_known(name, known_roman_fonts)) {
487 if (name == "fourier") {
488 h_font_roman = "utopia";
489 // when font uses real small capitals
490 if (opts == "expert")
494 if (name == "mathpazo")
495 h_font_roman = "palatino";
497 if (name == "mathptmx")
498 h_font_roman = "times";
501 if (is_known(name, known_sans_fonts)) {
505 h_font_sf_scale = scale_as_percentage(scale);
510 if (is_known(name, known_typewriter_fonts)) {
511 // fourier can be set as roman font _only_
512 // fourier as typewriter is handled in handling of \ttdefault
513 if (name != "fourier") {
514 h_font_typewriter = name;
517 h_font_tt_scale = scale_as_percentage(scale);
522 // font uses old-style figure
526 // after the detection and handling of special cases, we can remove the
527 // fonts, otherwise they would appear in the preamble, see bug #7856
528 if (is_known(name, known_roman_fonts) || is_known(name, known_sans_fonts)
529 || is_known(name, known_typewriter_fonts))
532 else if (name == "amsmath" || name == "amssymb")
535 else if (name == "esint")
538 else if (name == "mhchem")
541 else if (name == "mathdots")
542 h_use_mathdots = "2";
544 else if (name == "undertilde")
545 h_use_undertilde = "2";
547 else if (name == "babel") {
548 h_language_package = "default";
549 // we have to do nothing if babel is loaded without any options, otherwise
550 // we would pollute the preamble with this call in every roundtrip
552 // check if more than one option was used - used later for inputenc
553 // in case inputenc is parsed before babel, set the encoding to auto
554 if (options.begin() != options.end() - 1) {
555 one_language = false;
556 h_inputencoding = "auto";
558 // babel takes the last language of the option of its \usepackage
559 // call as document language. If there is no such language option, the
560 // last language in the documentclass options is used.
561 handle_opt(options, known_languages, h_language);
562 delete_opt(options, known_languages);
566 else if (name == "fontenc") {
567 h_fontencoding = getStringFromVector(options, ",");
568 // as of version LyX 2.0 "T1" is equal to the setting "global"
569 if (h_fontencoding == "T1")
570 h_fontencoding = "global";
574 else if (name == "inputenc" || name == "luainputenc") {
575 // h_inputencoding is only set when there is not more than one
576 // inputenc option because otherwise h_inputencoding must be
577 // set to "auto" (the default encoding of the document language)
578 // Therefore check for the "," character.
579 // It is also only set when there is not more then one babel
580 // language option but this is handled in the routine for babel.
581 if (opts.find(",") == string::npos && one_language == true)
582 h_inputencoding = opts;
583 if (!options.empty())
584 p.setEncoding(options.back());
588 else if (is_known(name, known_old_language_packages)) {
589 // known language packages from the times before babel
590 // if they are found and not also babel, they will be used as
591 // cutom language package
592 h_language_package = "\\usepackage{" + name + "}";
595 else if (name == "makeidx")
598 else if (name == "prettyref")
601 else if (name == "varioref")
604 else if (name == "verbatim")
607 else if (name == "nomencl")
610 else if (name == "textcomp")
613 else if (name == "url")
616 else if (name == "subscript")
619 else if (name == "color") {
620 // with the following command this package is only loaded when needed for
621 // undefined colors, since we only support the predefined colors
622 // only add it if not yet added
623 if (!ifundefined_color_set)
624 h_preamble << "\\@ifundefined{definecolor}\n {\\usepackage{color}}{}\n";
627 else if (name == "graphicx")
630 else if (name == "setspace")
634 // do not ignore as long as we don't support all commands (e.g. \xout is missing)
635 else if (name == "ulem")
639 else if (name == "geometry")
640 ; // Ignore this, the geometry settings are made by the \geometry
641 // command. This command is handled below.
643 else if (is_known(name, known_languages))
646 else if (name == "natbib") {
647 h_cite_engine = "natbib_authoryear";
648 vector<string>::iterator it =
649 find(options.begin(), options.end(), "authoryear");
650 if (it != options.end())
653 it = find(options.begin(), options.end(), "numbers");
654 if (it != options.end()) {
655 h_cite_engine = "natbib_numerical";
661 else if (name == "jurabib")
662 h_cite_engine = "jurabib";
664 else if (name == "hyperref")
665 handle_hyperref(options);
667 else if (!in_lyx_preamble) {
669 h_preamble << "\\usepackage{" << name << "}";
671 h_preamble << "\\usepackage[" << opts << "]{"
677 // We need to do something with the options...
678 if (!options.empty())
679 cerr << "Ignoring options '" << join(options, ",")
680 << "' of package " << name << '.' << endl;
682 // remove the whitespace
687 void handle_if(Parser & p, bool in_lyx_preamble)
690 Token t = p.get_token();
691 if (t.cat() == catEscape &&
692 is_known(t.cs(), known_if_commands))
693 handle_if(p, in_lyx_preamble);
695 if (!in_lyx_preamble)
696 h_preamble << t.asInput();
697 if (t.cat() == catEscape && t.cs() == "fi")
704 void end_preamble(ostream & os, TextClass const & /*textclass*/)
706 // translate from babel to LyX names
707 h_language = babel2lyx(h_language);
709 // set the quote language
710 // LyX only knows the following quotes languages:
711 // english, swedish, german, polish, french and danish
712 // (quotes for "japanese" and "chinese-traditional" are missing because
713 // they wouldn't be useful: http://www.lyx.org/trac/ticket/6383)
714 // conversion list taken from
715 // http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
716 // (quotes for kazakh and interlingua are unknown)
718 if (h_language == "danish")
719 h_quotes_language = "danish";
721 else if (is_known(h_language, known_french_quotes_languages))
722 h_quotes_language = "french";
724 else if (is_known(h_language, known_german_quotes_languages))
725 h_quotes_language = "german";
727 else if (is_known(h_language, known_polish_quotes_languages))
728 h_quotes_language = "polish";
730 else if (is_known(h_language, known_swedish_quotes_languages))
731 h_quotes_language = "swedish";
733 else if (is_known(h_language, known_english_quotes_languages))
734 h_quotes_language = "english";
736 // output the LyX file settings
737 os << "#LyX file created by tex2lyx " << PACKAGE_VERSION << "\n"
738 << "\\lyxformat " << LYX_FORMAT << '\n'
739 << "\\begin_document\n"
740 << "\\begin_header\n"
741 << "\\textclass " << h_textclass << "\n";
742 if (!h_preamble.str().empty())
743 os << "\\begin_preamble\n" << h_preamble.str() << "\n\\end_preamble\n";
744 if (!h_options.empty())
745 os << "\\options " << h_options << "\n";
746 os << "\\use_default_options " << h_use_default_options << "\n"
747 << modules_placeholder
748 << "\\language " << h_language << "\n"
749 << "\\language_package " << h_language_package << "\n"
750 << "\\inputencoding " << h_inputencoding << "\n"
751 << "\\fontencoding " << h_fontencoding << "\n"
752 << "\\font_roman " << h_font_roman << "\n"
753 << "\\font_sans " << h_font_sans << "\n"
754 << "\\font_typewriter " << h_font_typewriter << "\n"
755 << "\\font_default_family " << h_font_default_family << "\n"
756 << "\\font_sc " << h_font_sc << "\n"
757 << "\\font_osf " << h_font_osf << "\n"
758 << "\\font_sf_scale " << h_font_sf_scale << "\n"
759 << "\\font_tt_scale " << h_font_tt_scale << "\n"
760 << "\\graphics " << h_graphics << "\n";
761 if (!h_float_placement.empty())
762 os << "\\float_placement " << h_float_placement << "\n";
763 os << "\\paperfontsize " << h_paperfontsize << "\n"
764 << "\\spacing " << h_spacing << "\n"
765 << "\\use_hyperref " << h_use_hyperref << '\n';
766 if (h_use_hyperref == "1") {
767 if (!h_pdf_title.empty())
768 os << "\\pdf_title \"" << h_pdf_title << "\"\n";
769 if (!h_pdf_author.empty())
770 os << "\\pdf_author \"" << h_pdf_author << "\"\n";
771 if (!h_pdf_subject.empty())
772 os << "\\pdf_subject \"" << h_pdf_subject << "\"\n";
773 if (!h_pdf_keywords.empty())
774 os << "\\pdf_keywords \"" << h_pdf_keywords << "\"\n";
775 os << "\\pdf_bookmarks " << h_pdf_bookmarks << "\n"
776 "\\pdf_bookmarksnumbered " << h_pdf_bookmarksnumbered << "\n"
777 "\\pdf_bookmarksopen " << h_pdf_bookmarksopen << "\n"
778 "\\pdf_bookmarksopenlevel " << h_pdf_bookmarksopenlevel << "\n"
779 "\\pdf_breaklinks " << h_pdf_breaklinks << "\n"
780 "\\pdf_pdfborder " << h_pdf_pdfborder << "\n"
781 "\\pdf_colorlinks " << h_pdf_colorlinks << "\n"
782 "\\pdf_backref " << h_pdf_backref << "\n"
783 "\\pdf_pdfusetitle " << h_pdf_pdfusetitle << '\n';
784 if (!h_pdf_pagemode.empty())
785 os << "\\pdf_pagemode " << h_pdf_pagemode << '\n';
786 if (!h_pdf_quoted_options.empty())
787 os << "\\pdf_quoted_options \"" << h_pdf_quoted_options << "\"\n";
789 os << "\\papersize " << h_papersize << "\n"
790 << "\\use_geometry " << h_use_geometry << "\n"
791 << "\\use_amsmath " << h_use_amsmath << "\n"
792 << "\\use_esint " << h_use_esint << "\n"
793 << "\\use_mhchem " << h_use_mhchem << "\n"
794 << "\\use_mathdots " << h_use_mathdots << "\n"
795 << "\\use_undertilde " << h_use_undertilde << "\n"
796 << "\\cite_engine " << h_cite_engine << "\n"
797 << "\\use_bibtopic " << h_use_bibtopic << "\n"
798 << "\\paperorientation " << h_paperorientation << '\n'
799 << "\\suppress_date " << h_suppress_date << '\n'
800 << "\\use_refstyle " << h_use_refstyle << '\n';
801 if (!h_fontcolor.empty())
802 os << "\\fontcolor " << h_fontcolor << '\n';
803 if (!h_notefontcolor.empty())
804 os << "\\notefontcolor " << h_notefontcolor << '\n';
805 if (!h_backgroundcolor.empty())
806 os << "\\backgroundcolor " << h_backgroundcolor << '\n';
807 if (!h_boxbgcolor.empty())
808 os << "\\boxbgcolor " << h_boxbgcolor << '\n';
810 << "\\secnumdepth " << h_secnumdepth << "\n"
811 << "\\tocdepth " << h_tocdepth << "\n"
812 << "\\paragraph_separation " << h_paragraph_separation << "\n";
813 if (h_paragraph_separation == "skip")
814 os << "\\defskip " << h_defskip << "\n";
816 os << "\\paragraph_indentation " << h_paragraph_indentation << "\n";
817 os << "\\quotes_language " << h_quotes_language << "\n"
818 << "\\papercolumns " << h_papercolumns << "\n"
819 << "\\papersides " << h_papersides << "\n"
820 << "\\paperpagestyle " << h_paperpagestyle << "\n";
821 if (!h_listings_params.empty())
822 os << "\\listings_params " << h_listings_params << "\n";
823 os << "\\tracking_changes " << h_tracking_changes << "\n"
824 << "\\output_changes " << h_output_changes << "\n"
825 << "\\html_math_output " << h_html_math_output << "\n"
826 << "\\html_css_as_file " << h_html_css_as_file << "\n"
827 << "\\html_be_strict " << h_html_be_strict << "\n"
828 << "\\end_header\n\n"
830 // clear preamble for subdocuments
834 } // anonymous namespace
837 void parse_preamble(Parser & p, ostream & os,
838 string const & forceclass, TeX2LyXDocClass & tc)
840 // initialize fixed types
841 special_columns['D'] = 3;
842 bool is_full_document = false;
843 bool is_lyx_file = false;
844 bool in_lyx_preamble = false;
846 // determine whether this is a full document or a fragment for inclusion
848 Token const & t = p.get_token();
850 if (t.cat() == catEscape && t.cs() == "documentclass") {
851 is_full_document = true;
857 while (is_full_document && p.good()) {
858 Token const & t = p.get_token();
861 cerr << "t: " << t << "\n";
867 if (!in_lyx_preamble &&
868 (t.cat() == catLetter ||
869 t.cat() == catSuper ||
871 t.cat() == catOther ||
872 t.cat() == catMath ||
873 t.cat() == catActive ||
874 t.cat() == catBegin ||
876 t.cat() == catAlign ||
877 t.cat() == catParameter))
878 h_preamble << t.cs();
880 else if (!in_lyx_preamble &&
881 (t.cat() == catSpace || t.cat() == catNewline))
882 h_preamble << t.asInput();
884 else if (t.cat() == catComment) {
885 static regex const islyxfile("%% LyX .* created this file");
886 static regex const usercommands("User specified LaTeX commands");
888 string const comment = t.asInput();
890 // magically switch encoding default if it looks like XeLaTeX
891 static string const magicXeLaTeX =
892 "% This document must be compiled with XeLaTeX ";
893 if (comment.size() > magicXeLaTeX.size()
894 && comment.substr(0, magicXeLaTeX.size()) == magicXeLaTeX
895 && h_inputencoding == "auto") {
896 cerr << "XeLaTeX comment found, switching to UTF8\n";
897 h_inputencoding = "utf8";
900 if (regex_search(comment, sub, islyxfile)) {
902 in_lyx_preamble = true;
903 } else if (is_lyx_file
904 && regex_search(comment, sub, usercommands))
905 in_lyx_preamble = false;
906 else if (!in_lyx_preamble)
907 h_preamble << t.asInput();
910 else if (t.cs() == "pagestyle")
911 h_paperpagestyle = p.verbatim_item();
913 else if (t.cs() == "date") {
914 if (p.verbatim_item().empty())
915 h_suppress_date = "true";
918 else if (t.cs() == "color") {
919 string argument = p.getArg('{', '}');
920 // check the case that not a color defined by LyX is used
921 if (argument != "document_fontcolor") {
922 h_preamble << t.asInput() << '{' << argument << '}';
927 else if (t.cs() == "pagecolor") {
928 string argument = p.getArg('{', '}');
929 // check the case that not a color defined by LyX is used
930 if (argument != "page_backgroundcolor") {
931 h_preamble << t.asInput() << '{' << argument << '}';
932 h_backgroundcolor = "";
936 else if (t.cs() == "makeatletter") {
937 // LyX takes care of this
938 p.setCatCode('@', catLetter);
941 else if (t.cs() == "makeatother") {
942 // LyX takes care of this
943 p.setCatCode('@', catOther);
946 else if (t.cs() == "newcommand" || t.cs() == "newcommandx"
947 || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
948 || t.cs() == "providecommand" || t.cs() == "providecommandx"
949 || t.cs() == "DeclareRobustCommand"
950 || t.cs() == "DeclareRobustCommandx"
951 || t.cs() == "ProvideTextCommandDefault"
952 || t.cs() == "DeclareMathAccent") {
954 if (p.next_token().character() == '*') {
958 string const name = p.verbatim_item();
959 string const opt1 = p.getFullOpt();
960 string const opt2 = p.getFullOpt();
961 string const body = p.verbatim_item();
963 if (name == "\\rmdefault")
964 if (is_known(body, known_roman_fonts))
966 if (name == "\\sfdefault")
967 if (is_known(body, known_sans_fonts))
969 if (name == "\\ttdefault")
970 if (is_known(body, known_typewriter_fonts))
971 h_font_typewriter = body;
972 if (name == "\\familydefault") {
973 string family = body;
974 // remove leading "\"
975 h_font_default_family = family.erase(0,1);
978 // Add the command to the known commands
979 add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
981 // only non-lyxspecific stuff
982 if (!in_lyx_preamble) {
984 ss << '\\' << t.cs();
987 ss << '{' << name << '}' << opt1 << opt2
988 << '{' << body << "}";
989 h_preamble << ss.str();
991 ostream & out = in_preamble ? h_preamble : os;
992 out << "\\" << t.cs() << "{" << name << "}"
993 << opts << "{" << body << "}";
998 else if (t.cs() == "documentclass") {
999 vector<string>::iterator it;
1000 vector<string> opts = split_options(p.getArg('[', ']'));
1001 handle_opt(opts, known_fontsizes, h_paperfontsize);
1002 delete_opt(opts, known_fontsizes);
1003 // delete "pt" at the end
1004 string::size_type i = h_paperfontsize.find("pt");
1005 if (i != string::npos)
1006 h_paperfontsize.erase(i);
1007 // The documentclass options are always parsed before the options
1008 // of the babel call so that a language cannot overwrite the babel
1010 handle_opt(opts, known_languages, h_language);
1011 delete_opt(opts, known_languages);
1013 // paper orientation
1014 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
1015 h_paperorientation = "landscape";
1019 if ((it = find(opts.begin(), opts.end(), "oneside"))
1024 if ((it = find(opts.begin(), opts.end(), "twoside"))
1030 if ((it = find(opts.begin(), opts.end(), "onecolumn"))
1032 h_papercolumns = "1";
1035 if ((it = find(opts.begin(), opts.end(), "twocolumn"))
1037 h_papercolumns = "2";
1041 // some size options are know to any document classes, other sizes
1042 // are handled by the \geometry command of the geometry package
1043 handle_opt(opts, known_class_paper_sizes, h_papersize);
1044 delete_opt(opts, known_class_paper_sizes);
1045 // the remaining options
1046 h_options = join(opts, ",");
1047 // FIXME This does not work for classes that have a
1048 // different name in LyX than in LaTeX
1049 h_textclass = p.getArg('{', '}');
1052 else if (t.cs() == "usepackage") {
1053 string const options = p.getArg('[', ']');
1054 string const name = p.getArg('{', '}');
1055 vector<string> vecnames;
1056 split(name, vecnames, ',');
1057 vector<string>::const_iterator it = vecnames.begin();
1058 vector<string>::const_iterator end = vecnames.end();
1059 for (; it != end; ++it)
1060 handle_package(p, trimSpaceAndEol(*it), options,
1064 else if (t.cs() == "inputencoding") {
1065 string const encoding = p.getArg('{','}');
1066 h_inputencoding = encoding;
1067 p.setEncoding(encoding);
1070 else if (t.cs() == "newenvironment") {
1071 string const name = p.getArg('{', '}');
1072 string const opt1 = p.getFullOpt();
1073 string const opt2 = p.getFullOpt();
1074 string const beg = p.verbatim_item();
1075 string const end = p.verbatim_item();
1076 if (!in_lyx_preamble) {
1077 h_preamble << "\\newenvironment{" << name
1078 << '}' << opt1 << opt2 << '{'
1079 << beg << "}{" << end << '}';
1081 add_known_environment(name, opt1, !opt2.empty(),
1082 from_utf8(beg), from_utf8(end));
1086 else if (t.cs() == "def") {
1087 string name = p.get_token().cs();
1088 while (p.next_token().cat() != catBegin)
1089 name += p.get_token().cs();
1090 if (!in_lyx_preamble)
1091 h_preamble << "\\def\\" << name << '{'
1092 << p.verbatim_item() << "}";
1095 else if (t.cs() == "newcolumntype") {
1096 string const name = p.getArg('{', '}');
1097 trimSpaceAndEol(name);
1099 string opts = p.getOpt();
1100 if (!opts.empty()) {
1101 istringstream is(string(opts, 1));
1104 special_columns[name[0]] = nargs;
1105 h_preamble << "\\newcolumntype{" << name << "}";
1107 h_preamble << "[" << nargs << "]";
1108 h_preamble << "{" << p.verbatim_item() << "}";
1111 else if (t.cs() == "setcounter") {
1112 string const name = p.getArg('{', '}');
1113 string const content = p.getArg('{', '}');
1114 if (name == "secnumdepth")
1115 h_secnumdepth = content;
1116 else if (name == "tocdepth")
1117 h_tocdepth = content;
1119 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
1122 else if (t.cs() == "setlength") {
1123 string const name = p.verbatim_item();
1124 string const content = p.verbatim_item();
1125 // the paragraphs are only not indented when \parindent is set to zero
1126 if (name == "\\parindent" && content != "") {
1127 if (content[0] == '0')
1128 h_paragraph_separation = "skip";
1130 h_paragraph_indentation = translate_len(content);
1131 } else if (name == "\\parskip") {
1132 if (content == "\\smallskipamount")
1133 h_defskip = "smallskip";
1134 else if (content == "\\medskipamount")
1135 h_defskip = "medskip";
1136 else if (content == "\\bigskipamount")
1137 h_defskip = "bigskip";
1139 h_defskip = content;
1141 h_preamble << "\\setlength{" << name << "}{" << content << "}";
1144 else if (t.cs() == "onehalfspacing")
1145 h_spacing = "onehalf";
1147 else if (t.cs() == "doublespacing")
1148 h_spacing = "double";
1150 else if (t.cs() == "setstretch")
1151 h_spacing = "other " + p.verbatim_item();
1153 else if (t.cs() == "begin") {
1154 string const name = p.getArg('{', '}');
1155 if (name == "document")
1157 h_preamble << "\\begin{" << name << "}";
1160 else if (t.cs() == "geometry") {
1161 h_use_geometry = "true";
1162 vector<string> opts = split_options(p.getArg('{', '}'));
1163 vector<string>::iterator it;
1164 // paper orientation
1165 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
1166 h_paperorientation = "landscape";
1170 handle_opt(opts, known_paper_sizes, h_papersize);
1171 delete_opt(opts, known_paper_sizes);
1173 char const * const * margin = known_paper_margins;
1175 for (; *margin; ++margin) {
1177 // search for the "=" in e.g. "lmargin=2cm" to get the value
1178 for(size_t i = 0; i != opts.size(); i++) {
1179 if (opts.at(i).find(*margin) != string::npos) {
1180 string::size_type pos = opts.at(i).find("=");
1181 string value = opts.at(i).substr(pos + 1);
1182 string name = known_coded_paper_margins[k];
1183 h_margins += "\\" + name + " " + value + "\n";
1189 else if (t.cs() == "definecolor") {
1190 string const color = p.getArg('{', '}');
1191 string const space = p.getArg('{', '}');
1192 string const value = p.getArg('{', '}');
1193 if (color == "document_fontcolor" && space == "rgb") {
1194 RGBColor c(RGBColorFromLaTeX(value));
1195 h_fontcolor = X11hexname(c);
1196 } else if (color == "note_fontcolor" && space == "rgb") {
1197 RGBColor c(RGBColorFromLaTeX(value));
1198 h_notefontcolor = X11hexname(c);
1199 } else if (color == "page_backgroundcolor" && space == "rgb") {
1200 RGBColor c(RGBColorFromLaTeX(value));
1201 h_backgroundcolor = X11hexname(c);
1202 } else if (color == "shadecolor" && space == "rgb") {
1203 RGBColor c(RGBColorFromLaTeX(value));
1204 h_boxbgcolor = X11hexname(c);
1206 h_preamble << "\\definecolor{" << color
1207 << "}{" << space << "}{" << value
1212 else if (t.cs() == "jurabibsetup") {
1213 // FIXME p.getArg('{', '}') is most probably wrong (it
1214 // does not handle nested braces).
1215 // Use p.verbatim_item() instead.
1216 vector<string> jurabibsetup =
1217 split_options(p.getArg('{', '}'));
1218 // add jurabibsetup to the jurabib package options
1219 add_package("jurabib", jurabibsetup);
1220 if (!jurabibsetup.empty()) {
1221 h_preamble << "\\jurabibsetup{"
1222 << join(jurabibsetup, ",") << '}';
1226 else if (t.cs() == "hypersetup") {
1227 vector<string> hypersetup =
1228 split_options(p.verbatim_item());
1229 // add hypersetup to the hyperref package options
1230 handle_hyperref(hypersetup);
1231 if (!hypersetup.empty()) {
1232 h_preamble << "\\hypersetup{"
1233 << join(hypersetup, ",") << '}';
1237 else if (is_known(t.cs(), known_if_3arg_commands)) {
1238 // prevent misparsing of \usepackage if it is used
1239 // as an argument (see e.g. our own output of
1240 // \@ifundefined above)
1241 string const arg1 = p.verbatim_item();
1242 string const arg2 = p.verbatim_item();
1243 string const arg3 = p.verbatim_item();
1244 // test case \@ifundefined{date}{}{\date{}}
1245 if (arg1 == "date" && arg2.empty() && arg3 == "\\date{}") {
1246 h_suppress_date = "true";
1247 // test case \@ifundefined{definecolor}{\usepackage{color}}{}
1248 // because we could pollute the preamble with it in roundtrips
1249 } else if (arg1 == "definecolor" && arg2 == "\\usepackage{color}"
1251 ifundefined_color_set = true;
1252 } else if (!in_lyx_preamble) {
1253 h_preamble << t.asInput()
1254 << '{' << arg1 << '}'
1255 << '{' << arg2 << '}'
1256 << '{' << arg3 << '}';
1260 else if (is_known(t.cs(), known_if_commands)) {
1261 // must not parse anything in conditional code, since
1262 // LyX would output the parsed contents unconditionally
1263 if (!in_lyx_preamble)
1264 h_preamble << t.asInput();
1265 handle_if(p, in_lyx_preamble);
1268 else if (!t.cs().empty() && !in_lyx_preamble)
1269 h_preamble << '\\' << t.cs();
1272 // remove the whitespace
1275 // Force textclass if the user wanted it
1276 if (!forceclass.empty())
1277 h_textclass = forceclass;
1278 if (noweb_mode && !prefixIs(h_textclass, "literate-"))
1279 h_textclass.insert(0, "literate-");
1280 tc.setName(h_textclass);
1282 cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
1285 if (h_papersides.empty()) {
1288 h_papersides = ss.str();
1290 end_preamble(os, tc);
1294 /// translates a babel language name to a LyX language name
1295 string babel2lyx(string const & language)
1297 char const * const * where = is_known(language, known_languages);
1299 return known_coded_languages[where - known_languages];