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 char const * const known_fontsizes[] = { "10pt", "11pt", "12pt", 0 };
136 const char * const known_roman_fonts[] = { "ae", "bookman", "charter",
137 "cmr", "fourier", "lmodern", "mathpazo", "mathptmx", "newcent", 0};
139 const char * const known_sans_fonts[] = { "avant", "berasans", "cmbr", "cmss",
140 "helvet", "lmss", 0};
142 const char * const known_typewriter_fonts[] = { "beramono", "cmtl", "cmtt",
143 "courier", "lmtt", "luximono", "fourier", "lmodern", "mathpazo", "mathptmx",
146 const char * const known_paper_sizes[] = { "a0paper", "b0paper", "c0paper",
147 "a1paper", "b1paper", "c1paper", "a2paper", "b2paper", "c2paper", "a3paper",
148 "b3paper", "c3paper", "a4paper", "b4paper", "c4paper", "a5paper", "b5paper",
149 "c5paper", "a6paper", "b6paper", "c6paper", "executivepaper", "legalpaper",
150 "letterpaper", "b0j", "b1j", "b2j", "b3j", "b4j", "b5j", "b6j", 0};
152 const char * const known_class_paper_sizes[] = { "a4paper", "a5paper",
153 "executivepaper", "legalpaper", "letterpaper", 0};
155 const char * const known_paper_margins[] = { "lmargin", "tmargin", "rmargin",
156 "bmargin", "headheight", "headsep", "footskip", "columnsep", 0};
158 const char * const known_coded_paper_margins[] = { "leftmargin", "topmargin",
159 "rightmargin", "bottommargin", "headheight", "headsep", "footskip",
162 /// commands that can start an \if...\else...\endif sequence
163 const char * const known_if_commands[] = {"if", "ifarydshln", "ifbraket",
164 "ifcancel", "ifcolortbl", "ifeurosym", "ifmarginnote", "ifmmode", "ifpdf",
165 "ifsidecap", "ifupgreek", 0};
167 /// conditional commands with three arguments like \@ifundefined{}{}{}
168 const char * const known_if_3arg_commands[] = {"@ifundefined", "IfFileExists",
172 ostringstream h_preamble;
173 string h_textclass = "article";
174 string h_use_default_options = "false";
176 string h_language = "english";
177 string h_language_package = "default";
178 string h_fontencoding = "global";
179 string h_font_roman = "default";
180 string h_font_sans = "default";
181 string h_font_typewriter = "default";
182 string h_font_default_family = "default";
183 string h_font_sc = "false";
184 string h_font_osf = "false";
185 string h_font_sf_scale = "100";
186 string h_font_tt_scale = "100";
187 string h_graphics = "default";
188 string h_float_placement;
189 string h_paperfontsize = "default";
190 string h_spacing = "single";
191 string h_use_hyperref = "0";
194 string h_pdf_subject;
195 string h_pdf_keywords;
196 string h_pdf_bookmarks = "1";
197 string h_pdf_bookmarksnumbered = "0";
198 string h_pdf_bookmarksopen = "0";
199 string h_pdf_bookmarksopenlevel = "1";
200 string h_pdf_breaklinks = "0";
201 string h_pdf_pdfborder = "0";
202 string h_pdf_colorlinks = "0";
203 string h_pdf_backref = "section";
204 string h_pdf_pdfusetitle = "1";
205 string h_pdf_pagemode;
206 string h_pdf_quoted_options;
207 string h_papersize = "default";
208 string h_use_geometry = "false";
209 string h_use_amsmath = "1";
210 string h_use_esint = "1";
211 string h_use_mhchem = "0";
212 string h_use_mathdots = "0";
213 string h_use_undertilde = "0";
214 string h_cite_engine = "basic";
215 string h_use_bibtopic = "false";
216 string h_paperorientation = "portrait";
217 string h_suppress_date = "false";
218 string h_use_refstyle = "0";
219 string h_backgroundcolor;
222 string h_notefontcolor;
223 string h_secnumdepth = "3";
224 string h_tocdepth = "3";
225 string h_defskip = "medskip";
226 string h_paragraph_indentation = "default";
227 string h_quotes_language = "english";
228 string h_papercolumns = "1";
230 string h_paperpagestyle = "default";
231 string h_listings_params;
232 string h_tracking_changes = "false";
233 string h_output_changes = "false";
234 string h_html_math_output = "0";
235 string h_html_css_as_file = "0";
236 string h_html_be_strict = "false";
240 // returns true if at least one of the options in what has been found
241 bool handle_opt(vector<string> & opts, char const * const * what, string & target)
247 // the last language option is the document language (for babel and LyX)
248 // the last size option is the document font size
249 vector<string>::iterator it;
250 vector<string>::iterator position = opts.begin();
251 for (; *what; ++what) {
252 it = find(opts.begin(), opts.end(), *what);
253 if (it != opts.end()) {
254 if (it >= position) {
265 void delete_opt(vector<string> & opts, char const * const * what)
270 // remove found options from the list
271 // do this after handle_opt to avoid potential memory leaks
272 vector<string>::iterator it;
273 for (; *what; ++what) {
274 it = find(opts.begin(), opts.end(), *what);
275 if (it != opts.end())
282 * Split a package options string (keyval format) into a vector.
284 * authorformat=smallcaps,
286 * titleformat=colonsep,
287 * bibformat={tabular,ibidem,numbered}
289 vector<string> split_options(string const & input)
291 vector<string> options;
295 Token const & t = p.get_token();
296 if (t.asInput() == ",") {
297 options.push_back(trimSpaceAndEol(option));
299 } else if (t.asInput() == "=") {
302 if (p.next_token().asInput() == "{")
303 option += '{' + p.getArg('{', '}') + '}';
304 } else if (t.cat() != catSpace)
305 option += t.asInput();
309 options.push_back(trimSpaceAndEol(option));
316 * Retrieve a keyval option "name={value with=sign}" named \p name from
317 * \p options and return the value.
318 * The found option is also removed from \p options.
320 string process_keyval_opt(vector<string> & options, string name)
322 for (size_t i = 0; i < options.size(); ++i) {
323 vector<string> option;
324 split(options[i], option, '=');
325 if (option.size() < 2)
327 if (option[0] == name) {
328 options.erase(options.begin() + i);
329 option.erase(option.begin());
330 return join(option, "=");
338 * Add package \p name with options \p options to used_packages.
339 * Remove options from \p options that we don't want to output.
341 void add_package(string const & name, vector<string> & options)
343 // every package inherits the global options
344 if (used_packages.find(name) == used_packages.end())
345 used_packages[name] = split_options(h_options);
347 vector<string> & v = used_packages[name];
348 v.insert(v.end(), options.begin(), options.end());
349 if (name == "jurabib") {
350 // Don't output the order argument (see the cite command
351 // handling code in text.cpp).
352 vector<string>::iterator end =
353 remove(options.begin(), options.end(), "natbiborder");
354 end = remove(options.begin(), end, "jurabiborder");
355 options.erase(end, options.end());
360 // Given is a string like "scaled=0.9", return 0.9 * 100
361 string const scale_as_percentage(string const & scale)
363 string::size_type pos = scale.find('=');
364 if (pos != string::npos) {
365 string value = scale.substr(pos + 1);
367 return convert<string>(100 * convert<double>(value));
369 // If the input string didn't match our expectations.
370 // return the default value "100"
375 string remove_braces(string const & value)
379 if (value[0] == '{' && value[value.length()-1] == '}')
380 return value.substr(1, value.length()-2);
385 void handle_hyperref(vector<string> & options)
387 // FIXME swallow inputencoding changes that might surround the
388 // hyperref setup if it was written by LyX
389 h_use_hyperref = "1";
390 // swallow "unicode=true", since LyX does always write that
391 vector<string>::iterator it =
392 find(options.begin(), options.end(), "unicode=true");
393 if (it != options.end())
395 it = find(options.begin(), options.end(), "pdfusetitle");
396 if (it != options.end()) {
397 h_pdf_pdfusetitle = "1";
400 string bookmarks = process_keyval_opt(options, "bookmarks");
401 if (bookmarks == "true")
402 h_pdf_bookmarks = "1";
403 else if (bookmarks == "false")
404 h_pdf_bookmarks = "0";
405 if (h_pdf_bookmarks == "1") {
406 string bookmarksnumbered =
407 process_keyval_opt(options, "bookmarksnumbered");
408 if (bookmarksnumbered == "true")
409 h_pdf_bookmarksnumbered = "1";
410 else if (bookmarksnumbered == "false")
411 h_pdf_bookmarksnumbered = "0";
412 string bookmarksopen =
413 process_keyval_opt(options, "bookmarksopen");
414 if (bookmarksopen == "true")
415 h_pdf_bookmarksopen = "1";
416 else if (bookmarksopen == "false")
417 h_pdf_bookmarksopen = "0";
418 if (h_pdf_bookmarksopen == "1") {
419 string bookmarksopenlevel =
420 process_keyval_opt(options, "bookmarksopenlevel");
421 if (!bookmarksopenlevel.empty())
422 h_pdf_bookmarksopenlevel = bookmarksopenlevel;
425 string breaklinks = process_keyval_opt(options, "breaklinks");
426 if (breaklinks == "true")
427 h_pdf_breaklinks = "1";
428 else if (breaklinks == "false")
429 h_pdf_breaklinks = "0";
430 string pdfborder = process_keyval_opt(options, "pdfborder");
431 if (pdfborder == "{0 0 0}")
432 h_pdf_pdfborder = "1";
433 else if (pdfborder == "{0 0 1}")
434 h_pdf_pdfborder = "0";
435 string backref = process_keyval_opt(options, "backref");
436 if (!backref.empty())
437 h_pdf_backref = backref;
438 string colorlinks = process_keyval_opt(options, "colorlinks");
439 if (colorlinks == "true")
440 h_pdf_colorlinks = "1";
441 else if (colorlinks == "false")
442 h_pdf_colorlinks = "0";
443 string pdfpagemode = process_keyval_opt(options, "pdfpagemode");
444 if (!pdfpagemode.empty())
445 h_pdf_pagemode = pdfpagemode;
446 string pdftitle = process_keyval_opt(options, "pdftitle");
447 if (!pdftitle.empty()) {
448 h_pdf_title = remove_braces(pdftitle);
450 string pdfauthor = process_keyval_opt(options, "pdfauthor");
451 if (!pdfauthor.empty()) {
452 h_pdf_author = remove_braces(pdfauthor);
454 string pdfsubject = process_keyval_opt(options, "pdfsubject");
455 if (!pdfsubject.empty())
456 h_pdf_subject = remove_braces(pdfsubject);
457 string pdfkeywords = process_keyval_opt(options, "pdfkeywords");
458 if (!pdfkeywords.empty())
459 h_pdf_keywords = remove_braces(pdfkeywords);
460 if (!options.empty()) {
461 if (!h_pdf_quoted_options.empty())
462 h_pdf_quoted_options += ',';
463 h_pdf_quoted_options += join(options, ",");
469 void handle_package(Parser &p, string const & name, string const & opts,
470 bool in_lyx_preamble)
472 vector<string> options = split_options(opts);
473 add_package(name, options);
477 if (is_known(name, known_roman_fonts)) {
482 if (name == "fourier") {
483 h_font_roman = "utopia";
484 // when font uses real small capitals
485 if (opts == "expert")
489 if (name == "mathpazo")
490 h_font_roman = "palatino";
492 if (name == "mathptmx")
493 h_font_roman = "times";
496 if (is_known(name, known_sans_fonts)) {
500 h_font_sf_scale = scale_as_percentage(scale);
505 if (is_known(name, known_typewriter_fonts)) {
506 h_font_typewriter = name;
509 h_font_tt_scale = scale_as_percentage(scale);
513 // font uses old-style figure
517 else if (name == "amsmath" || name == "amssymb")
520 else if (name == "esint")
523 else if (name == "mhchem")
526 else if (name == "mathdots")
527 h_use_mathdots = "2";
529 else if (name == "undertilde")
530 h_use_undertilde = "2";
532 else if (name == "babel") {
533 // we have to do nothing if babel is loaded without any options, otherwise
534 // we would pollute the preamble with this call in every roundtrip
536 // check if more than one option was used - used later for inputenc
537 // in case inputenc is parsed before babel, set the encoding to auto
538 if (options.begin() != options.end() - 1) {
539 one_language = false;
540 h_inputencoding = "auto";
542 // babel takes the last language of the option of its \usepackage
543 // call as document language. If there is no such language option, the
544 // last language in the documentclass options is used.
545 handle_opt(options, known_languages, h_language);
546 delete_opt(options, known_languages);
550 else if (name == "fontenc") {
551 h_fontencoding = getStringFromVector(options, ",");
552 // as of version LyX 2.0 "T1" is equal to the setting "global"
553 if (h_fontencoding == "T1")
554 h_fontencoding = "global";
558 else if (name == "inputenc" || name == "luainputenc") {
559 // h_inputencoding is only set when there is not more than one
560 // inputenc option because otherwise h_inputencoding must be
561 // set to "auto" (the default encoding of the document language)
562 // Therefore check for the "," character.
563 // It is also only set when there is not more then one babel
564 // language option but this is handled in the routine for babel.
565 if (opts.find(",") == string::npos && one_language == true)
566 h_inputencoding = opts;
567 if (!options.empty())
568 p.setEncoding(options.back());
572 else if (name == "makeidx")
575 else if (name == "prettyref")
578 else if (name == "varioref")
581 else if (name == "verbatim")
584 else if (name == "nomencl")
587 else if (name == "textcomp")
590 else if (name == "url")
593 else if (name == "subscript")
596 else if (name == "color") {
597 // with the following command this package is only loaded when needed for
598 // undefined colors, since we only support the predefined colors
599 // only add it if not yet added
600 if (!ifundefined_color_set)
601 h_preamble << "\\@ifundefined{definecolor}\n {\\usepackage{color}}{}\n";
604 else if (name == "graphicx")
607 else if (name == "setspace")
611 // do not ignore as long as we don't support all commands (e.g. \xout is missing)
612 else if (name == "ulem")
616 else if (name == "geometry")
617 ; // Ignore this, the geometry settings are made by the \geometry
618 // command. This command is handled below.
620 else if (is_known(name, known_languages))
623 else if (name == "natbib") {
624 h_cite_engine = "natbib_authoryear";
625 vector<string>::iterator it =
626 find(options.begin(), options.end(), "authoryear");
627 if (it != options.end())
630 it = find(options.begin(), options.end(), "numbers");
631 if (it != options.end()) {
632 h_cite_engine = "natbib_numerical";
638 else if (name == "jurabib")
639 h_cite_engine = "jurabib";
641 else if (name == "hyperref")
642 handle_hyperref(options);
644 else if (!in_lyx_preamble) {
646 h_preamble << "\\usepackage{" << name << "}";
648 h_preamble << "\\usepackage[" << opts << "]{"
654 // We need to do something with the options...
655 if (!options.empty())
656 cerr << "Ignoring options '" << join(options, ",")
657 << "' of package " << name << '.' << endl;
659 // remove the whitespace
664 void handle_if(Parser & p, bool in_lyx_preamble)
667 Token t = p.get_token();
668 if (t.cat() == catEscape &&
669 is_known(t.cs(), known_if_commands))
670 handle_if(p, in_lyx_preamble);
672 if (!in_lyx_preamble)
673 h_preamble << t.asInput();
674 if (t.cat() == catEscape && t.cs() == "fi")
681 void end_preamble(ostream & os, TextClass const & /*textclass*/)
683 // translate from babel to LyX names
684 h_language = babel2lyx(h_language);
686 // set the quote language
687 // LyX only knows the following quotes languages:
688 // english, swedish, german, polish, french and danish
689 // (quotes for "japanese" and "chinese-traditional" are missing because
690 // they wouldn't be useful: http://www.lyx.org/trac/ticket/6383)
691 // conversion list taken from
692 // http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
693 // (quotes for kazakh and interlingua are unknown)
695 if (h_language == "danish")
696 h_quotes_language = "danish";
698 else if (is_known(h_language, known_french_quotes_languages))
699 h_quotes_language = "french";
701 else if (is_known(h_language, known_german_quotes_languages))
702 h_quotes_language = "german";
704 else if (is_known(h_language, known_polish_quotes_languages))
705 h_quotes_language = "polish";
707 else if (is_known(h_language, known_swedish_quotes_languages))
708 h_quotes_language = "swedish";
710 else if (is_known(h_language, known_english_quotes_languages))
711 h_quotes_language = "english";
713 // output the LyX file settings
714 os << "#LyX file created by tex2lyx " << PACKAGE_VERSION << "\n"
715 << "\\lyxformat " << LYX_FORMAT << '\n'
716 << "\\begin_document\n"
717 << "\\begin_header\n"
718 << "\\textclass " << h_textclass << "\n";
719 if (!h_preamble.str().empty())
720 os << "\\begin_preamble\n" << h_preamble.str() << "\n\\end_preamble\n";
721 if (!h_options.empty())
722 os << "\\options " << h_options << "\n";
723 os << "\\use_default_options " << h_use_default_options << "\n"
724 << modules_placeholder
725 << "\\language " << h_language << "\n"
726 << "\\language_package " << h_language_package << "\n"
727 << "\\inputencoding " << h_inputencoding << "\n"
728 << "\\fontencoding " << h_fontencoding << "\n"
729 << "\\font_roman " << h_font_roman << "\n"
730 << "\\font_sans " << h_font_sans << "\n"
731 << "\\font_typewriter " << h_font_typewriter << "\n"
732 << "\\font_default_family " << h_font_default_family << "\n"
733 << "\\font_sc " << h_font_sc << "\n"
734 << "\\font_osf " << h_font_osf << "\n"
735 << "\\font_sf_scale " << h_font_sf_scale << "\n"
736 << "\\font_tt_scale " << h_font_tt_scale << "\n"
737 << "\\graphics " << h_graphics << "\n";
738 if (!h_float_placement.empty())
739 os << "\\float_placement " << h_float_placement << "\n";
740 os << "\\paperfontsize " << h_paperfontsize << "\n"
741 << "\\spacing " << h_spacing << "\n"
742 << "\\use_hyperref " << h_use_hyperref << '\n';
743 if (h_use_hyperref == "1") {
744 if (!h_pdf_title.empty())
745 os << "\\pdf_title \"" << h_pdf_title << "\"\n";
746 if (!h_pdf_author.empty())
747 os << "\\pdf_author \"" << h_pdf_author << "\"\n";
748 if (!h_pdf_subject.empty())
749 os << "\\pdf_subject \"" << h_pdf_subject << "\"\n";
750 if (!h_pdf_keywords.empty())
751 os << "\\pdf_keywords \"" << h_pdf_keywords << "\"\n";
752 os << "\\pdf_bookmarks " << h_pdf_bookmarks << "\n"
753 "\\pdf_bookmarksnumbered " << h_pdf_bookmarksnumbered << "\n"
754 "\\pdf_bookmarksopen " << h_pdf_bookmarksopen << "\n"
755 "\\pdf_bookmarksopenlevel " << h_pdf_bookmarksopenlevel << "\n"
756 "\\pdf_breaklinks " << h_pdf_breaklinks << "\n"
757 "\\pdf_pdfborder " << h_pdf_pdfborder << "\n"
758 "\\pdf_colorlinks " << h_pdf_colorlinks << "\n"
759 "\\pdf_backref " << h_pdf_backref << "\n"
760 "\\pdf_pdfusetitle " << h_pdf_pdfusetitle << '\n';
761 if (!h_pdf_pagemode.empty())
762 os << "\\pdf_pagemode " << h_pdf_pagemode << '\n';
763 if (!h_pdf_quoted_options.empty())
764 os << "\\pdf_quoted_options \"" << h_pdf_quoted_options << "\"\n";
766 os << "\\papersize " << h_papersize << "\n"
767 << "\\use_geometry " << h_use_geometry << "\n"
768 << "\\use_amsmath " << h_use_amsmath << "\n"
769 << "\\use_esint " << h_use_esint << "\n"
770 << "\\use_mhchem " << h_use_mhchem << "\n"
771 << "\\use_mathdots " << h_use_mathdots << "\n"
772 << "\\use_undertilde " << h_use_undertilde << "\n"
773 << "\\cite_engine " << h_cite_engine << "\n"
774 << "\\use_bibtopic " << h_use_bibtopic << "\n"
775 << "\\paperorientation " << h_paperorientation << '\n'
776 << "\\suppress_date " << h_suppress_date << '\n'
777 << "\\use_refstyle " << h_use_refstyle << '\n';
778 if (!h_fontcolor.empty())
779 os << "\\fontcolor " << h_fontcolor << '\n';
780 if (!h_notefontcolor.empty())
781 os << "\\notefontcolor " << h_notefontcolor << '\n';
782 if (!h_backgroundcolor.empty())
783 os << "\\backgroundcolor " << h_backgroundcolor << '\n';
784 if (!h_boxbgcolor.empty())
785 os << "\\boxbgcolor " << h_boxbgcolor << '\n';
787 << "\\secnumdepth " << h_secnumdepth << "\n"
788 << "\\tocdepth " << h_tocdepth << "\n"
789 << "\\paragraph_separation " << h_paragraph_separation << "\n";
790 if (h_paragraph_separation == "skip")
791 os << "\\defskip " << h_defskip << "\n";
793 os << "\\paragraph_indentation " << h_paragraph_indentation << "\n";
794 os << "\\quotes_language " << h_quotes_language << "\n"
795 << "\\papercolumns " << h_papercolumns << "\n"
796 << "\\papersides " << h_papersides << "\n"
797 << "\\paperpagestyle " << h_paperpagestyle << "\n";
798 if (!h_listings_params.empty())
799 os << "\\listings_params " << h_listings_params << "\n";
800 os << "\\tracking_changes " << h_tracking_changes << "\n"
801 << "\\output_changes " << h_output_changes << "\n"
802 << "\\html_math_output " << h_html_math_output << "\n"
803 << "\\html_css_as_file " << h_html_css_as_file << "\n"
804 << "\\html_be_strict " << h_html_be_strict << "\n"
805 << "\\end_header\n\n"
807 // clear preamble for subdocuments
811 } // anonymous namespace
814 void parse_preamble(Parser & p, ostream & os,
815 string const & forceclass, TeX2LyXDocClass & tc)
817 // initialize fixed types
818 special_columns['D'] = 3;
819 bool is_full_document = false;
820 bool is_lyx_file = false;
821 bool in_lyx_preamble = false;
823 // determine whether this is a full document or a fragment for inclusion
825 Token const & t = p.get_token();
827 if (t.cat() == catEscape && t.cs() == "documentclass") {
828 is_full_document = true;
834 while (is_full_document && p.good()) {
835 Token const & t = p.get_token();
838 cerr << "t: " << t << "\n";
844 if (!in_lyx_preamble &&
845 (t.cat() == catLetter ||
846 t.cat() == catSuper ||
848 t.cat() == catOther ||
849 t.cat() == catMath ||
850 t.cat() == catActive ||
851 t.cat() == catBegin ||
853 t.cat() == catAlign ||
854 t.cat() == catParameter))
855 h_preamble << t.cs();
857 else if (!in_lyx_preamble &&
858 (t.cat() == catSpace || t.cat() == catNewline))
859 h_preamble << t.asInput();
861 else if (t.cat() == catComment) {
862 static regex const islyxfile("%% LyX .* created this file");
863 static regex const usercommands("User specified LaTeX commands");
865 string const comment = t.asInput();
867 // magically switch encoding default if it looks like XeLaTeX
868 static string const magicXeLaTeX =
869 "% This document must be compiled with XeLaTeX ";
870 if (comment.size() > magicXeLaTeX.size()
871 && comment.substr(0, magicXeLaTeX.size()) == magicXeLaTeX
872 && h_inputencoding == "auto") {
873 cerr << "XeLaTeX comment found, switching to UTF8\n";
874 h_inputencoding = "utf8";
877 if (regex_search(comment, sub, islyxfile)) {
879 in_lyx_preamble = true;
880 } else if (is_lyx_file
881 && regex_search(comment, sub, usercommands))
882 in_lyx_preamble = false;
883 else if (!in_lyx_preamble)
884 h_preamble << t.asInput();
887 else if (t.cs() == "pagestyle")
888 h_paperpagestyle = p.verbatim_item();
890 else if (t.cs() == "date") {
891 if (p.verbatim_item().empty())
892 h_suppress_date = "true";
895 else if (t.cs() == "makeatletter") {
896 // LyX takes care of this
897 p.setCatCode('@', catLetter);
900 else if (t.cs() == "makeatother") {
901 // LyX takes care of this
902 p.setCatCode('@', catOther);
905 else if (t.cs() == "newcommand" || t.cs() == "newcommandx"
906 || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
907 || t.cs() == "providecommand" || t.cs() == "providecommandx"
908 || t.cs() == "DeclareRobustCommand"
909 || t.cs() == "DeclareRobustCommandx"
910 || t.cs() == "ProvideTextCommandDefault"
911 || t.cs() == "DeclareMathAccent") {
913 if (p.next_token().character() == '*') {
917 string const name = p.verbatim_item();
918 string const opt1 = p.getFullOpt();
919 string const opt2 = p.getFullOpt();
920 string const body = p.verbatim_item();
922 if (name == "\\rmdefault")
923 if (is_known(body, known_roman_fonts))
925 if (name == "\\sfdefault")
926 if (is_known(body, known_sans_fonts))
928 if (name == "\\ttdefault")
929 if (is_known(body, known_typewriter_fonts))
930 h_font_typewriter = body;
931 if (name == "\\familydefault") {
932 string family = body;
933 // remove leading "\"
934 h_font_default_family = family.erase(0,1);
937 // Add the command to the known commands
938 add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
940 // only non-lyxspecific stuff
941 if (!in_lyx_preamble) {
943 ss << '\\' << t.cs();
946 ss << '{' << name << '}' << opt1 << opt2
947 << '{' << body << "}";
948 h_preamble << ss.str();
950 ostream & out = in_preamble ? h_preamble : os;
951 out << "\\" << t.cs() << "{" << name << "}"
952 << opts << "{" << body << "}";
957 else if (t.cs() == "documentclass") {
958 vector<string>::iterator it;
959 vector<string> opts = split_options(p.getArg('[', ']'));
960 handle_opt(opts, known_fontsizes, h_paperfontsize);
961 delete_opt(opts, known_fontsizes);
962 // delete "pt" at the end
963 string::size_type i = h_paperfontsize.find("pt");
964 if (i != string::npos)
965 h_paperfontsize.erase(i);
966 // The documentclass options are always parsed before the options
967 // of the babel call so that a language cannot overwrite the babel
969 handle_opt(opts, known_languages, h_language);
970 delete_opt(opts, known_languages);
973 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
974 h_paperorientation = "landscape";
978 if ((it = find(opts.begin(), opts.end(), "oneside"))
983 if ((it = find(opts.begin(), opts.end(), "twoside"))
989 if ((it = find(opts.begin(), opts.end(), "onecolumn"))
991 h_papercolumns = "1";
994 if ((it = find(opts.begin(), opts.end(), "twocolumn"))
996 h_papercolumns = "2";
1000 // some size options are know to any document classes, other sizes
1001 // are handled by the \geometry command of the geometry package
1002 handle_opt(opts, known_class_paper_sizes, h_papersize);
1003 delete_opt(opts, known_class_paper_sizes);
1004 // the remaining options
1005 h_options = join(opts, ",");
1006 // FIXME This does not work for classes that have a
1007 // different name in LyX than in LaTeX
1008 h_textclass = p.getArg('{', '}');
1011 else if (t.cs() == "usepackage") {
1012 string const options = p.getArg('[', ']');
1013 string const name = p.getArg('{', '}');
1014 vector<string> vecnames;
1015 split(name, vecnames, ',');
1016 vector<string>::const_iterator it = vecnames.begin();
1017 vector<string>::const_iterator end = vecnames.end();
1018 for (; it != end; ++it)
1019 handle_package(p, trimSpaceAndEol(*it), options,
1023 else if (t.cs() == "inputencoding") {
1024 string const encoding = p.getArg('{','}');
1025 h_inputencoding = encoding;
1026 p.setEncoding(encoding);
1029 else if (t.cs() == "newenvironment") {
1030 string const name = p.getArg('{', '}');
1031 string const opt1 = p.getFullOpt();
1032 string const opt2 = p.getFullOpt();
1033 string const beg = p.verbatim_item();
1034 string const end = p.verbatim_item();
1035 if (!in_lyx_preamble) {
1036 h_preamble << "\\newenvironment{" << name
1037 << '}' << opt1 << opt2 << '{'
1038 << beg << "}{" << end << '}';
1040 add_known_environment(name, opt1, !opt2.empty(),
1041 from_utf8(beg), from_utf8(end));
1045 else if (t.cs() == "def") {
1046 string name = p.get_token().cs();
1047 while (p.next_token().cat() != catBegin)
1048 name += p.get_token().cs();
1049 if (!in_lyx_preamble)
1050 h_preamble << "\\def\\" << name << '{'
1051 << p.verbatim_item() << "}";
1054 else if (t.cs() == "newcolumntype") {
1055 string const name = p.getArg('{', '}');
1056 trimSpaceAndEol(name);
1058 string opts = p.getOpt();
1059 if (!opts.empty()) {
1060 istringstream is(string(opts, 1));
1063 special_columns[name[0]] = nargs;
1064 h_preamble << "\\newcolumntype{" << name << "}";
1066 h_preamble << "[" << nargs << "]";
1067 h_preamble << "{" << p.verbatim_item() << "}";
1070 else if (t.cs() == "setcounter") {
1071 string const name = p.getArg('{', '}');
1072 string const content = p.getArg('{', '}');
1073 if (name == "secnumdepth")
1074 h_secnumdepth = content;
1075 else if (name == "tocdepth")
1076 h_tocdepth = content;
1078 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
1081 else if (t.cs() == "setlength") {
1082 string const name = p.verbatim_item();
1083 string const content = p.verbatim_item();
1084 // the paragraphs are only not indented when \parindent is set to zero
1085 if (name == "\\parindent" && content != "") {
1086 if (content[0] == '0')
1087 h_paragraph_separation = "skip";
1089 h_paragraph_indentation = translate_len(content);
1090 } else if (name == "\\parskip") {
1091 if (content == "\\smallskipamount")
1092 h_defskip = "smallskip";
1093 else if (content == "\\medskipamount")
1094 h_defskip = "medskip";
1095 else if (content == "\\bigskipamount")
1096 h_defskip = "bigskip";
1098 h_defskip = content;
1100 h_preamble << "\\setlength{" << name << "}{" << content << "}";
1103 else if (t.cs() == "onehalfspacing")
1104 h_spacing = "onehalf";
1106 else if (t.cs() == "doublespacing")
1107 h_spacing = "double";
1109 else if (t.cs() == "setstretch")
1110 h_spacing = "other " + p.verbatim_item();
1112 else if (t.cs() == "begin") {
1113 string const name = p.getArg('{', '}');
1114 if (name == "document")
1116 h_preamble << "\\begin{" << name << "}";
1119 else if (t.cs() == "geometry") {
1120 h_use_geometry = "true";
1121 vector<string> opts = split_options(p.getArg('{', '}'));
1122 vector<string>::iterator it;
1123 // paper orientation
1124 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
1125 h_paperorientation = "landscape";
1129 handle_opt(opts, known_paper_sizes, h_papersize);
1130 delete_opt(opts, known_paper_sizes);
1132 char const * const * margin = known_paper_margins;
1134 for (; *margin; ++margin) {
1136 // search for the "=" in e.g. "lmargin=2cm" to get the value
1137 for(size_t i = 0; i != opts.size(); i++) {
1138 if (opts.at(i).find(*margin) != string::npos) {
1139 string::size_type pos = opts.at(i).find("=");
1140 string value = opts.at(i).substr(pos + 1);
1141 string name = known_coded_paper_margins[k];
1142 h_margins += "\\" + name + " " + value + "\n";
1148 else if (t.cs() == "definecolor") {
1149 string const color = p.getArg('{', '}');
1150 string const space = p.getArg('{', '}');
1151 string const value = p.getArg('{', '}');
1152 if (color == "document_fontcolor" && space == "rgb") {
1153 RGBColor c(RGBColorFromLaTeX(value));
1154 h_fontcolor = X11hexname(c);
1155 } else if (color == "note_fontcolor" && space == "rgb") {
1156 RGBColor c(RGBColorFromLaTeX(value));
1157 h_notefontcolor = X11hexname(c);
1158 } else if (color == "page_backgroundcolor" && space == "rgb") {
1159 RGBColor c(RGBColorFromLaTeX(value));
1160 h_backgroundcolor = X11hexname(c);
1161 } else if (color == "shadecolor" && space == "rgb") {
1162 RGBColor c(RGBColorFromLaTeX(value));
1163 h_boxbgcolor = X11hexname(c);
1165 h_preamble << "\\definecolor{" << color
1166 << "}{" << space << "}{" << value
1171 else if (t.cs() == "jurabibsetup") {
1172 // FIXME p.getArg('{', '}') is most probably wrong (it
1173 // does not handle nested braces).
1174 // Use p.verbatim_item() instead.
1175 vector<string> jurabibsetup =
1176 split_options(p.getArg('{', '}'));
1177 // add jurabibsetup to the jurabib package options
1178 add_package("jurabib", jurabibsetup);
1179 if (!jurabibsetup.empty()) {
1180 h_preamble << "\\jurabibsetup{"
1181 << join(jurabibsetup, ",") << '}';
1185 else if (t.cs() == "hypersetup") {
1186 vector<string> hypersetup =
1187 split_options(p.verbatim_item());
1188 // add hypersetup to the hyperref package options
1189 handle_hyperref(hypersetup);
1190 if (!hypersetup.empty()) {
1191 h_preamble << "\\hypersetup{"
1192 << join(hypersetup, ",") << '}';
1196 else if (is_known(t.cs(), known_if_3arg_commands)) {
1197 // prevent misparsing of \usepackage if it is used
1198 // as an argument (see e.g. our own output of
1199 // \@ifundefined above)
1200 string const arg1 = p.verbatim_item();
1201 string const arg2 = p.verbatim_item();
1202 string const arg3 = p.verbatim_item();
1203 // test case \@ifundefined{date}{}{\date{}}
1204 if (arg1 == "date" && arg2.empty() && arg3 == "\\date{}") {
1205 h_suppress_date = "true";
1206 // test case \@ifundefined{definecolor}{\usepackage{color}}{}
1207 // because we could pollute the preamble with it in roundtrips
1208 } else if (arg1 == "definecolor" && arg2 == "\\usepackage{color}"
1210 ifundefined_color_set = true;
1211 } else if (!in_lyx_preamble) {
1212 h_preamble << t.asInput()
1213 << '{' << arg1 << '}'
1214 << '{' << arg2 << '}'
1215 << '{' << arg3 << '}';
1219 else if (is_known(t.cs(), known_if_commands)) {
1220 // must not parse anything in conditional code, since
1221 // LyX would output the parsed contents unconditionally
1222 if (!in_lyx_preamble)
1223 h_preamble << t.asInput();
1224 handle_if(p, in_lyx_preamble);
1227 else if (!t.cs().empty() && !in_lyx_preamble)
1228 h_preamble << '\\' << t.cs();
1231 // remove the whitespace
1234 // Force textclass if the user wanted it
1235 if (!forceclass.empty())
1236 h_textclass = forceclass;
1237 if (noweb_mode && !prefixIs(h_textclass, "literate-"))
1238 h_textclass.insert(0, "literate-");
1239 tc.setName(h_textclass);
1241 cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
1244 if (h_papersides.empty()) {
1247 h_papersides = ss.str();
1249 end_preamble(os, tc);
1253 /// translates a babel language name to a LyX language name
1254 string babel2lyx(string const & language)
1256 char const * const * where = is_known(language, known_languages);
1258 return known_coded_languages[where - known_languages];