1 /**
2  * \file Preamble.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  * \author Uwe Stöhr
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11 
12 // {[(
13 
14 #include <config.h>
15 
16 #include "Preamble.h"
17 #include "tex2lyx.h"
18 
19 #include "Encoding.h"
20 #include "LayoutFile.h"
21 #include "Layout.h"
22 #include "Lexer.h"
23 #include "TextClass.h"
24 #include "version.h"
25 
26 #include "support/convert.h"
27 #include "support/FileName.h"
28 #include "support/filetools.h"
29 #include "support/lstrings.h"
30 
31 #include "support/regex.h"
32 
33 #include <algorithm>
34 #include <iostream>
35 
36 using namespace std;
37 using namespace lyx::support;
38 
39 
40 namespace lyx {
41 
42 Preamble preamble;
43 
44 namespace {
45 
46 // CJK languages are handled in text.cpp, polyglossia languages are listed
47 // further down.
48 /**
49  * known babel language names (including synonyms)
50  * not in standard babel: arabic, arabtex, armenian, belarusian, serbian-latin, thai
51  * please keep this in sync with known_coded_languages line by line!
52  */
53 const char * const known_languages[] = {"acadian", "afrikaans", "albanian",
54 "american", "arabic", "arabtex", "australian", "austrian", "bahasa", "bahasai",
55 "bahasam", "basque", "belarusian", "bosnian", "brazil", "brazilian", "breton", "british",
56 "bulgarian", "canadian", "canadien", "catalan", "croatian", "czech", "danish",
57 "dutch", "english", "esperanto", "estonian", "farsi", "finnish", "francais",
58 "french", "frenchb", "frenchle", "frenchpro", "friulan", "galician", "german", "germanb",
59 "georgian", "greek", "hebrew", "hungarian", "icelandic", "indon", "indonesian",
60 "interlingua", "irish", "italian", "japanese", "kazakh", "kurmanji", "latin",
61 "latvian", "lithuanian", "lowersorbian", "lsorbian", "macedonian", "magyar", "malay", "meyalu",
62 "mongolian", "naustrian", "newzealand", "ngerman", "ngermanb", "norsk", "nswissgerman",
63 "nynorsk", "piedmontese", "polutonikogreek", "polish", "portuges", "portuguese",
64 "romanian", "romansh", "russian", "russianb", "samin", "scottish", "serbian", "serbian-latin",
65 "slovak", "slovene", "spanish", "swedish", "swissgerman", "thai", "turkish", "turkmen",
66 "ukraineb", "ukrainian", "uppersorbian", "UKenglish", "USenglish", "usorbian",
67 "vietnam", "welsh",
68 0};
69 
70 /**
71  * the same as known_languages with .lyx names
72  * please keep this in sync with known_languages line by line!
73  */
74 const char * const known_coded_languages[] = {"french", "afrikaans", "albanian",
75 "american", "arabic_arabi", "arabic_arabtex", "australian", "austrian", "bahasa", "bahasa",
76 "bahasam", "basque", "belarusian", "bosnian", "brazilian", "brazilian", "breton", "british",
77 "bulgarian", "canadian", "canadien", "catalan", "croatian", "czech", "danish",
78 "dutch", "english", "esperanto", "estonian", "farsi", "finnish", "french",
79 "french", "french", "french", "french", "friulan", "galician", "german", "german",
80 "georgian", "greek", "hebrew", "magyar", "icelandic", "bahasa", "bahasa",
81 "interlingua", "irish", "italian", "japanese", "kazakh", "kurmanji", "latin",
82 "latvian", "lithuanian", "lowersorbian", "lowersorbian", "macedonian", "magyar", "bahasam", "bahasam",
83 "mongolian", "naustrian", "newzealand", "ngerman", "ngerman", "norsk", "german-ch",
84 "nynorsk", "piedmontese", "polutonikogreek", "polish", "portuguese", "portuguese",
85 "romanian", "romansh", "russian", "russian", "samin", "scottish", "serbian", "serbian-latin",
86 "slovak", "slovene", "spanish", "swedish", "german-ch-old", "thai", "turkish", "turkmen",
87 "ukrainian", "ukrainian", "uppersorbian", "english", "english", "uppersorbian",
88 "vietnamese", "welsh",
89 0};
90 
91 /// languages with british quotes (.lyx names)
92 const char * const known_british_quotes_languages[] = {"british", "welsh", 0};
93 
94 /// languages with cjk quotes (.lyx names)
95 const char * const known_cjk_quotes_languages[] = {"chinese-traditional",
96 "japanese", "japanese-cjk", 0};
97 
98 /// languages with cjk-angle quotes (.lyx names)
99 const char * const known_cjkangle_quotes_languages[] = {"korean", 0};
100 
101 /// languages with danish quotes (.lyx names)
102 const char * const known_danish_quotes_languages[] = {"danish", 0};
103 
104 /// languages with english quotes (.lyx names)
105 const char * const known_english_quotes_languages[] = {"american", "australian",
106 "bahasa", "bahasam", "brazilian", "canadian", "chinese-simplified", "english",
107 "esperanto", "farsi", "interlingua", "irish", "newzealand", "scottish",
108 "thai", "turkish", "vietnamese", 0};
109 
110 /// languages with french quotes (.lyx names)
111 const char * const known_french_quotes_languages[] = {"ancientgreek",
112 "arabic_arabi", "arabic_arabtex", "asturian", "belarusian", "breton",
113 "canadien", "catalan", "french", "friulan", "galician", "italian", "occitan",
114 "piedmontese", "portuguese", "spanish", "spanish-mexico", 0};
115 
116 /// languages with german quotes (.lyx names)
117 const char * const known_german_quotes_languages[] = {"austrian", "bulgarian",
118 "czech", "estonian", "georgian", "german", "icelandic", "latvian", "lithuanian",
119 "lowersorbian", "macedonian", "naustrian", "ngerman", "romansh", "slovak", "slovene",
120 "uppersorbian", 0};
121 
122 /// languages with polish quotes (.lyx names)
123 const char * const known_polish_quotes_languages[] = {"afrikaans", "bosnian", "croatian",
124 "dutch", "magyar", "polish", "romanian", "serbian", "serbian-latin", 0};
125 
126 /// languages with russian quotes (.lyx names)
127 const char * const known_russian_quotes_languages[] = {"russian", "ukrainian", 0};
128 
129 /// languages with swedish quotes (.lyx names)
130 const char * const known_swedish_quotes_languages[] = {"finnish", "swedish", 0};
131 
132 /// languages with swiss quotes (.lyx names)
133 const char * const known_swiss_quotes_languages[] = {"albanian",
134 "armenian", "basque", "german-ch", "german-ch-old",
135 "norsk", "nynorsk", "turkmen", "ukrainian", "vietnamese", 0};
136 
137 /// known language packages from the times before babel
138 const char * const known_old_language_packages[] = {"french", "frenchle",
139 "frenchpro", "german", "ngerman", "pmfrench", 0};
140 
141 char const * const known_fontsizes[] = { "10pt", "11pt", "12pt", 0 };
142 
143 const char * const known_roman_fonts[] = { "ae", "beraserif", "bookman",
144 "ccfonts", "chancery", "charter", "cmr", "cochineal", "crimson", "fourier",
145 "garamondx", "libertine", "libertineRoman", "libertine-type1", "lmodern", "mathdesign",
146 "mathpazo", "mathptmx", "MinionPro", "newcent", "NotoSerif-TLF", "tgbonum", "tgchorus",
147 "tgpagella", "tgschola", "tgtermes", "utopia", 0 };
148 
149 const char * const known_sans_fonts[] = { "avant", "berasans", "biolinum",
150 "biolinum-type1", "cmbr", "cmss", "helvet", "iwona", "iwonac", "iwonal", "iwonalc",
151 "kurier", "kurierc", "kurierl", "kurierlc", "lmss", "NotoSans-TLF",
152 "tgadventor", "tgheros", "uop", 0 };
153 
154 const char * const known_typewriter_fonts[] = { "beramono", "cmtl", "cmtt",
155 "courier", "lmtt", "luximono", "fourier", "libertineMono", "libertineMono-type1", "lmodern",
156 "mathpazo", "mathptmx", "newcent", "NotoMono-TLF", "tgcursor", "txtt", 0 };
157 
158 const char * const known_math_fonts[] = { "eulervm", "newtxmath", 0};
159 
160 const char * const known_paper_sizes[] = { "a0paper", "b0paper", "c0paper",
161 "a1paper", "b1paper", "c1paper", "a2paper", "b2paper", "c2paper", "a3paper",
162 "b3paper", "c3paper", "a4paper", "b4paper", "c4paper", "a5paper", "b5paper",
163 "c5paper", "a6paper", "b6paper", "c6paper", "executivepaper", "legalpaper",
164 "letterpaper", "b0j", "b1j", "b2j", "b3j", "b4j", "b5j", "b6j", 0};
165 
166 const char * const known_class_paper_sizes[] = { "a4paper", "a5paper",
167 "executivepaper", "legalpaper", "letterpaper", 0};
168 
169 const char * const known_paper_margins[] = { "lmargin", "tmargin", "rmargin",
170 "bmargin", "headheight", "headsep", "footskip", "columnsep", 0};
171 
172 const char * const known_coded_paper_margins[] = { "leftmargin", "topmargin",
173 "rightmargin", "bottommargin", "headheight", "headsep", "footskip",
174 "columnsep", 0};
175 
176 /// commands that can start an \if...\else...\endif sequence
177 const char * const known_if_commands[] = {"if", "ifarydshln", "ifbraket",
178 "ifcancel", "ifcolortbl", "ifeurosym", "ifmarginnote", "ifmmode", "ifpdf",
179 "ifsidecap", "ifupgreek", 0};
180 
181 const char * const known_basic_colors[] = {"black", "blue", "brown", "cyan",
182 	"darkgray", "gray", "green", "lightgray", "lime", "magenta", "orange", "olive",
183 	"pink",	"purple", "red", "teal", "violet", "white", "yellow", 0};
184 
185 const char * const known_basic_color_codes[] = {"#000000", "#0000ff", "#964B00", "#00ffff",
186 	"#a9a9a9", "#808080", "#00ff00", "#d3d3d3", "#bfff00", "#ff00ff", "#ff7f00", "#808000",
187 	"#ffc0cb", "#800080", "#ff0000", "#008080", "#8f00ff", "#ffffff", "#ffff00", 0};
188 
189 /// conditional commands with three arguments like \@ifundefined{}{}{}
190 const char * const known_if_3arg_commands[] = {"@ifundefined", "IfFileExists",
191 0};
192 
193 /*!
194  * Known file extensions for TeX files as used by \\includeonly
195  */
196 char const * const known_tex_extensions[] = {"tex", 0};
197 
198 /// packages that work only in xetex
199 /// polyglossia is handled separately
200 const char * const known_xetex_packages[] = {"arabxetex", "fixlatvian",
201 "fontbook", "fontwrap", "mathspec", "philokalia", "unisugar",
202 "xeCJK", "xecolor", "xecyr", "xeindex", "xepersian", "xunicode", 0};
203 
204 /// packages that are automatically skipped if loaded by LyX
205 const char * const known_lyx_packages[] = {"amsbsy", "amsmath", "amssymb",
206 "amstext", "amsthm", "array", "babel", "booktabs", "calc", "CJK", "color",
207 "float", "fontspec", "framed", "graphicx", "hhline", "ifthen", "longtable",
208 "makeidx", "minted", "multirow", "nomencl", "pdfpages", "prettyref", "refstyle",
209 "rotating", "rotfloat", "splitidx", "setspace", "subscript", "textcomp", "tipa",
210 "tipx", "tone", "ulem", "url", "varioref", "verbatim", "wrapfig", "xcolor",
211 "xunicode", 0};
212 
213 // codes used to remove packages that are loaded automatically by LyX.
214 // Syntax: package_beg_sep<name>package_mid_sep<package loading code>package_end_sep
215 const char package_beg_sep = '\001';
216 const char package_mid_sep = '\002';
217 const char package_end_sep = '\003';
218 
219 
220 // returns true if at least one of the options in what has been found
handle_opt(vector<string> & opts,char const * const * what,string & target)221 bool handle_opt(vector<string> & opts, char const * const * what, string & target)
222 {
223 	if (opts.empty())
224 		return false;
225 
226 	bool found = false;
227 	// the last language option is the document language (for babel and LyX)
228 	// the last size option is the document font size
229 	vector<string>::iterator it;
230 	vector<string>::iterator position = opts.begin();
231 	for (; *what; ++what) {
232 		it = find(opts.begin(), opts.end(), *what);
233 		if (it != opts.end()) {
234 			if (it >= position) {
235 				found = true;
236 				target = *what;
237 				position = it;
238 			}
239 		}
240 	}
241 	return found;
242 }
243 
244 
delete_opt(vector<string> & opts,char const * const * what)245 void delete_opt(vector<string> & opts, char const * const * what)
246 {
247 	if (opts.empty())
248 		return;
249 
250 	// remove found options from the list
251 	// do this after handle_opt to avoid potential memory leaks
252 	vector<string>::iterator it;
253 	for (; *what; ++what) {
254 		it = find(opts.begin(), opts.end(), *what);
255 		if (it != opts.end())
256 			opts.erase(it);
257 	}
258 }
259 
260 
261 /*!
262  * Split a package options string (keyval format) into a vector.
263  * Example input:
264  *   authorformat=smallcaps,
265  *   commabeforerest,
266  *   titleformat=colonsep,
267  *   bibformat={tabular,ibidem,numbered}
268  */
split_options(string const & input)269 vector<string> split_options(string const & input)
270 {
271 	vector<string> options;
272 	string option;
273 	Parser p(input);
274 	while (p.good()) {
275 		Token const & t = p.get_token();
276 		if (t.asInput() == ",") {
277 			options.push_back(trimSpaceAndEol(option));
278 			option.erase();
279 		} else if (t.asInput() == "=") {
280 			option += '=';
281 			p.skip_spaces(true);
282 			if (p.next_token().asInput() == "{")
283 				option += '{' + p.getArg('{', '}') + '}';
284 		} else if (t.cat() != catSpace && t.cat() != catComment)
285 			option += t.asInput();
286 	}
287 
288 	if (!option.empty())
289 		options.push_back(trimSpaceAndEol(option));
290 
291 	return options;
292 }
293 
294 
295 /*!
296  * Retrieve a keyval option "name={value with=sign}" named \p name from
297  * \p options and return the value.
298  * The found option is also removed from \p options.
299  */
process_keyval_opt(vector<string> & options,string name)300 string process_keyval_opt(vector<string> & options, string name)
301 {
302 	for (size_t i = 0; i < options.size(); ++i) {
303 		vector<string> option;
304 		split(options[i], option, '=');
305 		if (option.size() < 2)
306 			continue;
307 		if (option[0] == name) {
308 			options.erase(options.begin() + i);
309 			option.erase(option.begin());
310 			return join(option, "=");
311 		}
312 	}
313 	return "";
314 }
315 
316 } // anonymous namespace
317 
318 
319 /**
320  * known polyglossia language names (including variants)
321  * FIXME: support spelling=old for german variants (german vs. ngerman LyX names etc)
322  */
323 const char * const Preamble::polyglossia_languages[] = {
324 "albanian", "american", "amharic", "ancient", "arabic", "armenian", "asturian", "australian",
325 "bahasai", "bahasam", "basque", "bengali", "brazil", "brazilian", "breton", "british", "bulgarian",
326 "catalan", "coptic", "croatian", "czech", "danish", "divehi", "dutch",
327 "english", "esperanto", "estonian", "farsi", "finnish", "french", "friulan",
328 "galician", "greek", "monotonic", "hebrew", "hindi",
329 "icelandic", "interlingua", "irish", "italian", "kannada", "khmer",
330 "lao", "latin", "latvian", "lithuanian", "lsorbian", "magyar", "malayalam", "marathi",
331 "austrian", "newzealand", "german", "norsk", "nynorsk", "occitan",
332 "piedmontese", "polish", "polytonic", "portuges", "romanian", "romansh", "russian",
333 "samin", "sanskrit", "scottish", "serbian", "slovak", "slovenian", "spanish", "swedish", "syriac",
334 "tamil", "telugu", "thai", "tibetan", "turkish", "turkmen",
335 "ukrainian", "urdu", "usorbian", "vietnamese", "welsh", 0};
336 // not yet supported by LyX: "korean", "nko"
337 
338 /**
339  * the same as polyglossia_languages with .lyx names
340  * please keep this in sync with polyglossia_languages line by line!
341  */
342 const char * const Preamble::coded_polyglossia_languages[] = {
343 "albanian", "american", "amharic", "ancientgreek", "arabic_arabi", "armenian", "asturian", "australian",
344 "bahasa", "bahasam", "basque", "bengali", "brazilian", "brazilian", "breton", "british", "bulgarian",
345 "catalan", "coptic", "croatian", "czech", "danish", "divehi", "dutch",
346 "english", "esperanto", "estonian", "farsi", "finnish", "french", "friulan",
347 "galician", "greek", "greek", "hebrew", "hindi",
348 "icelandic", "interlingua", "irish", "italian", "kannada", "khmer",
349 "lao", "latin", "latvian", "lithuanian", "lowersorbian", "magyar", "malayalam", "marathi",
350 "naustrian","newzealand", "ngerman", "norsk", "nynorsk", "occitan",
351 "piedmontese", "polish", "polutonikogreek", "portuges", "romanian", "romansh", "russian",
352 "samin", "sanskrit", "scottish", "serbian", "slovak", "slovene", "spanish", "swedish", "syriac",
353 "tamil", "telugu", "thai", "tibetan", "turkish", "turkmen",
354 "ukrainian", "urdu", "uppersorbian", "vietnamese", "welsh", 0};
355 // not yet supported by LyX: "korean-polyglossia", "nko"
356 
357 
usePolyglossia() const358 bool Preamble::usePolyglossia() const
359 {
360 	return h_use_non_tex_fonts && h_language_package == "default";
361 }
362 
363 
indentParagraphs() const364 bool Preamble::indentParagraphs() const
365 {
366 	return h_paragraph_separation == "indent";
367 }
368 
369 
isPackageUsed(string const & package) const370 bool Preamble::isPackageUsed(string const & package) const
371 {
372 	return used_packages.find(package) != used_packages.end();
373 }
374 
375 
isPackageAutoLoaded(string const & package) const376 bool Preamble::isPackageAutoLoaded(string const & package) const
377 {
378 	return auto_packages.find(package) != auto_packages.end();
379 }
380 
381 
getPackageOptions(string const & package) const382 vector<string> Preamble::getPackageOptions(string const & package) const
383 {
384 	map<string, vector<string> >::const_iterator it = used_packages.find(package);
385 	if (it != used_packages.end())
386 		return it->second;
387 	return vector<string>();
388 }
389 
390 
registerAutomaticallyLoadedPackage(std::string const & package)391 void Preamble::registerAutomaticallyLoadedPackage(std::string const & package)
392 {
393 	auto_packages.insert(package);
394 }
395 
396 
addModule(string const & module)397 void Preamble::addModule(string const & module)
398 {
399 	for (auto const & m : used_modules) {
400 		if (m == module)
401 			return;
402 	}
403 	used_modules.push_back(module);
404 }
405 
406 
suppressDate(bool suppress)407 void Preamble::suppressDate(bool suppress)
408 {
409 	if (suppress)
410 		h_suppress_date = "true";
411 	else
412 		h_suppress_date = "false";
413 }
414 
415 
registerAuthor(std::string const & name)416 void Preamble::registerAuthor(std::string const & name)
417 {
418 	Author author(from_utf8(name), empty_docstring());
419 	author.setUsed(true);
420 	authors_.record(author);
421 	h_tracking_changes = "true";
422 	h_output_changes = "true";
423 }
424 
425 
getAuthor(std::string const & name) const426 Author const & Preamble::getAuthor(std::string const & name) const
427 {
428 	Author author(from_utf8(name), empty_docstring());
429 	for (AuthorList::Authors::const_iterator it = authors_.begin();
430 	     it != authors_.end(); ++it)
431 		if (*it == author)
432 			return *it;
433 	static Author const dummy;
434 	return dummy;
435 }
436 
437 
getSpecialTableColumnArguments(char c) const438 int Preamble::getSpecialTableColumnArguments(char c) const
439 {
440 	map<char, int>::const_iterator it = special_columns_.find(c);
441 	if (it == special_columns_.end())
442 		return -1;
443 	return it->second;
444 }
445 
446 
add_package(string const & name,vector<string> & options)447 void Preamble::add_package(string const & name, vector<string> & options)
448 {
449 	// every package inherits the global options
450 	if (used_packages.find(name) == used_packages.end())
451 		used_packages[name] = split_options(h_options);
452 
453 	// Insert options passed via PassOptionsToPackage
454 	for (auto const & p : extra_package_options_) {
455 		if (p.first == name) {
456 			vector<string> eo = getVectorFromString(p.second);
457 			for (auto const & eoi : eo)
458 				options.push_back(eoi);
459 		}
460 
461 	}
462 	vector<string> & v = used_packages[name];
463 	v.insert(v.end(), options.begin(), options.end());
464 	if (name == "jurabib") {
465 		// Don't output the order argument (see the cite command
466 		// handling code in text.cpp).
467 		vector<string>::iterator end =
468 			remove(options.begin(), options.end(), "natbiborder");
469 		end = remove(options.begin(), end, "jurabiborder");
470 		options.erase(end, options.end());
471 	}
472 }
473 
474 
475 namespace {
476 
477 // Given is a string like "scaled=0.9" or "Scale=0.9", return 0.9 * 100
scale_as_percentage(string const & scale,string & percentage)478 bool scale_as_percentage(string const & scale, string & percentage)
479 {
480 	string::size_type pos = scale.find('=');
481 	if (pos != string::npos) {
482 		string value = scale.substr(pos + 1);
483 		if (isStrDbl(value)) {
484 			percentage = convert<string>(
485 				static_cast<int>(100 * convert<double>(value)));
486 			return true;
487 		}
488 	}
489 	return false;
490 }
491 
492 
remove_braces(string const & value)493 string remove_braces(string const & value)
494 {
495 	if (value.empty())
496 		return value;
497 	if (value[0] == '{' && value[value.length()-1] == '}')
498 		return value.substr(1, value.length()-2);
499 	return value;
500 }
501 
502 } // anonymous namespace
503 
504 
Preamble()505 Preamble::Preamble() : one_language(true), explicit_babel(false),
506 	title_layout_found(false), index_number(0), h_font_cjk_set(false),
507 	h_use_microtype("false")
508 {
509 	//h_backgroundcolor;
510 	//h_boxbgcolor;
511 	h_biblio_style            = "plain";
512 	h_bibtex_command          = "default";
513 	h_cite_engine             = "basic";
514 	h_cite_engine_type        = "default";
515 	h_color                   = "#008000";
516 	h_defskip                 = "medskip";
517 	h_dynamic_quotes          = false;
518 	//h_float_placement;
519 	//h_fontcolor;
520 	h_fontencoding            = "default";
521 	h_font_roman[0]           = "default";
522 	h_font_roman[1]           = "default";
523 	h_font_sans[0]            = "default";
524 	h_font_sans[1]            = "default";
525 	h_font_typewriter[0]      = "default";
526 	h_font_typewriter[1]      = "default";
527 	h_font_math[0]            = "auto";
528 	h_font_math[1]            = "auto";
529 	h_font_default_family     = "default";
530 	h_use_non_tex_fonts       = false;
531 	h_font_sc                 = "false";
532 	h_font_osf                = "false";
533 	h_font_sf_scale[0]        = "100";
534 	h_font_sf_scale[1]        = "100";
535 	h_font_tt_scale[0]        = "100";
536 	h_font_tt_scale[1]        = "100";
537 	//h_font_cjk
538 	h_is_mathindent           = "0";
539 	h_math_numbering_side     = "default";
540 	h_graphics                = "default";
541 	h_default_output_format   = "default";
542 	h_html_be_strict          = "false";
543 	h_html_css_as_file        = "0";
544 	h_html_math_output        = "0";
545 	h_index[0]                = "Index";
546 	h_index_command           = "default";
547 	h_inputencoding           = "auto";
548 	h_justification           = "true";
549 	h_language                = "english";
550 	h_language_package        = "none";
551 	//h_listings_params;
552 	h_maintain_unincluded_children = "false";
553 	//h_margins;
554 	//h_notefontcolor;
555 	//h_options;
556 	h_output_changes          = "false";
557 	h_output_sync             = "0";
558 	//h_output_sync_macro
559 	h_papercolumns            = "1";
560 	h_paperfontsize           = "default";
561 	h_paperorientation        = "portrait";
562 	h_paperpagestyle          = "default";
563 	//h_papersides;
564 	h_papersize               = "default";
565 	h_paragraph_indentation   = "default";
566 	h_paragraph_separation    = "indent";
567 	//h_pdf_title;
568 	//h_pdf_author;
569 	//h_pdf_subject;
570 	//h_pdf_keywords;
571 	h_pdf_bookmarks           = "0";
572 	h_pdf_bookmarksnumbered   = "0";
573 	h_pdf_bookmarksopen       = "0";
574 	h_pdf_bookmarksopenlevel  = "1";
575 	h_pdf_breaklinks          = "0";
576 	h_pdf_pdfborder           = "0";
577 	h_pdf_colorlinks          = "0";
578 	h_pdf_backref             = "section";
579 	h_pdf_pdfusetitle         = "0";
580 	//h_pdf_pagemode;
581 	//h_pdf_quoted_options;
582 	h_quotes_style         = "english";
583 	h_secnumdepth             = "3";
584 	h_shortcut[0]             = "idx";
585 	h_spacing                 = "single";
586 	h_save_transient_properties = "true";
587 	h_suppress_date           = "false";
588 	h_textclass               = "article";
589 	h_tocdepth                = "3";
590 	h_tracking_changes        = "false";
591 	h_use_bibtopic            = "false";
592 	h_use_dash_ligatures      = "true";
593 	h_use_indices             = "false";
594 	h_use_geometry            = "false";
595 	h_use_default_options     = "false";
596 	h_use_hyperref            = "false";
597 	h_use_microtype           = "false";
598 	h_use_refstyle            = false;
599 	h_use_minted              = false;
600 	h_use_packages["amsmath"]    = "1";
601 	h_use_packages["amssymb"]    = "0";
602 	h_use_packages["cancel"]     = "0";
603 	h_use_packages["esint"]      = "1";
604 	h_use_packages["mhchem"]     = "0";
605 	h_use_packages["mathdots"]   = "0";
606 	h_use_packages["mathtools"]  = "0";
607 	h_use_packages["stackrel"]   = "0";
608 	h_use_packages["stmaryrd"]   = "0";
609 	h_use_packages["undertilde"] = "0";
610 }
611 
612 
handle_hyperref(vector<string> & options)613 void Preamble::handle_hyperref(vector<string> & options)
614 {
615 	// FIXME swallow inputencoding changes that might surround the
616 	//       hyperref setup if it was written by LyX
617 	h_use_hyperref = "true";
618 	// swallow "unicode=true", since LyX does always write that
619 	vector<string>::iterator it =
620 		find(options.begin(), options.end(), "unicode=true");
621 	if (it != options.end())
622 		options.erase(it);
623 	it = find(options.begin(), options.end(), "pdfusetitle");
624 	if (it != options.end()) {
625 		h_pdf_pdfusetitle = "1";
626 		options.erase(it);
627 	}
628 	string bookmarks = process_keyval_opt(options, "bookmarks");
629 	if (bookmarks == "true")
630 		h_pdf_bookmarks = "1";
631 	else if (bookmarks == "false")
632 		h_pdf_bookmarks = "0";
633 	if (h_pdf_bookmarks == "1") {
634 		string bookmarksnumbered =
635 			process_keyval_opt(options, "bookmarksnumbered");
636 		if (bookmarksnumbered == "true")
637 			h_pdf_bookmarksnumbered = "1";
638 		else if (bookmarksnumbered == "false")
639 			h_pdf_bookmarksnumbered = "0";
640 		string bookmarksopen =
641 			process_keyval_opt(options, "bookmarksopen");
642 		if (bookmarksopen == "true")
643 			h_pdf_bookmarksopen = "1";
644 		else if (bookmarksopen == "false")
645 			h_pdf_bookmarksopen = "0";
646 		if (h_pdf_bookmarksopen == "1") {
647 			string bookmarksopenlevel =
648 				process_keyval_opt(options, "bookmarksopenlevel");
649 			if (!bookmarksopenlevel.empty())
650 				h_pdf_bookmarksopenlevel = bookmarksopenlevel;
651 		}
652 	}
653 	string breaklinks = process_keyval_opt(options, "breaklinks");
654 	if (breaklinks == "true")
655 		h_pdf_breaklinks = "1";
656 	else if (breaklinks == "false")
657 		h_pdf_breaklinks = "0";
658 	string pdfborder = process_keyval_opt(options, "pdfborder");
659 	if (pdfborder == "{0 0 0}")
660 		h_pdf_pdfborder = "1";
661 	else if (pdfborder == "{0 0 1}")
662 		h_pdf_pdfborder = "0";
663 	string backref = process_keyval_opt(options, "backref");
664 	if (!backref.empty())
665 		h_pdf_backref = backref;
666 	string colorlinks = process_keyval_opt(options, "colorlinks");
667 	if (colorlinks == "true")
668 		h_pdf_colorlinks = "1";
669 	else if (colorlinks == "false")
670 		h_pdf_colorlinks = "0";
671 	string pdfpagemode = process_keyval_opt(options, "pdfpagemode");
672 	if (!pdfpagemode.empty())
673 		h_pdf_pagemode = pdfpagemode;
674 	string pdftitle = process_keyval_opt(options, "pdftitle");
675 	if (!pdftitle.empty()) {
676 		h_pdf_title = remove_braces(pdftitle);
677 	}
678 	string pdfauthor = process_keyval_opt(options, "pdfauthor");
679 	if (!pdfauthor.empty()) {
680 		h_pdf_author = remove_braces(pdfauthor);
681 	}
682 	string pdfsubject = process_keyval_opt(options, "pdfsubject");
683 	if (!pdfsubject.empty())
684 		h_pdf_subject = remove_braces(pdfsubject);
685 	string pdfkeywords = process_keyval_opt(options, "pdfkeywords");
686 	if (!pdfkeywords.empty())
687 		h_pdf_keywords = remove_braces(pdfkeywords);
688 	if (!options.empty()) {
689 		if (!h_pdf_quoted_options.empty())
690 			h_pdf_quoted_options += ',';
691 		h_pdf_quoted_options += join(options, ",");
692 		options.clear();
693 	}
694 }
695 
696 
handle_geometry(vector<string> & options)697 void Preamble::handle_geometry(vector<string> & options)
698 {
699 	h_use_geometry = "true";
700 	vector<string>::iterator it;
701 	// paper orientation
702 	if ((it = find(options.begin(), options.end(), "landscape")) != options.end()) {
703 		h_paperorientation = "landscape";
704 		options.erase(it);
705 	}
706 	// paper size
707 	// keyval version: "paper=letter"
708 	string paper = process_keyval_opt(options, "paper");
709 	if (!paper.empty())
710 		h_papersize = paper + "paper";
711 	// alternative version: "letterpaper"
712 	handle_opt(options, known_paper_sizes, h_papersize);
713 	delete_opt(options, known_paper_sizes);
714 	// page margins
715 	char const * const * margin = known_paper_margins;
716 	for (; *margin; ++margin) {
717 		string value = process_keyval_opt(options, *margin);
718 		if (!value.empty()) {
719 			int k = margin - known_paper_margins;
720 			string name = known_coded_paper_margins[k];
721 			h_margins += '\\' + name + ' ' + value + '\n';
722 		}
723 	}
724 }
725 
726 
handle_package(Parser & p,string const & name,string const & opts,bool in_lyx_preamble,bool detectEncoding)727 void Preamble::handle_package(Parser &p, string const & name,
728                               string const & opts, bool in_lyx_preamble,
729                               bool detectEncoding)
730 {
731 	vector<string> options = split_options(opts);
732 	add_package(name, options);
733 
734 	if (is_known(name, known_xetex_packages)) {
735 		xetex = true;
736 		h_use_non_tex_fonts = true;
737 		registerAutomaticallyLoadedPackage("fontspec");
738 		if (h_inputencoding == "auto")
739 			p.setEncoding("UTF-8");
740 	}
741 
742 	// roman fonts
743 	if (is_known(name, known_roman_fonts))
744 		h_font_roman[0] = name;
745 
746 	if (name == "fourier") {
747 		h_font_roman[0] = "utopia";
748 		// when font uses real small capitals
749 		if (opts == "expert")
750 			h_font_sc = "true";
751 	}
752 
753 	if (name == "garamondx") {
754 		h_font_roman[0] = "garamondx";
755 		if (opts == "osfI")
756 			h_font_osf = "true";
757 	}
758 
759 	if (name == "libertine") {
760 		h_font_roman[0] = "libertine";
761 		// this automatically invokes biolinum
762 		h_font_sans[0] = "biolinum";
763 		// as well as libertineMono
764 		h_font_typewriter[0] = "libertine-mono";
765 		if (opts == "osf")
766 			h_font_osf = "true";
767 		else if (opts == "lining")
768 			h_font_osf = "false";
769 	}
770 
771 	if (name == "libertineRoman" || name == "libertine-type1") {
772 		h_font_roman[0] = "libertine";
773 		// NOTE: contrary to libertine.sty, libertineRoman
774 		// and libertine-type1 do not automatically invoke
775 		// biolinum and libertineMono
776 		if (opts == "lining")
777 			h_font_osf = "false";
778 		else if (opts == "osf")
779 			h_font_osf = "true";
780 	}
781 
782 	if (name == "MinionPro") {
783 		h_font_roman[0] = "minionpro";
784 		if (opts.find("lf") != string::npos)
785 			h_font_osf = "false";
786 		else
787 			h_font_osf = "true";
788 		if (opts.find("onlytext") != string::npos)
789 			h_font_math[0] = "default";
790 		else
791 			h_font_math[0] = "auto";
792 	}
793 
794 	if (name == "mathdesign") {
795 		if (opts.find("charter") != string::npos)
796 			h_font_roman[0] = "md-charter";
797 		if (opts.find("garamond") != string::npos)
798 			h_font_roman[0] = "md-garamond";
799 		if (opts.find("utopia") != string::npos)
800 			h_font_roman[0] = "md-utopia";
801 		if (opts.find("expert") != string::npos) {
802 			h_font_sc = "true";
803 			h_font_osf = "true";
804 		}
805 	}
806 
807 	else if (name == "mathpazo")
808 		h_font_roman[0] = "palatino";
809 
810 	else if (name == "mathptmx")
811 		h_font_roman[0] = "times";
812 
813 	if (name == "crimson")
814 		h_font_roman[0] = "cochineal";
815 
816 	if (name == "cochineal") {
817 		h_font_roman[0] = "cochineal";
818 		// cochineal can have several options, e.g. [proportional,osf]
819 		string::size_type pos = opts.find("osf");
820 		if (pos != string::npos)
821 			h_font_osf = "true";
822 	}
823 
824 	if (name == "noto") {
825 		// noto can have several options
826 		if (opts.empty())
827 			h_font_roman[0] = "NotoSerif-TLF";
828 		string::size_type pos = opts.find("rm");
829 		if (pos != string::npos)
830 			h_font_roman[0] = "NotoSerif-TLF";
831 		pos = opts.find("sf");
832 		if (pos != string::npos)
833 			h_font_sans[0] = "NotoSans-TLF";
834 		pos = opts.find("nott");
835 		if (pos != string::npos) {
836 			h_font_roman[0] = "NotoSerif-TLF";
837 			h_font_sans[0] = "NotoSans-TLF";
838 		}
839 		// noto as typewriter is handled in handling of \ttdefault
840 		// special cases are handled in handling of \rmdefault and \sfdefault
841 	}
842 
843 	// sansserif fonts
844 	if (is_known(name, known_sans_fonts)) {
845 		h_font_sans[0] = name;
846 		if (options.size() >= 1) {
847 			if (scale_as_percentage(opts, h_font_sf_scale[0]))
848 				options.clear();
849 		}
850 	}
851 
852 	if (name == "biolinum" || name == "biolinum-type1") {
853 		h_font_sans[0] = "biolinum";
854 		// biolinum can have several options, e.g. [osf,scaled=0.97]
855 		string::size_type pos = opts.find("osf");
856 		if (pos != string::npos)
857 			h_font_osf = "true";
858 	}
859 
860 	// typewriter fonts
861 	if (is_known(name, known_typewriter_fonts)) {
862 		// fourier can be set as roman font _only_
863 		// fourier as typewriter is handled in handling of \ttdefault
864 		if (name != "fourier") {
865 			h_font_typewriter[0] = name;
866 			if (options.size() >= 1) {
867 				if (scale_as_percentage(opts, h_font_tt_scale[0]))
868 					options.clear();
869 			}
870 		}
871 	}
872 
873 	if (name == "libertineMono" || name == "libertineMono-type1")
874 		h_font_typewriter[0] = "libertine-mono";
875 
876 	// font uses old-style figure
877 	if (name == "eco")
878 		h_font_osf = "true";
879 
880 	// math fonts
881 	if (is_known(name, known_math_fonts))
882 		h_font_math[0] = name;
883 
884 	if (name == "newtxmath") {
885 		if (opts.empty())
886 			h_font_math[0] = "newtxmath";
887 		else if (opts == "garamondx")
888 			h_font_math[0] = "garamondx-ntxm";
889 		else if (opts == "libertine")
890 			h_font_math[0] = "libertine-ntxm";
891 		else if (opts == "minion")
892 			h_font_math[0] = "minion-ntxm";
893 		else if (opts == "cochineal")
894 			h_font_math[0] = "cochineal-ntxm";
895 	}
896 
897 	if (name == "iwona")
898 		if (opts == "math")
899 			h_font_math[0] = "iwona-math";
900 
901 	if (name == "kurier")
902 		if (opts == "math")
903 			h_font_math[0] = "kurier-math";
904 
905 	// after the detection and handling of special cases, we can remove the
906 	// fonts, otherwise they would appear in the preamble, see bug #7856
907 	if (is_known(name, known_roman_fonts) || is_known(name, known_sans_fonts)
908 		||	is_known(name, known_typewriter_fonts) || is_known(name, known_math_fonts))
909 		;
910 	//"On". See the enum Package in BufferParams.h if you thought that "2" should have been "42"
911 	else if (name == "amsmath" || name == "amssymb" || name == "cancel" ||
912 	         name == "esint" || name == "mhchem" || name == "mathdots" ||
913 	         name == "mathtools" || name == "stackrel" ||
914 		 name == "stmaryrd" || name == "undertilde") {
915 		h_use_packages[name] = "2";
916 		registerAutomaticallyLoadedPackage(name);
917 	}
918 
919 	else if (name == "babel") {
920 		h_language_package = "default";
921 		// One might think we would have to do nothing if babel is loaded
922 		// without any options to prevent pollution of the preamble with this
923 		// babel call in every roundtrip.
924 		// But the user could have defined babel-specific things afterwards. So
925 		// we need to keep it in the preamble to prevent cases like bug #7861.
926 		if (!opts.empty()) {
927 			// check if more than one option was used - used later for inputenc
928 			if (options.begin() != options.end() - 1)
929 				one_language = false;
930 			// babel takes the last language of the option of its \usepackage
931 			// call as document language. If there is no such language option, the
932 			// last language in the documentclass options is used.
933 			handle_opt(options, known_languages, h_language);
934 			// translate the babel name to a LyX name
935 			h_language = babel2lyx(h_language);
936 			if (h_language == "japanese") {
937 				// For Japanese, the encoding isn't indicated in the source
938 				// file, and there's really not much we can do. We could
939 				// 1) offer a list of possible encodings to choose from, or
940 				// 2) determine the encoding of the file by inspecting it.
941 				// For the time being, we leave the encoding alone so that
942 				// we don't get iconv errors when making a wrong guess, and
943 				// we will output a note at the top of the document
944 				// explaining what to do.
945 				Encoding const * const enc = encodings.fromIconvName(
946 					p.getEncoding(), Encoding::japanese, false);
947 				if (enc)
948 					h_inputencoding = enc->name();
949 				is_nonCJKJapanese = true;
950 				// in this case babel can be removed from the preamble
951 				registerAutomaticallyLoadedPackage("babel");
952 			} else {
953 				// If babel is called with options, LyX puts them by default into the
954 				// document class options. This works for most languages, except
955 				// for Latvian, Lithuanian, Mongolian, Turkmen and Vietnamese and
956 				// perhaps in future others.
957 				// Therefore keep the babel call as it is as the user might have
958 				// reasons for it.
959 				string const babelcall = "\\usepackage[" + opts + "]{babel}\n";
960 				if (!contains(h_preamble.str(), babelcall))
961 					h_preamble << babelcall;
962 			}
963 			delete_opt(options, known_languages);
964 		} else {
965 			if (!contains(h_preamble.str(), "\\usepackage{babel}\n"))
966 				h_preamble << "\\usepackage{babel}\n";
967 			explicit_babel = true;
968 		}
969 	}
970 
971 	else if (name == "polyglossia") {
972 		h_language_package = "default";
973 		h_default_output_format = "pdf4";
974 		h_use_non_tex_fonts = true;
975 		xetex = true;
976 		registerAutomaticallyLoadedPackage("xunicode");
977 		if (h_inputencoding == "auto")
978 			p.setEncoding("UTF-8");
979 	}
980 
981 	else if (name == "CJK") {
982 		// set the encoding to "auto" because it might be set to "default" by the babel handling
983 		// and this would not be correct for CJK
984 		if (h_inputencoding == "default")
985 			h_inputencoding = "auto";
986 		registerAutomaticallyLoadedPackage("CJK");
987 	}
988 
989 	else if (name == "CJKutf8") {
990 		h_inputencoding = "utf8-cjk";
991 		p.setEncoding("UTF-8");
992 		registerAutomaticallyLoadedPackage("CJKutf8");
993 	}
994 
995 	else if (name == "fontenc") {
996 		h_fontencoding = getStringFromVector(options, ",");
997 		/* We could do the following for better round trip support,
998 		 * but this makes the document less portable, so I skip it:
999 		if (h_fontencoding == lyxrc.fontenc)
1000 			h_fontencoding = "global";
1001 		 */
1002 		options.clear();
1003 	}
1004 
1005 	else if (name == "inputenc" || name == "luainputenc") {
1006 		// h_inputencoding is only set when there is not more than one
1007 		// inputenc option because otherwise h_inputencoding must be
1008 		// set to "auto" (the default encoding of the document language)
1009 		// Therefore check that exactly one option is passed to inputenc.
1010 		// It is also only set when there is not more than one babel
1011 		// language option.
1012 		if (!options.empty()) {
1013 			string const encoding = options.back();
1014 			Encoding const * const enc = encodings.fromLaTeXName(
1015 				encoding, Encoding::inputenc, true);
1016 			if (!enc) {
1017 				if (!detectEncoding)
1018 					cerr << "Unknown encoding " << encoding
1019 					     << ". Ignoring." << std::endl;
1020 			} else {
1021 				if (!enc->unsafe() && options.size() == 1 && one_language == true)
1022 					h_inputencoding = enc->name();
1023 				p.setEncoding(enc->iconvName());
1024 			}
1025 			options.clear();
1026 		}
1027 	}
1028 
1029 	else if (name == "srcltx") {
1030 		h_output_sync = "1";
1031 		if (!opts.empty()) {
1032 			h_output_sync_macro = "\\usepackage[" + opts + "]{srcltx}";
1033 			options.clear();
1034 		} else
1035 			h_output_sync_macro = "\\usepackage{srcltx}";
1036 	}
1037 
1038 	else if (is_known(name, known_old_language_packages)) {
1039 		// known language packages from the times before babel
1040 		// if they are found and not also babel, they will be used as
1041 		// custom language package
1042 		h_language_package = "\\usepackage{" + name + "}";
1043 	}
1044 
1045 	else if (name == "lyxskak") {
1046 		// ignore this and its options
1047 		const char * const o[] = {"ps", "mover", 0};
1048 		delete_opt(options, o);
1049 	}
1050 
1051 	else if (is_known(name, known_lyx_packages) && options.empty()) {
1052 		if (name == "splitidx")
1053 			h_use_indices = "true";
1054 		else if (name == "minted")
1055 			h_use_minted = true;
1056 		else if (name == "refstyle")
1057 			h_use_refstyle = true;
1058 		else if (name == "prettyref")
1059 			h_use_refstyle = false;
1060 		if (!in_lyx_preamble) {
1061 			h_preamble << package_beg_sep << name
1062 			           << package_mid_sep << "\\usepackage{"
1063 			           << name << '}';
1064 			if (p.next_token().cat() == catNewline ||
1065 			    (p.next_token().cat() == catSpace &&
1066 			     p.next_next_token().cat() == catNewline))
1067 				h_preamble << '\n';
1068 			h_preamble << package_end_sep;
1069 		}
1070 	}
1071 
1072 	else if (name == "geometry")
1073 		handle_geometry(options);
1074 
1075 	else if (name == "subfig")
1076 		; // ignore this FIXME: Use the package separator mechanism instead
1077 
1078 	else if (char const * const * where = is_known(name, known_languages))
1079 		h_language = known_coded_languages[where - known_languages];
1080 
1081 	else if (name == "natbib") {
1082 		h_biblio_style = "plainnat";
1083 		h_cite_engine = "natbib";
1084 		h_cite_engine_type = "authoryear";
1085 		vector<string>::iterator it =
1086 			find(options.begin(), options.end(), "authoryear");
1087 		if (it != options.end())
1088 			options.erase(it);
1089 		else {
1090 			it = find(options.begin(), options.end(), "numbers");
1091 			if (it != options.end()) {
1092 				h_cite_engine_type = "numerical";
1093 				options.erase(it);
1094 			}
1095 		}
1096 		if (!options.empty())
1097 			h_biblio_options = join(options, ",");
1098 	}
1099 
1100 	else if (name == "biblatex") {
1101 		h_biblio_style = "plainnat";
1102 		h_cite_engine = "biblatex";
1103 		h_cite_engine_type = "authoryear";
1104 		string opt;
1105 		vector<string>::iterator it =
1106 			find(options.begin(), options.end(), "natbib");
1107 		if (it != options.end()) {
1108 			options.erase(it);
1109 			h_cite_engine = "biblatex-natbib";
1110 		} else {
1111 			opt = process_keyval_opt(options, "natbib");
1112 			if (opt == "true")
1113 				h_cite_engine = "biblatex-natbib";
1114 		}
1115 		opt = process_keyval_opt(options, "style");
1116 		if (!opt.empty()) {
1117 			h_biblatex_citestyle = opt;
1118 			h_biblatex_bibstyle = opt;
1119 		} else {
1120 			opt = process_keyval_opt(options, "citestyle");
1121 			if (!opt.empty())
1122 				h_biblatex_citestyle = opt;
1123 			opt = process_keyval_opt(options, "bibstyle");
1124 			if (!opt.empty())
1125 				h_biblatex_bibstyle = opt;
1126 		}
1127 		opt = process_keyval_opt(options, "refsection");
1128 		if (!opt.empty()) {
1129 			if (opt == "none" || opt == "part"
1130 			    || opt == "chapter" || opt == "section"
1131 			    || opt == "subsection")
1132 				h_multibib = opt;
1133 			else
1134 				cerr << "Ignoring unkown refesection value '"
1135 				     << opt << "'.";
1136 		}
1137 		if (!options.empty()) {
1138 			h_biblio_options = join(options, ",");
1139 			options.clear();
1140 		}
1141 	}
1142 
1143 	else if (name == "jurabib") {
1144 		h_biblio_style = "jurabib";
1145 		h_cite_engine = "jurabib";
1146 		h_cite_engine_type = "authoryear";
1147 		if (!options.empty())
1148 			h_biblio_options = join(options, ",");
1149 	}
1150 
1151 	else if (name == "bibtopic")
1152 		h_use_bibtopic = "true";
1153 
1154 	else if (name == "chapterbib")
1155 		h_multibib = "child";
1156 
1157 	else if (name == "hyperref")
1158 		handle_hyperref(options);
1159 
1160 	else if (name == "algorithm2e") {
1161 		// Load "algorithm2e" module
1162 		addModule("algorithm2e");
1163 		// Add the package options to the global document options
1164 		if (!options.empty()) {
1165 			if (h_options.empty())
1166 				h_options = join(options, ",");
1167 			else
1168 				h_options += ',' + join(options, ",");
1169 		}
1170 	}
1171 	else if (name == "microtype") {
1172 		//we internally support only microtype without params
1173 		if (options.empty())
1174 			h_use_microtype = "true";
1175 		else
1176 			h_preamble << "\\usepackage[" << opts << "]{microtype}";
1177 	}
1178 
1179 	else if (!in_lyx_preamble) {
1180 		if (options.empty())
1181 			h_preamble << "\\usepackage{" << name << '}';
1182 		else {
1183 			h_preamble << "\\usepackage[" << opts << "]{"
1184 				   << name << '}';
1185 			options.clear();
1186 		}
1187 		if (p.next_token().cat() == catNewline ||
1188 		    (p.next_token().cat() == catSpace &&
1189 		     p.next_next_token().cat() == catNewline))
1190 			h_preamble << '\n';
1191 	}
1192 
1193 	// We need to do something with the options...
1194 	if (!options.empty() && !detectEncoding)
1195 		cerr << "Ignoring options '" << join(options, ",")
1196 		     << "' of package " << name << '.' << endl;
1197 
1198 	// remove the whitespace
1199 	p.skip_spaces();
1200 }
1201 
1202 
handle_if(Parser & p,bool in_lyx_preamble)1203 void Preamble::handle_if(Parser & p, bool in_lyx_preamble)
1204 {
1205 	while (p.good()) {
1206 		Token t = p.get_token();
1207 		if (t.cat() == catEscape &&
1208 		    is_known(t.cs(), known_if_commands))
1209 			handle_if(p, in_lyx_preamble);
1210 		else {
1211 			if (!in_lyx_preamble)
1212 				h_preamble << t.asInput();
1213 			if (t.cat() == catEscape && t.cs() == "fi")
1214 				return;
1215 		}
1216 	}
1217 }
1218 
1219 
writeLyXHeader(ostream & os,bool subdoc,string const & outfiledir)1220 bool Preamble::writeLyXHeader(ostream & os, bool subdoc, string const & outfiledir)
1221 {
1222 	if (contains(h_float_placement, "H"))
1223 		registerAutomaticallyLoadedPackage("float");
1224 	if (h_spacing != "single" && h_spacing != "default")
1225 		registerAutomaticallyLoadedPackage("setspace");
1226 	if (h_use_packages["amsmath"] == "2") {
1227 		// amsbsy and amstext are already provided by amsmath
1228 		registerAutomaticallyLoadedPackage("amsbsy");
1229 		registerAutomaticallyLoadedPackage("amstext");
1230 	}
1231 
1232 	// output the LyX file settings
1233 	// Important: Keep the version formatting in sync with LyX and
1234 	//            lyx2lyx (bug 7951)
1235 	string const origin = roundtripMode() ? "roundtrip" : outfiledir;
1236 	os << "#LyX file created by tex2lyx " << lyx_version_major << '.'
1237 	   << lyx_version_minor << '\n'
1238 	   << "\\lyxformat " << LYX_FORMAT << '\n'
1239 	   << "\\begin_document\n"
1240 	   << "\\begin_header\n"
1241 	   << "\\save_transient_properties " << h_save_transient_properties << "\n"
1242 	   << "\\origin " << origin << "\n"
1243 	   << "\\textclass " << h_textclass << "\n";
1244 	string const raw = subdoc ? empty_string() : h_preamble.str();
1245 	if (!raw.empty()) {
1246 		os << "\\begin_preamble\n";
1247 		for (string::size_type i = 0; i < raw.size(); ++i) {
1248 			if (raw[i] == package_beg_sep) {
1249 				// Here follows some package loading code that
1250 				// must be skipped if the package is loaded
1251 				// automatically.
1252 				string::size_type j = raw.find(package_mid_sep, i);
1253 				if (j == string::npos)
1254 					return false;
1255 				string::size_type k = raw.find(package_end_sep, j);
1256 				if (k == string::npos)
1257 					return false;
1258 				string const package = raw.substr(i + 1, j - i - 1);
1259 				string const replacement = raw.substr(j + 1, k - j - 1);
1260 				if (auto_packages.find(package) == auto_packages.end())
1261 					os << replacement;
1262 				i = k;
1263 			} else
1264 				os.put(raw[i]);
1265 		}
1266 		os << "\n\\end_preamble\n";
1267 	}
1268 	if (!h_options.empty())
1269 		os << "\\options " << h_options << "\n";
1270 	os << "\\use_default_options " << h_use_default_options << "\n";
1271 	if (!used_modules.empty()) {
1272 		os << "\\begin_modules\n";
1273 		vector<string>::const_iterator const end = used_modules.end();
1274 		vector<string>::const_iterator it = used_modules.begin();
1275 		for (; it != end; ++it)
1276 			os << *it << '\n';
1277 		os << "\\end_modules\n";
1278 	}
1279 	if (!h_includeonlys.empty()) {
1280 		os << "\\begin_includeonly\n";
1281 		for (auto const & iofile : h_includeonlys)
1282 			os << iofile << '\n';
1283 		os << "\\end_includeonly\n";
1284 	}
1285 	os << "\\maintain_unincluded_children " << h_maintain_unincluded_children << "\n"
1286 	   << "\\language " << h_language << "\n"
1287 	   << "\\language_package " << h_language_package << "\n"
1288 	   << "\\inputencoding " << h_inputencoding << "\n"
1289 	   << "\\fontencoding " << h_fontencoding << "\n"
1290 	   << "\\font_roman \"" << h_font_roman[0]
1291 	   << "\" \"" << h_font_roman[1] << "\"\n"
1292 	   << "\\font_sans \"" << h_font_sans[0] << "\" \"" << h_font_sans[1] << "\"\n"
1293 	   << "\\font_typewriter \"" << h_font_typewriter[0]
1294 	   << "\" \"" << h_font_typewriter[1] << "\"\n"
1295 	   << "\\font_math \"" << h_font_math[0] << "\" \"" << h_font_math[1] << "\"\n"
1296 	   << "\\font_default_family " << h_font_default_family << "\n"
1297 	   << "\\use_non_tex_fonts " << (h_use_non_tex_fonts ? "true" : "false") << '\n'
1298 	   << "\\font_sc " << h_font_sc << "\n"
1299 	   << "\\font_osf " << h_font_osf << "\n"
1300 	   << "\\font_sf_scale " << h_font_sf_scale[0]
1301 	   << ' ' << h_font_sf_scale[1] << '\n'
1302 	   << "\\font_tt_scale " << h_font_tt_scale[0]
1303 	   << ' ' << h_font_tt_scale[1] << '\n';
1304 	if (!h_font_cjk.empty())
1305 		os << "\\font_cjk " << h_font_cjk << '\n';
1306 	os << "\\use_microtype " << h_use_microtype << '\n'
1307 	   << "\\use_dash_ligatures " << h_use_dash_ligatures << '\n'
1308 	   << "\\graphics " << h_graphics << '\n'
1309 	   << "\\default_output_format " << h_default_output_format << "\n"
1310 	   << "\\output_sync " << h_output_sync << "\n";
1311 	if (h_output_sync == "1")
1312 		os << "\\output_sync_macro \"" << h_output_sync_macro << "\"\n";
1313 	os << "\\bibtex_command " << h_bibtex_command << "\n"
1314 	   << "\\index_command " << h_index_command << "\n";
1315 	if (!h_float_placement.empty())
1316 		os << "\\float_placement " << h_float_placement << "\n";
1317 	os << "\\paperfontsize " << h_paperfontsize << "\n"
1318 	   << "\\spacing " << h_spacing << "\n"
1319 	   << "\\use_hyperref " << h_use_hyperref << '\n';
1320 	if (h_use_hyperref == "true") {
1321 		if (!h_pdf_title.empty())
1322 			os << "\\pdf_title " << Lexer::quoteString(h_pdf_title) << '\n';
1323 		if (!h_pdf_author.empty())
1324 			os << "\\pdf_author " << Lexer::quoteString(h_pdf_author) << '\n';
1325 		if (!h_pdf_subject.empty())
1326 			os << "\\pdf_subject " << Lexer::quoteString(h_pdf_subject) << '\n';
1327 		if (!h_pdf_keywords.empty())
1328 			os << "\\pdf_keywords " << Lexer::quoteString(h_pdf_keywords) << '\n';
1329 		os << "\\pdf_bookmarks " << h_pdf_bookmarks << "\n"
1330 		      "\\pdf_bookmarksnumbered " << h_pdf_bookmarksnumbered << "\n"
1331 		      "\\pdf_bookmarksopen " << h_pdf_bookmarksopen << "\n"
1332 		      "\\pdf_bookmarksopenlevel " << h_pdf_bookmarksopenlevel << "\n"
1333 		      "\\pdf_breaklinks " << h_pdf_breaklinks << "\n"
1334 		      "\\pdf_pdfborder " << h_pdf_pdfborder << "\n"
1335 		      "\\pdf_colorlinks " << h_pdf_colorlinks << "\n"
1336 		      "\\pdf_backref " << h_pdf_backref << "\n"
1337 		      "\\pdf_pdfusetitle " << h_pdf_pdfusetitle << '\n';
1338 		if (!h_pdf_pagemode.empty())
1339 			os << "\\pdf_pagemode " << h_pdf_pagemode << '\n';
1340 		if (!h_pdf_quoted_options.empty())
1341 			os << "\\pdf_quoted_options " << Lexer::quoteString(h_pdf_quoted_options) << '\n';
1342 	}
1343 	os << "\\papersize " << h_papersize << "\n"
1344 	   << "\\use_geometry " << h_use_geometry << '\n';
1345 	for (map<string, string>::const_iterator it = h_use_packages.begin();
1346 	     it != h_use_packages.end(); ++it)
1347 		os << "\\use_package " << it->first << ' ' << it->second << '\n';
1348 	os << "\\cite_engine " << h_cite_engine << '\n'
1349 	   << "\\cite_engine_type " << h_cite_engine_type << '\n'
1350 	   << "\\biblio_style " << h_biblio_style << "\n"
1351 	   << "\\use_bibtopic " << h_use_bibtopic << "\n";
1352 	if (!h_biblio_options.empty())
1353 		os << "\\biblio_options " << h_biblio_options << "\n";
1354 	if (!h_biblatex_bibstyle.empty())
1355 		os << "\\biblatex_bibstyle " << h_biblatex_bibstyle << "\n";
1356 	if (!h_biblatex_citestyle.empty())
1357 		os << "\\biblatex_citestyle " << h_biblatex_citestyle << "\n";
1358 	if (!h_multibib.empty())
1359 		os << "\\multibib " << h_multibib << "\n";
1360 	os << "\\use_indices " << h_use_indices << "\n"
1361 	   << "\\paperorientation " << h_paperorientation << '\n'
1362 	   << "\\suppress_date " << h_suppress_date << '\n'
1363 	   << "\\justification " << h_justification << '\n'
1364 	   << "\\use_refstyle " << h_use_refstyle << '\n'
1365 	   << "\\use_minted " << h_use_minted << '\n';
1366 	if (!h_fontcolor.empty())
1367 		os << "\\fontcolor " << h_fontcolor << '\n';
1368 	if (!h_notefontcolor.empty())
1369 		os << "\\notefontcolor " << h_notefontcolor << '\n';
1370 	if (!h_backgroundcolor.empty())
1371 		os << "\\backgroundcolor " << h_backgroundcolor << '\n';
1372 	if (!h_boxbgcolor.empty())
1373 		os << "\\boxbgcolor " << h_boxbgcolor << '\n';
1374 	if (index_number != 0)
1375 		for (int i = 0; i < index_number; i++) {
1376 			os << "\\index " << h_index[i] << '\n'
1377 			   << "\\shortcut " << h_shortcut[i] << '\n'
1378 			   << "\\color " << h_color << '\n'
1379 			   << "\\end_index\n";
1380 		}
1381 	else {
1382 		os << "\\index " << h_index[0] << '\n'
1383 		   << "\\shortcut " << h_shortcut[0] << '\n'
1384 		   << "\\color " << h_color << '\n'
1385 		   << "\\end_index\n";
1386 	}
1387 	os << h_margins
1388 	   << "\\secnumdepth " << h_secnumdepth << "\n"
1389 	   << "\\tocdepth " << h_tocdepth << "\n"
1390 	   << "\\paragraph_separation " << h_paragraph_separation << "\n";
1391 	if (h_paragraph_separation == "skip")
1392 		os << "\\defskip " << h_defskip << "\n";
1393 	else
1394 		os << "\\paragraph_indentation " << h_paragraph_indentation << "\n";
1395 	os << "\\is_math_indent " << h_is_mathindent << "\n";
1396 	if (!h_mathindentation.empty())
1397 		os << "\\math_indentation " << h_mathindentation << "\n";
1398 	os << "\\math_numbering_side " << h_math_numbering_side << "\n";
1399 	os << "\\quotes_style " << h_quotes_style << "\n"
1400 	   << "\\dynamic_quotes " << h_dynamic_quotes << "\n"
1401 	   << "\\papercolumns " << h_papercolumns << "\n"
1402 	   << "\\papersides " << h_papersides << "\n"
1403 	   << "\\paperpagestyle " << h_paperpagestyle << "\n";
1404 	if (!h_listings_params.empty())
1405 		os << "\\listings_params " << h_listings_params << "\n";
1406 	os << "\\tracking_changes " << h_tracking_changes << "\n"
1407 	   << "\\output_changes " << h_output_changes << "\n"
1408 	   << "\\html_math_output " << h_html_math_output << "\n"
1409 	   << "\\html_css_as_file " << h_html_css_as_file << "\n"
1410 	   << "\\html_be_strict " << h_html_be_strict << "\n"
1411 	   << authors_
1412 	   << "\\end_header\n\n"
1413 	   << "\\begin_body\n";
1414 	return true;
1415 }
1416 
1417 
parse(Parser & p,string const & forceclass,TeX2LyXDocClass & tc)1418 void Preamble::parse(Parser & p, string const & forceclass,
1419                      TeX2LyXDocClass & tc)
1420 {
1421 	// initialize fixed types
1422 	special_columns_['D'] = 3;
1423 	parse(p, forceclass, false, tc);
1424 }
1425 
1426 
parse(Parser & p,string const & forceclass,bool detectEncoding,TeX2LyXDocClass & tc)1427 void Preamble::parse(Parser & p, string const & forceclass,
1428                      bool detectEncoding, TeX2LyXDocClass & tc)
1429 {
1430 	bool is_full_document = false;
1431 	bool is_lyx_file = false;
1432 	bool in_lyx_preamble = false;
1433 
1434 	// determine whether this is a full document or a fragment for inclusion
1435 	while (p.good()) {
1436 		Token const & t = p.get_token();
1437 
1438 		if (t.cat() == catEscape && t.cs() == "documentclass") {
1439 			is_full_document = true;
1440 			break;
1441 		}
1442 	}
1443 	p.reset();
1444 
1445 	if (detectEncoding && !is_full_document)
1446 		return;
1447 
1448 	while (is_full_document && p.good()) {
1449 		if (detectEncoding && h_inputencoding != "auto" &&
1450 		    h_inputencoding != "default")
1451 			return;
1452 
1453 		Token const & t = p.get_token();
1454 
1455 #ifdef FILEDEBUG
1456 		if (!detectEncoding)
1457 			cerr << "t: " << t << '\n';
1458 #endif
1459 
1460 		//
1461 		// cat codes
1462 		//
1463 		if (!in_lyx_preamble &&
1464 		    (t.cat() == catLetter ||
1465 		     t.cat() == catSuper ||
1466 		     t.cat() == catSub ||
1467 		     t.cat() == catOther ||
1468 		     t.cat() == catMath ||
1469 		     t.cat() == catActive ||
1470 		     t.cat() == catBegin ||
1471 		     t.cat() == catEnd ||
1472 		     t.cat() == catAlign ||
1473 		     t.cat() == catParameter)) {
1474 			h_preamble << t.cs();
1475 			continue;
1476 		}
1477 
1478 		if (!in_lyx_preamble &&
1479 		    (t.cat() == catSpace || t.cat() == catNewline)) {
1480 			h_preamble << t.asInput();
1481 			continue;
1482 		}
1483 
1484 		if (t.cat() == catComment) {
1485 			static regex const islyxfile("%% LyX .* created this file");
1486 			static regex const usercommands("User specified LaTeX commands");
1487 
1488 			string const comment = t.asInput();
1489 
1490 			// magically switch encoding default if it looks like XeLaTeX
1491 			static string const magicXeLaTeX =
1492 				"% This document must be compiled with XeLaTeX ";
1493 			if (comment.size() > magicXeLaTeX.size()
1494 				  && comment.substr(0, magicXeLaTeX.size()) == magicXeLaTeX
1495 				  && h_inputencoding == "auto") {
1496 				if (!detectEncoding)
1497 					cerr << "XeLaTeX comment found, switching to UTF8\n";
1498 				h_inputencoding = "utf8";
1499 			}
1500 			smatch sub;
1501 			if (regex_search(comment, sub, islyxfile)) {
1502 				is_lyx_file = true;
1503 				in_lyx_preamble = true;
1504 			} else if (is_lyx_file
1505 				   && regex_search(comment, sub, usercommands))
1506 				in_lyx_preamble = false;
1507 			else if (!in_lyx_preamble)
1508 				h_preamble << t.asInput();
1509 			continue;
1510 		}
1511 
1512 		if (t.cs() == "PassOptionsToPackage") {
1513 			string const poptions = p.getArg('{', '}');
1514 			string const package = p.verbatim_item();
1515 			extra_package_options_.insert(make_pair(package, poptions));
1516 			continue;
1517 		}
1518 
1519 		if (t.cs() == "pagestyle") {
1520 			h_paperpagestyle = p.verbatim_item();
1521 			continue;
1522 		}
1523 
1524 		if (t.cs() == "setdefaultlanguage") {
1525 			xetex = true;
1526 			// We don't yet care about non-language variant options
1527 			// because LyX doesn't support this yet, see bug #8214
1528 			if (p.hasOpt()) {
1529 				string langopts = p.getOpt();
1530 				// check if the option contains a variant, if yes, extract it
1531 				string::size_type pos_var = langopts.find("variant");
1532 				string::size_type i = langopts.find(',', pos_var);
1533 				string::size_type k = langopts.find('=', pos_var);
1534 				if (pos_var != string::npos){
1535 					string variant;
1536 					if (i == string::npos)
1537 						variant = langopts.substr(k + 1, langopts.length() - k - 2);
1538 					else
1539 						variant = langopts.substr(k + 1, i - k - 1);
1540 					h_language = variant;
1541 				}
1542 				p.verbatim_item();
1543 			} else
1544 				h_language = p.verbatim_item();
1545 			//finally translate the poyglossia name to a LyX name
1546 			h_language = polyglossia2lyx(h_language);
1547 			continue;
1548 		}
1549 
1550 		if (t.cs() == "setotherlanguage") {
1551 			// We don't yet care about the option because LyX doesn't
1552 			// support this yet, see bug #8214
1553 			p.hasOpt() ? p.getOpt() : string();
1554 			p.verbatim_item();
1555 			continue;
1556 		}
1557 
1558 		if (t.cs() == "setmainfont") {
1559 			// we don't care about the option
1560 			p.hasOpt() ? p.getOpt() : string();
1561 			h_font_roman[1] = p.getArg('{', '}');
1562 			continue;
1563 		}
1564 
1565 		if (t.cs() == "setsansfont" || t.cs() == "setmonofont") {
1566 			// LyX currently only supports the scale option
1567 			string scale;
1568 			if (p.hasOpt()) {
1569 				string fontopts = p.getArg('[', ']');
1570 				// check if the option contains a scaling, if yes, extract it
1571 				string::size_type pos = fontopts.find("Scale");
1572 				if (pos != string::npos) {
1573 					string::size_type i = fontopts.find(',', pos);
1574 					if (i == string::npos)
1575 						scale_as_percentage(fontopts.substr(pos + 1), scale);
1576 					else
1577 						scale_as_percentage(fontopts.substr(pos, i - pos), scale);
1578 				}
1579 			}
1580 			if (t.cs() == "setsansfont") {
1581 				if (!scale.empty())
1582 					h_font_sf_scale[1] = scale;
1583 				h_font_sans[1] = p.getArg('{', '}');
1584 			} else {
1585 				if (!scale.empty())
1586 					h_font_tt_scale[1] = scale;
1587 				h_font_typewriter[1] = p.getArg('{', '}');
1588 			}
1589 			continue;
1590 		}
1591 
1592 		if (t.cs() == "date") {
1593 			string argument = p.getArg('{', '}');
1594 			if (argument.empty())
1595 				h_suppress_date = "true";
1596 			else
1597 				h_preamble << t.asInput() << '{' << argument << '}';
1598 			continue;
1599 		}
1600 
1601 		if (t.cs() == "color") {
1602 			string const space =
1603 				(p.hasOpt() ? p.getOpt() : string());
1604 			string argument = p.getArg('{', '}');
1605 			// check the case that a standard color is used
1606 			if (space.empty() && is_known(argument, known_basic_colors)) {
1607 				h_fontcolor = rgbcolor2code(argument);
1608 				registerAutomaticallyLoadedPackage("color");
1609 			} else if (space.empty() && argument == "document_fontcolor")
1610 				registerAutomaticallyLoadedPackage("color");
1611 			// check the case that LyX's document_fontcolor is defined
1612 			// but not used for \color
1613 			else {
1614 				h_preamble << t.asInput();
1615 				if (!space.empty())
1616 					h_preamble << space;
1617 				h_preamble << '{' << argument << '}';
1618 				// the color might already be set because \definecolor
1619 				// is parsed before this
1620 				h_fontcolor = "";
1621 			}
1622 			continue;
1623 		}
1624 
1625 		if (t.cs() == "pagecolor") {
1626 			string argument = p.getArg('{', '}');
1627 			// check the case that a standard color is used
1628 			if (is_known(argument, known_basic_colors)) {
1629 				h_backgroundcolor = rgbcolor2code(argument);
1630 			} else if (argument == "page_backgroundcolor")
1631 				registerAutomaticallyLoadedPackage("color");
1632 			// check the case that LyX's page_backgroundcolor is defined
1633 			// but not used for \pagecolor
1634 			else {
1635 				h_preamble << t.asInput() << '{' << argument << '}';
1636 				// the color might already be set because \definecolor
1637 				// is parsed before this
1638 				h_backgroundcolor = "";
1639 			}
1640 			continue;
1641 		}
1642 
1643 		if (t.cs() == "makeatletter") {
1644 			// LyX takes care of this
1645 			p.setCatcode('@', catLetter);
1646 			continue;
1647 		}
1648 
1649 		if (t.cs() == "makeatother") {
1650 			// LyX takes care of this
1651 			p.setCatcode('@', catOther);
1652 			continue;
1653 		}
1654 
1655 		if (t.cs() == "makeindex") {
1656 			// LyX will re-add this if a print index command is found
1657 			p.skip_spaces();
1658 			continue;
1659 		}
1660 
1661 		if (t.cs() == "newindex") {
1662 			string const indexname = p.getArg('[', ']');
1663 			string const shortcut = p.verbatim_item();
1664 			if (!indexname.empty())
1665 				h_index[index_number] = indexname;
1666 			else
1667 				h_index[index_number] = shortcut;
1668 			h_shortcut[index_number] = shortcut;
1669 			index_number += 1;
1670 			p.skip_spaces();
1671 			continue;
1672 		}
1673 
1674 		if (t.cs() == "addbibresource") {
1675 			biblatex_bibliographies.push_back(removeExtension(p.getArg('{', '}')));
1676 			continue;
1677 		}
1678 
1679 		if (t.cs() == "bibliography") {
1680 			vector<string> bibs = getVectorFromString(p.getArg('{', '}'));
1681 			biblatex_bibliographies.insert(biblatex_bibliographies.end(), bibs.begin(), bibs.end());
1682 			continue;
1683 		}
1684 
1685 		if (t.cs() == "RS@ifundefined") {
1686 			string const name = p.verbatim_item();
1687 			string const body1 = p.verbatim_item();
1688 			string const body2 = p.verbatim_item();
1689 			// only non-lyxspecific stuff
1690 			if (in_lyx_preamble &&
1691 			    (name == "subsecref" || name == "thmref" || name == "lemref"))
1692 				p.skip_spaces();
1693 			else {
1694 				ostringstream ss;
1695 				ss << '\\' << t.cs();
1696 				ss << '{' << name << '}'
1697 				   << '{' << body1 << '}'
1698 				   << '{' << body2 << '}';
1699 				h_preamble << ss.str();
1700 			}
1701 			continue;
1702 		}
1703 
1704 		if (t.cs() == "AtBeginDocument") {
1705 			string const name = p.verbatim_item();
1706 			// only non-lyxspecific stuff
1707 			if (in_lyx_preamble &&
1708 			    (name == "\\providecommand\\partref[1]{\\ref{part:#1}}"
1709 				|| name == "\\providecommand\\chapref[1]{\\ref{chap:#1}}"
1710 				|| name == "\\providecommand\\secref[1]{\\ref{sec:#1}}"
1711 				|| name == "\\providecommand\\subsecref[1]{\\ref{subsec:#1}}"
1712 				|| name == "\\providecommand\\parref[1]{\\ref{par:#1}}"
1713 				|| name == "\\providecommand\\figref[1]{\\ref{fig:#1}}"
1714 				|| name == "\\providecommand\\tabref[1]{\\ref{tab:#1}}"
1715 				|| name == "\\providecommand\\algref[1]{\\ref{alg:#1}}"
1716 				|| name == "\\providecommand\\fnref[1]{\\ref{fn:#1}}"
1717 				|| name == "\\providecommand\\enuref[1]{\\ref{enu:#1}}"
1718 				|| name == "\\providecommand\\eqref[1]{\\ref{eq:#1}}"
1719 				|| name == "\\providecommand\\lemref[1]{\\ref{lem:#1}}"
1720 				|| name == "\\providecommand\\thmref[1]{\\ref{thm:#1}}"
1721 				|| name == "\\providecommand\\corref[1]{\\ref{cor:#1}}"
1722 				|| name == "\\providecommand\\propref[1]{\\ref{prop:#1}}"))
1723 				p.skip_spaces();
1724 			else {
1725 				ostringstream ss;
1726 				ss << '\\' << t.cs();
1727 				ss << '{' << name << '}';
1728 				h_preamble << ss.str();
1729 			}
1730 			continue;
1731 		}
1732 
1733 		if (t.cs() == "newcommand" || t.cs() == "newcommandx"
1734 		    || t.cs() == "renewcommand" || t.cs() == "renewcommandx"
1735 		    || t.cs() == "providecommand" || t.cs() == "providecommandx"
1736 		    || t.cs() == "DeclareRobustCommand"
1737 		    || t.cs() == "DeclareRobustCommandx"
1738 		    || t.cs() == "ProvideTextCommandDefault"
1739 		    || t.cs() == "DeclareMathAccent") {
1740 			bool star = false;
1741 			if (p.next_token().character() == '*') {
1742 				p.get_token();
1743 				star = true;
1744 			}
1745 			string const name = p.verbatim_item();
1746 			string const opt1 = p.getFullOpt();
1747 			string const opt2 = p.getFullOpt();
1748 			string const body = p.verbatim_item();
1749 			// store the in_lyx_preamble setting
1750 			bool const was_in_lyx_preamble = in_lyx_preamble;
1751 			// font settings
1752 			if (name == "\\rmdefault")
1753 				if (is_known(body, known_roman_fonts)) {
1754 					h_font_roman[0] = body;
1755 					p.skip_spaces();
1756 					in_lyx_preamble = true;
1757 				}
1758 			if (name == "\\sfdefault")
1759 				if (is_known(body, known_sans_fonts)) {
1760 					h_font_sans[0] = body;
1761 					p.skip_spaces();
1762 					in_lyx_preamble = true;
1763 				}
1764 			if (name == "\\ttdefault")
1765 				if (is_known(body, known_typewriter_fonts)) {
1766 					h_font_typewriter[0] = body;
1767 					p.skip_spaces();
1768 					in_lyx_preamble = true;
1769 				}
1770 			if (name == "\\familydefault") {
1771 				string family = body;
1772 				// remove leading "\"
1773 				h_font_default_family = family.erase(0,1);
1774 				p.skip_spaces();
1775 				in_lyx_preamble = true;
1776 			}
1777 
1778 			// remove LyX-specific definitions that are re-added by LyX
1779 			// if necessary
1780 			// \lyxline is an ancient command that is converted by tex2lyx into
1781 			// a \rule therefore remove its preamble code
1782 			if (name == "\\lyxdot" || name == "\\lyxarrow"
1783 			    || name == "\\lyxline" || name == "\\LyX") {
1784 				p.skip_spaces();
1785 				in_lyx_preamble = true;
1786 			}
1787 
1788 			// Add the command to the known commands
1789 			add_known_command(name, opt1, !opt2.empty(), from_utf8(body));
1790 
1791 			// only non-lyxspecific stuff
1792 			if (!in_lyx_preamble) {
1793 				ostringstream ss;
1794 				ss << '\\' << t.cs();
1795 				if (star)
1796 					ss << '*';
1797 				ss << '{' << name << '}' << opt1 << opt2
1798 				   << '{' << body << "}";
1799 				if (prefixIs(t.cs(), "renew") || !contains(h_preamble.str(), ss.str()))
1800 					h_preamble << ss.str();
1801 /*
1802 				ostream & out = in_preamble ? h_preamble : os;
1803 				out << "\\" << t.cs() << "{" << name << "}"
1804 				    << opts << "{" << body << "}";
1805 */
1806 			}
1807 			// restore the in_lyx_preamble setting
1808 			in_lyx_preamble = was_in_lyx_preamble;
1809 			continue;
1810 		}
1811 
1812 		if (t.cs() == "documentclass") {
1813 			vector<string>::iterator it;
1814 			vector<string> opts = split_options(p.getArg('[', ']'));
1815 			handle_opt(opts, known_fontsizes, h_paperfontsize);
1816 			delete_opt(opts, known_fontsizes);
1817 			// delete "pt" at the end
1818 			string::size_type i = h_paperfontsize.find("pt");
1819 			if (i != string::npos)
1820 				h_paperfontsize.erase(i);
1821 			// The documentclass options are always parsed before the options
1822 			// of the babel call so that a language cannot overwrite the babel
1823 			// options.
1824 			handle_opt(opts, known_languages, h_language);
1825 			delete_opt(opts, known_languages);
1826 
1827 			// math indentation
1828 			if ((it = find(opts.begin(), opts.end(), "fleqn"))
1829 				 != opts.end()) {
1830 				h_is_mathindent = "1";
1831 				opts.erase(it);
1832 			}
1833 			// formula numbering side
1834 			if ((it = find(opts.begin(), opts.end(), "leqno"))
1835 				 != opts.end()) {
1836 				h_math_numbering_side = "left";
1837 				opts.erase(it);
1838 			}
1839 			else if ((it = find(opts.begin(), opts.end(), "reqno"))
1840 				 != opts.end()) {
1841 				h_math_numbering_side = "right";
1842 				opts.erase(it);
1843 			}
1844 
1845 			// paper orientation
1846 			if ((it = find(opts.begin(), opts.end(), "landscape")) != opts.end()) {
1847 				h_paperorientation = "landscape";
1848 				opts.erase(it);
1849 			}
1850 			// paper sides
1851 			if ((it = find(opts.begin(), opts.end(), "oneside"))
1852 				 != opts.end()) {
1853 				h_papersides = "1";
1854 				opts.erase(it);
1855 			}
1856 			if ((it = find(opts.begin(), opts.end(), "twoside"))
1857 				 != opts.end()) {
1858 				h_papersides = "2";
1859 				opts.erase(it);
1860 			}
1861 			// paper columns
1862 			if ((it = find(opts.begin(), opts.end(), "onecolumn"))
1863 				 != opts.end()) {
1864 				h_papercolumns = "1";
1865 				opts.erase(it);
1866 			}
1867 			if ((it = find(opts.begin(), opts.end(), "twocolumn"))
1868 				 != opts.end()) {
1869 				h_papercolumns = "2";
1870 				opts.erase(it);
1871 			}
1872 			// paper sizes
1873 			// some size options are known to any document classes, other sizes
1874 			// are handled by the \geometry command of the geometry package
1875 			handle_opt(opts, known_class_paper_sizes, h_papersize);
1876 			delete_opt(opts, known_class_paper_sizes);
1877 			// the remaining options
1878 			h_options = join(opts, ",");
1879 			// FIXME This does not work for classes that have a
1880 			//       different name in LyX than in LaTeX
1881 			h_textclass = p.getArg('{', '}');
1882 			p.skip_spaces();
1883 			continue;
1884 		}
1885 
1886 		if (t.cs() == "usepackage") {
1887 			string const options = p.getArg('[', ']');
1888 			string const name = p.getArg('{', '}');
1889 			vector<string> vecnames;
1890 			split(name, vecnames, ',');
1891 			vector<string>::const_iterator it  = vecnames.begin();
1892 			vector<string>::const_iterator end = vecnames.end();
1893 			for (; it != end; ++it)
1894 				handle_package(p, trimSpaceAndEol(*it), options,
1895 					       in_lyx_preamble, detectEncoding);
1896 			continue;
1897 		}
1898 
1899 		if (t.cs() == "inputencoding") {
1900 			string const encoding = p.getArg('{','}');
1901 			Encoding const * const enc = encodings.fromLaTeXName(
1902 				encoding, Encoding::inputenc, true);
1903 			if (!enc) {
1904 				if (!detectEncoding)
1905 					cerr << "Unknown encoding " << encoding
1906 					     << ". Ignoring." << std::endl;
1907 			} else {
1908 				if (!enc->unsafe())
1909 					h_inputencoding = enc->name();
1910 				p.setEncoding(enc->iconvName());
1911 			}
1912 			continue;
1913 		}
1914 
1915 		if (t.cs() == "newenvironment") {
1916 			string const name = p.getArg('{', '}');
1917 			string const opt1 = p.getFullOpt();
1918 			string const opt2 = p.getFullOpt();
1919 			string const beg = p.verbatim_item();
1920 			string const end = p.verbatim_item();
1921 			if (!in_lyx_preamble) {
1922 				h_preamble << "\\newenvironment{" << name
1923 				           << '}' << opt1 << opt2 << '{'
1924 				           << beg << "}{" << end << '}';
1925 			}
1926 			add_known_environment(name, opt1, !opt2.empty(),
1927 			                      from_utf8(beg), from_utf8(end));
1928 			continue;
1929 		}
1930 
1931 		if (t.cs() == "newtheorem") {
1932 			bool star = false;
1933 			if (p.next_token().character() == '*') {
1934 				p.get_token();
1935 				star = true;
1936 			}
1937 			string const name = p.getArg('{', '}');
1938 			string const opt1 = p.getFullOpt();
1939 			string const opt2 = p.getFullOpt();
1940 			string const body = p.verbatim_item();
1941 			string const opt3 = p.getFullOpt();
1942 			string const cmd = star ? "\\newtheorem*" : "\\newtheorem";
1943 
1944 			string const complete = cmd + "{" + name + '}' +
1945 				          opt1 + opt2 + '{' + body + '}' + opt3;
1946 
1947 			add_known_theorem(name, opt1, !opt2.empty(), from_utf8(complete));
1948 
1949 			if (!in_lyx_preamble)
1950 				h_preamble << complete;
1951 			continue;
1952 		}
1953 
1954 		if (t.cs() == "def") {
1955 			string name = p.get_token().cs();
1956 			// In fact, name may be more than the name:
1957 			// In the test case of bug 8116
1958 			// name == "csname SF@gobble@opt \endcsname".
1959 			// Therefore, we need to use asInput() instead of cs().
1960 			while (p.next_token().cat() != catBegin)
1961 				name += p.get_token().asInput();
1962 			if (!in_lyx_preamble)
1963 				h_preamble << "\\def\\" << name << '{'
1964 					   << p.verbatim_item() << "}";
1965 			continue;
1966 		}
1967 
1968 		if (t.cs() == "newcolumntype") {
1969 			string const name = p.getArg('{', '}');
1970 			trimSpaceAndEol(name);
1971 			int nargs = 0;
1972 			string opts = p.getOpt();
1973 			if (!opts.empty()) {
1974 				istringstream is(string(opts, 1));
1975 				is >> nargs;
1976 			}
1977 			special_columns_[name[0]] = nargs;
1978 			h_preamble << "\\newcolumntype{" << name << "}";
1979 			if (nargs)
1980 				h_preamble << "[" << nargs << "]";
1981 			h_preamble << "{" << p.verbatim_item() << "}";
1982 			continue;
1983 		}
1984 
1985 		if (t.cs() == "setcounter") {
1986 			string const name = p.getArg('{', '}');
1987 			string const content = p.getArg('{', '}');
1988 			if (name == "secnumdepth")
1989 				h_secnumdepth = content;
1990 			else if (name == "tocdepth")
1991 				h_tocdepth = content;
1992 			else
1993 				h_preamble << "\\setcounter{" << name << "}{" << content << "}";
1994 			continue;
1995 		}
1996 
1997 		if (t.cs() == "setlength") {
1998 			string const name = p.verbatim_item();
1999 			string const content = p.verbatim_item();
2000 			// the paragraphs are only not indented when \parindent is set to zero
2001 			if (name == "\\parindent" && content != "") {
2002 				if (content[0] == '0')
2003 					h_paragraph_separation = "skip";
2004 				else
2005 					h_paragraph_indentation = translate_len(content);
2006 			} else if (name == "\\parskip") {
2007 				if (content == "\\smallskipamount")
2008 					h_defskip = "smallskip";
2009 				else if (content == "\\medskipamount")
2010 					h_defskip = "medskip";
2011 				else if (content == "\\bigskipamount")
2012 					h_defskip = "bigskip";
2013 				else
2014 					h_defskip = translate_len(content);
2015 			} else if (name == "\\mathindent") {
2016 				h_mathindentation = translate_len(content);
2017 			} else
2018 				h_preamble << "\\setlength{" << name << "}{" << content << "}";
2019 			continue;
2020 		}
2021 
2022 		if (t.cs() == "onehalfspacing") {
2023 			h_spacing = "onehalf";
2024 			continue;
2025 		}
2026 
2027 		if (t.cs() == "doublespacing") {
2028 			h_spacing = "double";
2029 			continue;
2030 		}
2031 
2032 		if (t.cs() == "setstretch") {
2033 			h_spacing = "other " + p.verbatim_item();
2034 			continue;
2035 		}
2036 
2037 		if (t.cs() == "synctex") {
2038 			// the scheme is \synctex=value
2039 			// where value can only be "1" or "-1"
2040 			h_output_sync = "1";
2041 			// there can be any character behind the value (e.g. a linebreak or a '\'
2042 			// therefore we extract it char by char
2043 			p.get_token();
2044 			string value = p.get_token().asInput();
2045 			if (value == "-")
2046 				value += p.get_token().asInput();
2047 			h_output_sync_macro = "\\synctex=" + value;
2048 			continue;
2049 		}
2050 
2051 		if (t.cs() == "begin") {
2052 			string const name = p.getArg('{', '}');
2053 			if (name == "document")
2054 				break;
2055 			h_preamble << "\\begin{" << name << "}";
2056 			continue;
2057 		}
2058 
2059 		if (t.cs() == "geometry") {
2060 			vector<string> opts = split_options(p.getArg('{', '}'));
2061 			handle_geometry(opts);
2062 			continue;
2063 		}
2064 
2065 		if (t.cs() == "definecolor") {
2066 			string const color = p.getArg('{', '}');
2067 			string const space = p.getArg('{', '}');
2068 			string const value = p.getArg('{', '}');
2069 			if (color == "document_fontcolor" && space == "rgb") {
2070 				RGBColor c(RGBColorFromLaTeX(value));
2071 				h_fontcolor = X11hexname(c);
2072 			} else if (color == "note_fontcolor" && space == "rgb") {
2073 				RGBColor c(RGBColorFromLaTeX(value));
2074 				h_notefontcolor = X11hexname(c);
2075 			} else if (color == "page_backgroundcolor" && space == "rgb") {
2076 				RGBColor c(RGBColorFromLaTeX(value));
2077 				h_backgroundcolor = X11hexname(c);
2078 			} else if (color == "shadecolor" && space == "rgb") {
2079 				RGBColor c(RGBColorFromLaTeX(value));
2080 				h_boxbgcolor = X11hexname(c);
2081 			} else {
2082 				h_preamble << "\\definecolor{" << color
2083 				           << "}{" << space << "}{" << value
2084 				           << '}';
2085 			}
2086 			continue;
2087 		}
2088 
2089 		if (t.cs() == "bibliographystyle") {
2090 			h_biblio_style = p.verbatim_item();
2091 			continue;
2092 		}
2093 
2094 		if (t.cs() == "jurabibsetup") {
2095 			// FIXME p.getArg('{', '}') is most probably wrong (it
2096 			//       does not handle nested braces).
2097 			//       Use p.verbatim_item() instead.
2098 			vector<string> jurabibsetup =
2099 				split_options(p.getArg('{', '}'));
2100 			// add jurabibsetup to the jurabib package options
2101 			add_package("jurabib", jurabibsetup);
2102 			if (!jurabibsetup.empty()) {
2103 				h_preamble << "\\jurabibsetup{"
2104 					   << join(jurabibsetup, ",") << '}';
2105 			}
2106 			continue;
2107 		}
2108 
2109 		if (t.cs() == "hypersetup") {
2110 			vector<string> hypersetup =
2111 				split_options(p.verbatim_item());
2112 			// add hypersetup to the hyperref package options
2113 			handle_hyperref(hypersetup);
2114 			if (!hypersetup.empty()) {
2115 				h_preamble << "\\hypersetup{"
2116 				           << join(hypersetup, ",") << '}';
2117 			}
2118 			continue;
2119 		}
2120 
2121 		if (t.cs() == "includeonly") {
2122 			vector<string> includeonlys = getVectorFromString(p.getArg('{', '}'));
2123 			for (auto & iofile : includeonlys) {
2124 				string filename(normalize_filename(iofile));
2125 				string const path = getMasterFilePath(true);
2126 				// We want to preserve relative/absolute filenames,
2127 				// therefore path is only used for testing
2128 				if (!makeAbsPath(filename, path).exists()) {
2129 					// The file extension is probably missing.
2130 					// Now try to find it out.
2131 					string const tex_name =
2132 						find_file(filename, path,
2133 							  known_tex_extensions);
2134 					if (!tex_name.empty())
2135 						filename = tex_name;
2136 				}
2137 				string outname;
2138 				if (makeAbsPath(filename, path).exists())
2139 					fix_child_filename(filename);
2140 				else
2141 					cerr << "Warning: Could not find included file '"
2142 					     << filename << "'." << endl;
2143 				outname = changeExtension(filename, "lyx");
2144 				h_includeonlys.push_back(outname);
2145 			}
2146 			continue;
2147 		}
2148 
2149 		if (is_known(t.cs(), known_if_3arg_commands)) {
2150 			// prevent misparsing of \usepackage if it is used
2151 			// as an argument (see e.g. our own output of
2152 			// \@ifundefined above)
2153 			string const arg1 = p.verbatim_item();
2154 			string const arg2 = p.verbatim_item();
2155 			string const arg3 = p.verbatim_item();
2156 			// test case \@ifundefined{date}{}{\date{}}
2157 			if (t.cs() == "@ifundefined" && arg1 == "date" &&
2158 			    arg2.empty() && arg3 == "\\date{}") {
2159 				h_suppress_date = "true";
2160 			// older tex2lyx versions did output
2161 			// \@ifundefined{definecolor}{\usepackage{color}}{}
2162 			} else if (t.cs() == "@ifundefined" &&
2163 			           arg1 == "definecolor" &&
2164 			           arg2 == "\\usepackage{color}" &&
2165 			           arg3.empty()) {
2166 				if (!in_lyx_preamble)
2167 					h_preamble << package_beg_sep
2168 					           << "color"
2169 					           << package_mid_sep
2170 					           << "\\@ifundefined{definecolor}{color}{}"
2171 					           << package_end_sep;
2172 			// test for case
2173 			//\@ifundefined{showcaptionsetup}{}{%
2174 			// \PassOptionsToPackage{caption=false}{subfig}}
2175 			// that LyX uses for subfloats
2176 			} else if (t.cs() == "@ifundefined" &&
2177 			           arg1 == "showcaptionsetup" && arg2.empty()
2178 				&& arg3 == "%\n \\PassOptionsToPackage{caption=false}{subfig}") {
2179 				; // do nothing
2180 			} else if (!in_lyx_preamble) {
2181 				h_preamble << t.asInput()
2182 				           << '{' << arg1 << '}'
2183 				           << '{' << arg2 << '}'
2184 				           << '{' << arg3 << '}';
2185 			}
2186 			continue;
2187 		}
2188 
2189 		if (is_known(t.cs(), known_if_commands)) {
2190 			// must not parse anything in conditional code, since
2191 			// LyX would output the parsed contents unconditionally
2192 			if (!in_lyx_preamble)
2193 				h_preamble << t.asInput();
2194 			handle_if(p, in_lyx_preamble);
2195 			continue;
2196 		}
2197 
2198 		if (!t.cs().empty() && !in_lyx_preamble) {
2199 			h_preamble << '\\' << t.cs();
2200 			continue;
2201 		}
2202 	}
2203 
2204 	// remove the whitespace
2205 	p.skip_spaces();
2206 
2207 	// Force textclass if the user wanted it
2208 	if (!forceclass.empty())
2209 		h_textclass = forceclass;
2210 	tc.setName(h_textclass);
2211 	if (!LayoutFileList::get().haveClass(h_textclass) || !tc.load()) {
2212 		cerr << "Error: Could not read layout file for textclass \"" << h_textclass << "\"." << endl;
2213 		exit(EXIT_FAILURE);
2214 	}
2215 	if (h_papersides.empty()) {
2216 		ostringstream ss;
2217 		ss << tc.sides();
2218 		h_papersides = ss.str();
2219 	}
2220 
2221 	// If the CJK package is used we cannot set the document language from
2222 	// the babel options. Instead, we guess which language is used most
2223 	// and set this one.
2224 	default_language = h_language;
2225 	if (is_full_document &&
2226 	    (auto_packages.find("CJK") != auto_packages.end() ||
2227 	     auto_packages.find("CJKutf8") != auto_packages.end())) {
2228 		p.pushPosition();
2229 		h_language = guessLanguage(p, default_language);
2230 		p.popPosition();
2231 		if (explicit_babel && h_language != default_language) {
2232 			// We set the document language to a CJK language,
2233 			// but babel is explicitly called in the user preamble
2234 			// without options. LyX will not add the default
2235 			// language to the document options if it is either
2236 			// english, or no text is set as default language.
2237 			// Therefore we need to add a language option explicitly.
2238 			// FIXME: It would be better to remove all babel calls
2239 			//        from the user preamble, but this is difficult
2240 			//        without re-introducing bug 7861.
2241 			if (h_options.empty())
2242 				h_options = lyx2babel(default_language);
2243 			else
2244 				h_options += ',' + lyx2babel(default_language);
2245 		}
2246 	}
2247 
2248 	// Finally, set the quote style.
2249 	// LyX knows the following quotes styles:
2250 	// british, cjk, cjkangle, danish, english, french, german,
2251 	// polish, russian, swedish and swiss
2252 	// conversion list taken from
2253 	// https://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
2254 	// (quotes for kazakh are unknown)
2255 	// british
2256 	if (is_known(h_language, known_british_quotes_languages))
2257 		h_quotes_style = "british";
2258 	// cjk
2259 	else if (is_known(h_language, known_cjk_quotes_languages))
2260 		h_quotes_style = "cjk";
2261 	// cjkangle
2262 	else if (is_known(h_language, known_cjkangle_quotes_languages))
2263 		h_quotes_style = "cjkangle";
2264 	// danish
2265 	else if (is_known(h_language, known_danish_quotes_languages))
2266 		h_quotes_style = "danish";
2267 	// french
2268 	else if (is_known(h_language, known_french_quotes_languages))
2269 		h_quotes_style = "french";
2270 	// german
2271 	else if (is_known(h_language, known_german_quotes_languages))
2272 		h_quotes_style = "german";
2273 	// polish
2274 	else if (is_known(h_language, known_polish_quotes_languages))
2275 		h_quotes_style = "polish";
2276 	// russian
2277 	else if (is_known(h_language, known_russian_quotes_languages))
2278 		h_quotes_style = "russian";
2279 	// swedish
2280 	else if (is_known(h_language, known_swedish_quotes_languages))
2281 		h_quotes_style = "swedish";
2282 	// swiss
2283 	else if (is_known(h_language, known_swiss_quotes_languages))
2284 		h_quotes_style = "swiss";
2285 	// english
2286 	else if (is_known(h_language, known_english_quotes_languages))
2287 		h_quotes_style = "english";
2288 }
2289 
2290 
parseEncoding(Parser & p,string const & forceclass)2291 string Preamble::parseEncoding(Parser & p, string const & forceclass)
2292 {
2293 	TeX2LyXDocClass dummy;
2294 	parse(p, forceclass, true, dummy);
2295 	if (h_inputencoding != "auto" && h_inputencoding != "default")
2296 		return h_inputencoding;
2297 	return "";
2298 }
2299 
2300 
babel2lyx(string const & language)2301 string babel2lyx(string const & language)
2302 {
2303 	char const * const * where = is_known(language, known_languages);
2304 	if (where)
2305 		return known_coded_languages[where - known_languages];
2306 	return language;
2307 }
2308 
2309 
lyx2babel(string const & language)2310 string lyx2babel(string const & language)
2311 {
2312 	char const * const * where = is_known(language, known_coded_languages);
2313 	if (where)
2314 		return known_languages[where - known_coded_languages];
2315 	return language;
2316 }
2317 
2318 
polyglossia2lyx(string const & language)2319 string Preamble::polyglossia2lyx(string const & language)
2320 {
2321 	char const * const * where = is_known(language, polyglossia_languages);
2322 	if (where)
2323 		return coded_polyglossia_languages[where - polyglossia_languages];
2324 	return language;
2325 }
2326 
2327 
rgbcolor2code(string const & name)2328 string rgbcolor2code(string const & name)
2329 {
2330 	char const * const * where = is_known(name, known_basic_colors);
2331 	if (where) {
2332 		// "red", "green" etc
2333 		return known_basic_color_codes[where - known_basic_colors];
2334 	}
2335 	// "255,0,0", "0,255,0" etc
2336 	RGBColor c(RGBColorFromLaTeX(name));
2337 	return X11hexname(c);
2338 }
2339 
2340 // }])
2341 
2342 
2343 } // namespace lyx
2344