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() == "makeatletter") {
919 // LyX takes care of this
920 p.setCatCode('@', catLetter);
923 else if (t.cs() == "makeatother") {
924 // LyX takes care of this
925 p.setCatCode('@', catOther);
928 else if (t.cs() == "newcommand" || t.cs() == "newcommandx"
929 || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
930 || t.cs() == "providecommand" || t.cs() == "providecommandx"
931 || t.cs() == "DeclareRobustCommand"
932 || t.cs() == "DeclareRobustCommandx"
933 || t.cs() == "ProvideTextCommandDefault"
934 || t.cs() == "DeclareMathAccent") {
936 if (p.next_token().character() == '*') {
940 string const name = p.verbatim_item();
941 string const opt1 = p.getFullOpt();
942 string const opt2 = p.getFullOpt();
943 string const body = p.verbatim_item();
945 if (name == "\\rmdefault")
946 if (is_known(body, known_roman_fonts))
948 if (name == "\\sfdefault")
949 if (is_known(body, known_sans_fonts))
951 if (name == "\\ttdefault")
952 if (is_known(body, known_typewriter_fonts))
953 h_font_typewriter = body;
954 if (name == "\\familydefault") {
955 string family = body;
956 // remove leading "\"
957 h_font_default_family = family.erase(0,1);
960 // Add the command to the known commands
961 add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
963 // only non-lyxspecific stuff
964 if (!in_lyx_preamble) {
966 ss << '\\' << t.cs();
969 ss << '{' << name << '}' << opt1 << opt2
970 << '{' << body << "}";
971 h_preamble << ss.str();
973 ostream & out = in_preamble ? h_preamble : os;
974 out << "\\" << t.cs() << "{" << name << "}"
975 << opts << "{" << body << "}";
980 else if (t.cs() == "documentclass") {
981 vector<string>::iterator it;
982 vector<string> opts = split_options(p.getArg('[', ']'));
983 handle_opt(opts, known_fontsizes, h_paperfontsize);
984 delete_opt(opts, known_fontsizes);
985 // delete "pt" at the end
986 string::size_type i = h_paperfontsize.find("pt");
987 if (i != string::npos)
988 h_paperfontsize.erase(i);
989 // The documentclass options are always parsed before the options
990 // of the babel call so that a language cannot overwrite the babel
992 handle_opt(opts, known_languages, h_language);
993 delete_opt(opts, known_languages);
996 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
997 h_paperorientation = "landscape";
1001 if ((it = find(opts.begin(), opts.end(), "oneside"))
1006 if ((it = find(opts.begin(), opts.end(), "twoside"))
1012 if ((it = find(opts.begin(), opts.end(), "onecolumn"))
1014 h_papercolumns = "1";
1017 if ((it = find(opts.begin(), opts.end(), "twocolumn"))
1019 h_papercolumns = "2";
1023 // some size options are know to any document classes, other sizes
1024 // are handled by the \geometry command of the geometry package
1025 handle_opt(opts, known_class_paper_sizes, h_papersize);
1026 delete_opt(opts, known_class_paper_sizes);
1027 // the remaining options
1028 h_options = join(opts, ",");
1029 // FIXME This does not work for classes that have a
1030 // different name in LyX than in LaTeX
1031 h_textclass = p.getArg('{', '}');
1034 else if (t.cs() == "usepackage") {
1035 string const options = p.getArg('[', ']');
1036 string const name = p.getArg('{', '}');
1037 vector<string> vecnames;
1038 split(name, vecnames, ',');
1039 vector<string>::const_iterator it = vecnames.begin();
1040 vector<string>::const_iterator end = vecnames.end();
1041 for (; it != end; ++it)
1042 handle_package(p, trimSpaceAndEol(*it), options,
1046 else if (t.cs() == "inputencoding") {
1047 string const encoding = p.getArg('{','}');
1048 h_inputencoding = encoding;
1049 p.setEncoding(encoding);
1052 else if (t.cs() == "newenvironment") {
1053 string const name = p.getArg('{', '}');
1054 string const opt1 = p.getFullOpt();
1055 string const opt2 = p.getFullOpt();
1056 string const beg = p.verbatim_item();
1057 string const end = p.verbatim_item();
1058 if (!in_lyx_preamble) {
1059 h_preamble << "\\newenvironment{" << name
1060 << '}' << opt1 << opt2 << '{'
1061 << beg << "}{" << end << '}';
1063 add_known_environment(name, opt1, !opt2.empty(),
1064 from_utf8(beg), from_utf8(end));
1068 else if (t.cs() == "def") {
1069 string name = p.get_token().cs();
1070 while (p.next_token().cat() != catBegin)
1071 name += p.get_token().cs();
1072 if (!in_lyx_preamble)
1073 h_preamble << "\\def\\" << name << '{'
1074 << p.verbatim_item() << "}";
1077 else if (t.cs() == "newcolumntype") {
1078 string const name = p.getArg('{', '}');
1079 trimSpaceAndEol(name);
1081 string opts = p.getOpt();
1082 if (!opts.empty()) {
1083 istringstream is(string(opts, 1));
1086 special_columns[name[0]] = nargs;
1087 h_preamble << "\\newcolumntype{" << name << "}";
1089 h_preamble << "[" << nargs << "]";
1090 h_preamble << "{" << p.verbatim_item() << "}";
1093 else if (t.cs() == "setcounter") {
1094 string const name = p.getArg('{', '}');
1095 string const content = p.getArg('{', '}');
1096 if (name == "secnumdepth")
1097 h_secnumdepth = content;
1098 else if (name == "tocdepth")
1099 h_tocdepth = content;
1101 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
1104 else if (t.cs() == "setlength") {
1105 string const name = p.verbatim_item();
1106 string const content = p.verbatim_item();
1107 // the paragraphs are only not indented when \parindent is set to zero
1108 if (name == "\\parindent" && content != "") {
1109 if (content[0] == '0')
1110 h_paragraph_separation = "skip";
1112 h_paragraph_indentation = translate_len(content);
1113 } else if (name == "\\parskip") {
1114 if (content == "\\smallskipamount")
1115 h_defskip = "smallskip";
1116 else if (content == "\\medskipamount")
1117 h_defskip = "medskip";
1118 else if (content == "\\bigskipamount")
1119 h_defskip = "bigskip";
1121 h_defskip = content;
1123 h_preamble << "\\setlength{" << name << "}{" << content << "}";
1126 else if (t.cs() == "onehalfspacing")
1127 h_spacing = "onehalf";
1129 else if (t.cs() == "doublespacing")
1130 h_spacing = "double";
1132 else if (t.cs() == "setstretch")
1133 h_spacing = "other " + p.verbatim_item();
1135 else if (t.cs() == "begin") {
1136 string const name = p.getArg('{', '}');
1137 if (name == "document")
1139 h_preamble << "\\begin{" << name << "}";
1142 else if (t.cs() == "geometry") {
1143 h_use_geometry = "true";
1144 vector<string> opts = split_options(p.getArg('{', '}'));
1145 vector<string>::iterator it;
1146 // paper orientation
1147 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
1148 h_paperorientation = "landscape";
1152 handle_opt(opts, known_paper_sizes, h_papersize);
1153 delete_opt(opts, known_paper_sizes);
1155 char const * const * margin = known_paper_margins;
1157 for (; *margin; ++margin) {
1159 // search for the "=" in e.g. "lmargin=2cm" to get the value
1160 for(size_t i = 0; i != opts.size(); i++) {
1161 if (opts.at(i).find(*margin) != string::npos) {
1162 string::size_type pos = opts.at(i).find("=");
1163 string value = opts.at(i).substr(pos + 1);
1164 string name = known_coded_paper_margins[k];
1165 h_margins += "\\" + name + " " + value + "\n";
1171 else if (t.cs() == "definecolor") {
1172 string const color = p.getArg('{', '}');
1173 string const space = p.getArg('{', '}');
1174 string const value = p.getArg('{', '}');
1175 if (color == "document_fontcolor" && space == "rgb") {
1176 RGBColor c(RGBColorFromLaTeX(value));
1177 h_fontcolor = X11hexname(c);
1178 } else if (color == "note_fontcolor" && space == "rgb") {
1179 RGBColor c(RGBColorFromLaTeX(value));
1180 h_notefontcolor = X11hexname(c);
1181 } else if (color == "page_backgroundcolor" && space == "rgb") {
1182 RGBColor c(RGBColorFromLaTeX(value));
1183 h_backgroundcolor = X11hexname(c);
1184 } else if (color == "shadecolor" && space == "rgb") {
1185 RGBColor c(RGBColorFromLaTeX(value));
1186 h_boxbgcolor = X11hexname(c);
1188 h_preamble << "\\definecolor{" << color
1189 << "}{" << space << "}{" << value
1194 else if (t.cs() == "jurabibsetup") {
1195 // FIXME p.getArg('{', '}') is most probably wrong (it
1196 // does not handle nested braces).
1197 // Use p.verbatim_item() instead.
1198 vector<string> jurabibsetup =
1199 split_options(p.getArg('{', '}'));
1200 // add jurabibsetup to the jurabib package options
1201 add_package("jurabib", jurabibsetup);
1202 if (!jurabibsetup.empty()) {
1203 h_preamble << "\\jurabibsetup{"
1204 << join(jurabibsetup, ",") << '}';
1208 else if (t.cs() == "hypersetup") {
1209 vector<string> hypersetup =
1210 split_options(p.verbatim_item());
1211 // add hypersetup to the hyperref package options
1212 handle_hyperref(hypersetup);
1213 if (!hypersetup.empty()) {
1214 h_preamble << "\\hypersetup{"
1215 << join(hypersetup, ",") << '}';
1219 else if (is_known(t.cs(), known_if_3arg_commands)) {
1220 // prevent misparsing of \usepackage if it is used
1221 // as an argument (see e.g. our own output of
1222 // \@ifundefined above)
1223 string const arg1 = p.verbatim_item();
1224 string const arg2 = p.verbatim_item();
1225 string const arg3 = p.verbatim_item();
1226 // test case \@ifundefined{date}{}{\date{}}
1227 if (arg1 == "date" && arg2.empty() && arg3 == "\\date{}") {
1228 h_suppress_date = "true";
1229 // test case \@ifundefined{definecolor}{\usepackage{color}}{}
1230 // because we could pollute the preamble with it in roundtrips
1231 } else if (arg1 == "definecolor" && arg2 == "\\usepackage{color}"
1233 ifundefined_color_set = true;
1234 } else if (!in_lyx_preamble) {
1235 h_preamble << t.asInput()
1236 << '{' << arg1 << '}'
1237 << '{' << arg2 << '}'
1238 << '{' << arg3 << '}';
1242 else if (is_known(t.cs(), known_if_commands)) {
1243 // must not parse anything in conditional code, since
1244 // LyX would output the parsed contents unconditionally
1245 if (!in_lyx_preamble)
1246 h_preamble << t.asInput();
1247 handle_if(p, in_lyx_preamble);
1250 else if (!t.cs().empty() && !in_lyx_preamble)
1251 h_preamble << '\\' << t.cs();
1254 // remove the whitespace
1257 // Force textclass if the user wanted it
1258 if (!forceclass.empty())
1259 h_textclass = forceclass;
1260 if (noweb_mode && !prefixIs(h_textclass, "literate-"))
1261 h_textclass.insert(0, "literate-");
1262 tc.setName(h_textclass);
1264 cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
1267 if (h_papersides.empty()) {
1270 h_papersides = ss.str();
1272 end_preamble(os, tc);
1276 /// translates a babel language name to a LyX language name
1277 string babel2lyx(string const & language)
1279 char const * const * where = is_known(language, known_languages);
1281 return known_coded_languages[where - known_languages];