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 = "default";
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 /* We could do the following for better round trip support,
553 * but this makes the document less portable, so I skip it:
554 if (h_fontencoding == lyxrc.fontenc)
555 h_fontencoding = "global";
560 else if (name == "inputenc" || name == "luainputenc") {
561 // h_inputencoding is only set when there is not more than one
562 // inputenc option because otherwise h_inputencoding must be
563 // set to "auto" (the default encoding of the document language)
564 // Therefore check for the "," character.
565 // It is also only set when there is not more then one babel
566 // language option but this is handled in the routine for babel.
567 if (opts.find(",") == string::npos && one_language == true)
568 h_inputencoding = opts;
569 if (!options.empty())
570 p.setEncoding(options.back());
574 else if (name == "makeidx")
577 else if (name == "prettyref")
580 else if (name == "varioref")
583 else if (name == "verbatim")
586 else if (name == "nomencl")
589 else if (name == "textcomp")
592 else if (name == "url")
595 else if (name == "subscript")
598 else if (name == "color") {
599 // with the following command this package is only loaded when needed for
600 // undefined colors, since we only support the predefined colors
601 // only add it if not yet added
602 if (!ifundefined_color_set)
603 h_preamble << "\\@ifundefined{definecolor}\n {\\usepackage{color}}{}\n";
606 else if (name == "graphicx")
609 else if (name == "setspace")
613 // do not ignore as long as we don't support all commands (e.g. \xout is missing)
614 else if (name == "ulem")
618 else if (name == "geometry")
619 ; // Ignore this, the geometry settings are made by the \geometry
620 // command. This command is handled below.
622 else if (is_known(name, known_languages))
625 else if (name == "natbib") {
626 h_cite_engine = "natbib_authoryear";
627 vector<string>::iterator it =
628 find(options.begin(), options.end(), "authoryear");
629 if (it != options.end())
632 it = find(options.begin(), options.end(), "numbers");
633 if (it != options.end()) {
634 h_cite_engine = "natbib_numerical";
640 else if (name == "jurabib")
641 h_cite_engine = "jurabib";
643 else if (name == "hyperref")
644 handle_hyperref(options);
646 else if (!in_lyx_preamble) {
648 h_preamble << "\\usepackage{" << name << "}";
650 h_preamble << "\\usepackage[" << opts << "]{"
656 // We need to do something with the options...
657 if (!options.empty())
658 cerr << "Ignoring options '" << join(options, ",")
659 << "' of package " << name << '.' << endl;
661 // remove the whitespace
666 void handle_if(Parser & p, bool in_lyx_preamble)
669 Token t = p.get_token();
670 if (t.cat() == catEscape &&
671 is_known(t.cs(), known_if_commands))
672 handle_if(p, in_lyx_preamble);
674 if (!in_lyx_preamble)
675 h_preamble << t.asInput();
676 if (t.cat() == catEscape && t.cs() == "fi")
683 void end_preamble(ostream & os, TextClass const & /*textclass*/)
685 // translate from babel to LyX names
686 h_language = babel2lyx(h_language);
688 // set the quote language
689 // LyX only knows the following quotes languages:
690 // english, swedish, german, polish, french and danish
691 // (quotes for "japanese" and "chinese-traditional" are missing because
692 // they wouldn't be useful: http://www.lyx.org/trac/ticket/6383)
693 // conversion list taken from
694 // http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
695 // (quotes for kazakh and interlingua are unknown)
697 if (h_language == "danish")
698 h_quotes_language = "danish";
700 else if (is_known(h_language, known_french_quotes_languages))
701 h_quotes_language = "french";
703 else if (is_known(h_language, known_german_quotes_languages))
704 h_quotes_language = "german";
706 else if (is_known(h_language, known_polish_quotes_languages))
707 h_quotes_language = "polish";
709 else if (is_known(h_language, known_swedish_quotes_languages))
710 h_quotes_language = "swedish";
712 else if (is_known(h_language, known_english_quotes_languages))
713 h_quotes_language = "english";
715 // output the LyX file settings
716 os << "#LyX file created by tex2lyx " << PACKAGE_VERSION << "\n"
717 << "\\lyxformat " << LYX_FORMAT << '\n'
718 << "\\begin_document\n"
719 << "\\begin_header\n"
720 << "\\textclass " << h_textclass << "\n";
721 if (!h_preamble.str().empty())
722 os << "\\begin_preamble\n" << h_preamble.str() << "\n\\end_preamble\n";
723 if (!h_options.empty())
724 os << "\\options " << h_options << "\n";
725 os << "\\use_default_options " << h_use_default_options << "\n"
726 << modules_placeholder
727 << "\\language " << h_language << "\n"
728 << "\\language_package " << h_language_package << "\n"
729 << "\\inputencoding " << h_inputencoding << "\n"
730 << "\\fontencoding " << h_fontencoding << "\n"
731 << "\\font_roman " << h_font_roman << "\n"
732 << "\\font_sans " << h_font_sans << "\n"
733 << "\\font_typewriter " << h_font_typewriter << "\n"
734 << "\\font_default_family " << h_font_default_family << "\n"
735 << "\\font_sc " << h_font_sc << "\n"
736 << "\\font_osf " << h_font_osf << "\n"
737 << "\\font_sf_scale " << h_font_sf_scale << "\n"
738 << "\\font_tt_scale " << h_font_tt_scale << "\n"
739 << "\\graphics " << h_graphics << "\n";
740 if (!h_float_placement.empty())
741 os << "\\float_placement " << h_float_placement << "\n";
742 os << "\\paperfontsize " << h_paperfontsize << "\n"
743 << "\\spacing " << h_spacing << "\n"
744 << "\\use_hyperref " << h_use_hyperref << '\n';
745 if (h_use_hyperref == "1") {
746 if (!h_pdf_title.empty())
747 os << "\\pdf_title \"" << h_pdf_title << "\"\n";
748 if (!h_pdf_author.empty())
749 os << "\\pdf_author \"" << h_pdf_author << "\"\n";
750 if (!h_pdf_subject.empty())
751 os << "\\pdf_subject \"" << h_pdf_subject << "\"\n";
752 if (!h_pdf_keywords.empty())
753 os << "\\pdf_keywords \"" << h_pdf_keywords << "\"\n";
754 os << "\\pdf_bookmarks " << h_pdf_bookmarks << "\n"
755 "\\pdf_bookmarksnumbered " << h_pdf_bookmarksnumbered << "\n"
756 "\\pdf_bookmarksopen " << h_pdf_bookmarksopen << "\n"
757 "\\pdf_bookmarksopenlevel " << h_pdf_bookmarksopenlevel << "\n"
758 "\\pdf_breaklinks " << h_pdf_breaklinks << "\n"
759 "\\pdf_pdfborder " << h_pdf_pdfborder << "\n"
760 "\\pdf_colorlinks " << h_pdf_colorlinks << "\n"
761 "\\pdf_backref " << h_pdf_backref << "\n"
762 "\\pdf_pdfusetitle " << h_pdf_pdfusetitle << '\n';
763 if (!h_pdf_pagemode.empty())
764 os << "\\pdf_pagemode " << h_pdf_pagemode << '\n';
765 if (!h_pdf_quoted_options.empty())
766 os << "\\pdf_quoted_options \"" << h_pdf_quoted_options << "\"\n";
768 os << "\\papersize " << h_papersize << "\n"
769 << "\\use_geometry " << h_use_geometry << "\n"
770 << "\\use_amsmath " << h_use_amsmath << "\n"
771 << "\\use_esint " << h_use_esint << "\n"
772 << "\\use_mhchem " << h_use_mhchem << "\n"
773 << "\\use_mathdots " << h_use_mathdots << "\n"
774 << "\\use_undertilde " << h_use_undertilde << "\n"
775 << "\\cite_engine " << h_cite_engine << "\n"
776 << "\\use_bibtopic " << h_use_bibtopic << "\n"
777 << "\\paperorientation " << h_paperorientation << '\n'
778 << "\\suppress_date " << h_suppress_date << '\n'
779 << "\\use_refstyle " << h_use_refstyle << '\n';
780 if (!h_fontcolor.empty())
781 os << "\\fontcolor " << h_fontcolor << '\n';
782 if (!h_notefontcolor.empty())
783 os << "\\notefontcolor " << h_notefontcolor << '\n';
784 if (!h_backgroundcolor.empty())
785 os << "\\backgroundcolor " << h_backgroundcolor << '\n';
786 if (!h_boxbgcolor.empty())
787 os << "\\boxbgcolor " << h_boxbgcolor << '\n';
789 << "\\secnumdepth " << h_secnumdepth << "\n"
790 << "\\tocdepth " << h_tocdepth << "\n"
791 << "\\paragraph_separation " << h_paragraph_separation << "\n";
792 if (h_paragraph_separation == "skip")
793 os << "\\defskip " << h_defskip << "\n";
795 os << "\\paragraph_indentation " << h_paragraph_indentation << "\n";
796 os << "\\quotes_language " << h_quotes_language << "\n"
797 << "\\papercolumns " << h_papercolumns << "\n"
798 << "\\papersides " << h_papersides << "\n"
799 << "\\paperpagestyle " << h_paperpagestyle << "\n";
800 if (!h_listings_params.empty())
801 os << "\\listings_params " << h_listings_params << "\n";
802 os << "\\tracking_changes " << h_tracking_changes << "\n"
803 << "\\output_changes " << h_output_changes << "\n"
804 << "\\html_math_output " << h_html_math_output << "\n"
805 << "\\html_css_as_file " << h_html_css_as_file << "\n"
806 << "\\html_be_strict " << h_html_be_strict << "\n"
807 << "\\end_header\n\n"
809 // clear preamble for subdocuments
813 } // anonymous namespace
816 void parse_preamble(Parser & p, ostream & os,
817 string const & forceclass, TeX2LyXDocClass & tc)
819 // initialize fixed types
820 special_columns['D'] = 3;
821 bool is_full_document = false;
822 bool is_lyx_file = false;
823 bool in_lyx_preamble = false;
825 // determine whether this is a full document or a fragment for inclusion
827 Token const & t = p.get_token();
829 if (t.cat() == catEscape && t.cs() == "documentclass") {
830 is_full_document = true;
836 while (is_full_document && p.good()) {
837 Token const & t = p.get_token();
840 cerr << "t: " << t << "\n";
846 if (!in_lyx_preamble &&
847 (t.cat() == catLetter ||
848 t.cat() == catSuper ||
850 t.cat() == catOther ||
851 t.cat() == catMath ||
852 t.cat() == catActive ||
853 t.cat() == catBegin ||
855 t.cat() == catAlign ||
856 t.cat() == catParameter))
857 h_preamble << t.cs();
859 else if (!in_lyx_preamble &&
860 (t.cat() == catSpace || t.cat() == catNewline))
861 h_preamble << t.asInput();
863 else if (t.cat() == catComment) {
864 static regex const islyxfile("%% LyX .* created this file");
865 static regex const usercommands("User specified LaTeX commands");
867 string const comment = t.asInput();
869 // magically switch encoding default if it looks like XeLaTeX
870 static string const magicXeLaTeX =
871 "% This document must be compiled with XeLaTeX ";
872 if (comment.size() > magicXeLaTeX.size()
873 && comment.substr(0, magicXeLaTeX.size()) == magicXeLaTeX
874 && h_inputencoding == "auto") {
875 cerr << "XeLaTeX comment found, switching to UTF8\n";
876 h_inputencoding = "utf8";
879 if (regex_search(comment, sub, islyxfile)) {
881 in_lyx_preamble = true;
882 } else if (is_lyx_file
883 && regex_search(comment, sub, usercommands))
884 in_lyx_preamble = false;
885 else if (!in_lyx_preamble)
886 h_preamble << t.asInput();
889 else if (t.cs() == "pagestyle")
890 h_paperpagestyle = p.verbatim_item();
892 else if (t.cs() == "date") {
893 if (p.verbatim_item().empty())
894 h_suppress_date = "true";
897 else if (t.cs() == "makeatletter") {
898 // LyX takes care of this
899 p.setCatCode('@', catLetter);
902 else if (t.cs() == "makeatother") {
903 // LyX takes care of this
904 p.setCatCode('@', catOther);
907 else if (t.cs() == "newcommand" || t.cs() == "newcommandx"
908 || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
909 || t.cs() == "providecommand" || t.cs() == "providecommandx"
910 || t.cs() == "DeclareRobustCommand"
911 || t.cs() == "DeclareRobustCommandx"
912 || t.cs() == "ProvideTextCommandDefault"
913 || t.cs() == "DeclareMathAccent") {
915 if (p.next_token().character() == '*') {
919 string const name = p.verbatim_item();
920 string const opt1 = p.getFullOpt();
921 string const opt2 = p.getFullOpt();
922 string const body = p.verbatim_item();
924 if (name == "\\rmdefault")
925 if (is_known(body, known_roman_fonts))
927 if (name == "\\sfdefault")
928 if (is_known(body, known_sans_fonts))
930 if (name == "\\ttdefault")
931 if (is_known(body, known_typewriter_fonts))
932 h_font_typewriter = body;
933 if (name == "\\familydefault") {
934 string family = body;
935 // remove leading "\"
936 h_font_default_family = family.erase(0,1);
939 // Add the command to the known commands
940 add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
942 // only non-lyxspecific stuff
943 if (!in_lyx_preamble) {
945 ss << '\\' << t.cs();
948 ss << '{' << name << '}' << opt1 << opt2
949 << '{' << body << "}";
950 h_preamble << ss.str();
952 ostream & out = in_preamble ? h_preamble : os;
953 out << "\\" << t.cs() << "{" << name << "}"
954 << opts << "{" << body << "}";
959 else if (t.cs() == "documentclass") {
960 vector<string>::iterator it;
961 vector<string> opts = split_options(p.getArg('[', ']'));
962 handle_opt(opts, known_fontsizes, h_paperfontsize);
963 delete_opt(opts, known_fontsizes);
964 // delete "pt" at the end
965 string::size_type i = h_paperfontsize.find("pt");
966 if (i != string::npos)
967 h_paperfontsize.erase(i);
968 // The documentclass options are always parsed before the options
969 // of the babel call so that a language cannot overwrite the babel
971 handle_opt(opts, known_languages, h_language);
972 delete_opt(opts, known_languages);
975 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
976 h_paperorientation = "landscape";
980 if ((it = find(opts.begin(), opts.end(), "oneside"))
985 if ((it = find(opts.begin(), opts.end(), "twoside"))
991 if ((it = find(opts.begin(), opts.end(), "onecolumn"))
993 h_papercolumns = "1";
996 if ((it = find(opts.begin(), opts.end(), "twocolumn"))
998 h_papercolumns = "2";
1002 // some size options are know to any document classes, other sizes
1003 // are handled by the \geometry command of the geometry package
1004 handle_opt(opts, known_class_paper_sizes, h_papersize);
1005 delete_opt(opts, known_class_paper_sizes);
1006 // the remaining options
1007 h_options = join(opts, ",");
1008 // FIXME This does not work for classes that have a
1009 // different name in LyX than in LaTeX
1010 h_textclass = p.getArg('{', '}');
1013 else if (t.cs() == "usepackage") {
1014 string const options = p.getArg('[', ']');
1015 string const name = p.getArg('{', '}');
1016 vector<string> vecnames;
1017 split(name, vecnames, ',');
1018 vector<string>::const_iterator it = vecnames.begin();
1019 vector<string>::const_iterator end = vecnames.end();
1020 for (; it != end; ++it)
1021 handle_package(p, trimSpaceAndEol(*it), options,
1025 else if (t.cs() == "inputencoding") {
1026 string const encoding = p.getArg('{','}');
1027 h_inputencoding = encoding;
1028 p.setEncoding(encoding);
1031 else if (t.cs() == "newenvironment") {
1032 string const name = p.getArg('{', '}');
1033 string const opt1 = p.getFullOpt();
1034 string const opt2 = p.getFullOpt();
1035 string const beg = p.verbatim_item();
1036 string const end = p.verbatim_item();
1037 if (!in_lyx_preamble) {
1038 h_preamble << "\\newenvironment{" << name
1039 << '}' << opt1 << opt2 << '{'
1040 << beg << "}{" << end << '}';
1042 add_known_environment(name, opt1, !opt2.empty(),
1043 from_utf8(beg), from_utf8(end));
1047 else if (t.cs() == "def") {
1048 string name = p.get_token().cs();
1049 while (p.next_token().cat() != catBegin)
1050 name += p.get_token().cs();
1051 if (!in_lyx_preamble)
1052 h_preamble << "\\def\\" << name << '{'
1053 << p.verbatim_item() << "}";
1056 else if (t.cs() == "newcolumntype") {
1057 string const name = p.getArg('{', '}');
1058 trimSpaceAndEol(name);
1060 string opts = p.getOpt();
1061 if (!opts.empty()) {
1062 istringstream is(string(opts, 1));
1065 special_columns[name[0]] = nargs;
1066 h_preamble << "\\newcolumntype{" << name << "}";
1068 h_preamble << "[" << nargs << "]";
1069 h_preamble << "{" << p.verbatim_item() << "}";
1072 else if (t.cs() == "setcounter") {
1073 string const name = p.getArg('{', '}');
1074 string const content = p.getArg('{', '}');
1075 if (name == "secnumdepth")
1076 h_secnumdepth = content;
1077 else if (name == "tocdepth")
1078 h_tocdepth = content;
1080 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
1083 else if (t.cs() == "setlength") {
1084 string const name = p.verbatim_item();
1085 string const content = p.verbatim_item();
1086 // the paragraphs are only not indented when \parindent is set to zero
1087 if (name == "\\parindent" && content != "") {
1088 if (content[0] == '0')
1089 h_paragraph_separation = "skip";
1091 h_paragraph_indentation = translate_len(content);
1092 } else if (name == "\\parskip") {
1093 if (content == "\\smallskipamount")
1094 h_defskip = "smallskip";
1095 else if (content == "\\medskipamount")
1096 h_defskip = "medskip";
1097 else if (content == "\\bigskipamount")
1098 h_defskip = "bigskip";
1100 h_defskip = content;
1102 h_preamble << "\\setlength{" << name << "}{" << content << "}";
1105 else if (t.cs() == "onehalfspacing")
1106 h_spacing = "onehalf";
1108 else if (t.cs() == "doublespacing")
1109 h_spacing = "double";
1111 else if (t.cs() == "setstretch")
1112 h_spacing = "other " + p.verbatim_item();
1114 else if (t.cs() == "begin") {
1115 string const name = p.getArg('{', '}');
1116 if (name == "document")
1118 h_preamble << "\\begin{" << name << "}";
1121 else if (t.cs() == "geometry") {
1122 h_use_geometry = "true";
1123 vector<string> opts = split_options(p.getArg('{', '}'));
1124 vector<string>::iterator it;
1125 // paper orientation
1126 if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
1127 h_paperorientation = "landscape";
1131 handle_opt(opts, known_paper_sizes, h_papersize);
1132 delete_opt(opts, known_paper_sizes);
1134 char const * const * margin = known_paper_margins;
1136 for (; *margin; ++margin) {
1138 // search for the "=" in e.g. "lmargin=2cm" to get the value
1139 for(size_t i = 0; i != opts.size(); i++) {
1140 if (opts.at(i).find(*margin) != string::npos) {
1141 string::size_type pos = opts.at(i).find("=");
1142 string value = opts.at(i).substr(pos + 1);
1143 string name = known_coded_paper_margins[k];
1144 h_margins += "\\" + name + " " + value + "\n";
1150 else if (t.cs() == "definecolor") {
1151 string const color = p.getArg('{', '}');
1152 string const space = p.getArg('{', '}');
1153 string const value = p.getArg('{', '}');
1154 if (color == "document_fontcolor" && space == "rgb") {
1155 RGBColor c(RGBColorFromLaTeX(value));
1156 h_fontcolor = X11hexname(c);
1157 } else if (color == "note_fontcolor" && space == "rgb") {
1158 RGBColor c(RGBColorFromLaTeX(value));
1159 h_notefontcolor = X11hexname(c);
1160 } else if (color == "page_backgroundcolor" && space == "rgb") {
1161 RGBColor c(RGBColorFromLaTeX(value));
1162 h_backgroundcolor = X11hexname(c);
1163 } else if (color == "shadecolor" && space == "rgb") {
1164 RGBColor c(RGBColorFromLaTeX(value));
1165 h_boxbgcolor = X11hexname(c);
1167 h_preamble << "\\definecolor{" << color
1168 << "}{" << space << "}{" << value
1173 else if (t.cs() == "jurabibsetup") {
1174 // FIXME p.getArg('{', '}') is most probably wrong (it
1175 // does not handle nested braces).
1176 // Use p.verbatim_item() instead.
1177 vector<string> jurabibsetup =
1178 split_options(p.getArg('{', '}'));
1179 // add jurabibsetup to the jurabib package options
1180 add_package("jurabib", jurabibsetup);
1181 if (!jurabibsetup.empty()) {
1182 h_preamble << "\\jurabibsetup{"
1183 << join(jurabibsetup, ",") << '}';
1187 else if (t.cs() == "hypersetup") {
1188 vector<string> hypersetup =
1189 split_options(p.verbatim_item());
1190 // add hypersetup to the hyperref package options
1191 handle_hyperref(hypersetup);
1192 if (!hypersetup.empty()) {
1193 h_preamble << "\\hypersetup{"
1194 << join(hypersetup, ",") << '}';
1198 else if (is_known(t.cs(), known_if_3arg_commands)) {
1199 // prevent misparsing of \usepackage if it is used
1200 // as an argument (see e.g. our own output of
1201 // \@ifundefined above)
1202 string const arg1 = p.verbatim_item();
1203 string const arg2 = p.verbatim_item();
1204 string const arg3 = p.verbatim_item();
1205 // test case \@ifundefined{date}{}{\date{}}
1206 if (arg1 == "date" && arg2.empty() && arg3 == "\\date{}") {
1207 h_suppress_date = "true";
1208 // test case \@ifundefined{definecolor}{\usepackage{color}}{}
1209 // because we could pollute the preamble with it in roundtrips
1210 } else if (arg1 == "definecolor" && arg2 == "\\usepackage{color}"
1212 ifundefined_color_set = true;
1213 } else if (!in_lyx_preamble) {
1214 h_preamble << t.asInput()
1215 << '{' << arg1 << '}'
1216 << '{' << arg2 << '}'
1217 << '{' << arg3 << '}';
1221 else if (is_known(t.cs(), known_if_commands)) {
1222 // must not parse anything in conditional code, since
1223 // LyX would output the parsed contents unconditionally
1224 if (!in_lyx_preamble)
1225 h_preamble << t.asInput();
1226 handle_if(p, in_lyx_preamble);
1229 else if (!t.cs().empty() && !in_lyx_preamble)
1230 h_preamble << '\\' << t.cs();
1233 // remove the whitespace
1236 // Force textclass if the user wanted it
1237 if (!forceclass.empty())
1238 h_textclass = forceclass;
1239 if (noweb_mode && !prefixIs(h_textclass, "literate-"))
1240 h_textclass.insert(0, "literate-");
1241 tc.setName(h_textclass);
1243 cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
1246 if (h_papersides.empty()) {
1249 h_papersides = ss.str();
1251 end_preamble(os, tc);
1255 /// translates a babel language name to a LyX language name
1256 string babel2lyx(string const & language)
1258 char const * const * where = is_known(language, known_languages);
1260 return known_coded_languages[where - known_languages];