]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/preamble.cpp
* Doxy: polish html output.
[lyx.git] / src / tex2lyx / preamble.cpp
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 "tex2lyx.h"
17
18 #include "Layout.h"
19 #include "Lexer.h"
20 #include "TextClass.h"
21
22 #include "support/convert.h"
23 #include "support/FileName.h"
24 #include "support/filetools.h"
25 #include "support/lstrings.h"
26
27 #include <algorithm>
28 #include <iostream>
29 #include <sstream>
30 #include <string>
31 #include <vector>
32 #include <map>
33
34 using namespace std;
35 using namespace lyx::support;
36
37 namespace lyx {
38
39 // special columntypes
40 extern map<char, int> special_columns;
41
42 map<string, vector<string> > used_packages;
43
44 // needed to handle encodings with babel
45 bool one_language = true;
46
47 // to avoid that the babel options overwrite the documentclass options
48 bool documentclass_language;
49
50 namespace {
51
52 const char * const known_languages[] = { "afrikaans", "american", "arabic",
53 "austrian", "bahasa", "basque", "belarusian", "brazil", "breton", "british",
54 "bulgarian", "canadian", "canadien", "catalan", "croatian", "czech", "danish",
55 "dutch", "english", "esperanto", "estonian", "finnish", "francais", "french",
56 "frenchb", "frenchle", "frenchpro", "galician", "german", "germanb", "greek",
57 "hebrew", "icelandic", "irish", "italian", "lsorbian", "magyar", "naustrian",
58 "ngerman", "ngermanb", "norsk", "nynorsk", "polish", "portuges", "romanian",
59 "russian", "russianb", "scottish", "serbian", "slovak", "slovene", "spanish",
60 "swedish", "thai", "turkish", "ukraineb", "ukrainian", "usorbian", "welsh", 0};
61
62 //note this when updating to lyxformat 305:
63 //bahasai, indonesian, and indon = equal to bahasa
64 //malay, and meyalu = equal to bahasam
65
66 const char * const known_french_languages[] = {"french", "frenchb", "francais",
67                                                 "frenchle", "frenchpro", 0};
68 const char * const known_german_languages[] = {"german", "germanb", 0};
69 const char * const known_ngerman_languages[] = {"ngerman", "ngermanb", 0};
70 const char * const known_russian_languages[] = {"russian", "russianb", 0};
71 const char * const known_ukrainian_languages[] = {"ukrainian", "ukraineb", 0};
72
73 char const * const known_fontsizes[] = { "10pt", "11pt", "12pt", 0 };
74
75 const char * const known_roman_fonts[] = { "ae", "bookman", "charter",
76 "cmr", "fourier", "lmodern", "mathpazo", "mathptmx", "newcent", 0};
77
78 const char * const known_sans_fonts[] = { "avant", "berasans", "cmbr", "cmss",
79 "helvet", "lmss", 0};
80
81 const char * const known_typewriter_fonts[] = { "beramono", "cmtl", "cmtt",
82 "courier", "lmtt", "luximono", "fourier", "lmodern", "mathpazo", "mathptmx",
83 "newcent", 0};
84
85 // some ugly stuff
86 ostringstream h_preamble;
87 string h_textclass               = "article";
88 string h_options                 = string();
89 string h_language                = "english";
90 string h_inputencoding           = "auto";
91 string h_font_roman              = "default";
92 string h_font_sans               = "default";
93 string h_font_typewriter         = "default";
94 string h_font_default_family     = "default";
95 string h_font_sc                 = "false";
96 string h_font_osf                = "false";
97 string h_font_sf_scale           = "100";
98 string h_font_tt_scale           = "100";
99 string h_graphics                = "default";
100 string h_paperfontsize           = "default";
101 string h_spacing                 = "single";
102 string h_papersize               = "default";
103 string h_use_geometry            = "false";
104 string h_use_amsmath             = "0";
105 string h_cite_engine             = "basic";
106 string h_use_bibtopic            = "false";
107 string h_paperorientation        = "portrait";
108 string h_secnumdepth             = "3";
109 string h_tocdepth                = "3";
110 string h_paragraph_separation    = "indent";
111 string h_defskip                 = "medskip";
112 string h_quotes_language         = "english";
113 string h_papercolumns            = "1";
114 string h_papersides              = string();
115 string h_paperpagestyle          = "default";
116 string h_tracking_changes        = "false";
117 string h_output_changes          = "false";
118
119
120 void handle_opt(vector<string> & opts, char const * const * what, string & target)
121 {
122         if (opts.empty())
123                 return;
124
125         // the last language option is the document language (for babel and LyX)
126         // the last size option is the document font size
127         vector<string>::iterator it;
128         vector<string>::iterator position = opts.begin();
129         for (; *what; ++what) {
130                 it = find(opts.begin(), opts.end(), *what);
131                 if (it != opts.end()) {
132                         documentclass_language = true;
133                         if (it >= position) {
134                                 target = *what;
135                                 position = it;
136                         }
137                 }
138         }
139 }
140
141
142 void delete_opt(vector<string> & opts, char const * const * what)
143 {
144         if (opts.empty())
145                 return;
146
147         // remove found options from the list
148         // do this after handle_opt to avoid potential memory leaks and to be able
149         // to find in every case the last language option
150         vector<string>::iterator it;
151         for (; *what; ++what) {
152                 it = find(opts.begin(), opts.end(), *what);
153                 if (it != opts.end())
154                         opts.erase(it);
155         }
156 }
157
158
159 /*!
160  * Split a package options string (keyval format) into a vector.
161  * Example input:
162  *   authorformat=smallcaps,
163  *   commabeforerest,
164  *   titleformat=colonsep,
165  *   bibformat={tabular,ibidem,numbered}
166  */
167 vector<string> split_options(string const & input)
168 {
169         vector<string> options;
170         string option;
171         Parser p(input);
172         while (p.good()) {
173                 Token const & t = p.get_token();
174                 if (t.asInput() == ",") {
175                         options.push_back(trim(option));
176                         option.erase();
177                 } else if (t.asInput() == "=") {
178                         option += '=';
179                         p.skip_spaces(true);
180                         if (p.next_token().asInput() == "{")
181                                 option += '{' + p.getArg('{', '}') + '}';
182                 } else if (t.cat() != catSpace)
183                         option += t.asInput();
184         }
185
186         if (!option.empty())
187                 options.push_back(trim(option));
188
189         return options;
190 }
191
192
193 /*!
194  * Add package \p name with options \p options to used_packages.
195  * Remove options from \p options that we don't want to output.
196  */
197 void add_package(string const & name, vector<string> & options)
198 {
199         // every package inherits the global options
200         if (used_packages.find(name) == used_packages.end())
201                 used_packages[name] = split_options(h_options);
202
203         vector<string> & v = used_packages[name];
204         v.insert(v.end(), options.begin(), options.end());
205         if (name == "jurabib") {
206                 // Don't output the order argument (see the cite command
207                 // handling code in text.cpp).
208                 vector<string>::iterator end =
209                         remove(options.begin(), options.end(), "natbiborder");
210                 end = remove(options.begin(), end, "jurabiborder");
211                 options.erase(end, options.end());
212         }
213 }
214
215
216 // Given is a string like "scaled=0.9", return 0.9 * 100
217 string const scale_as_percentage(string const & scale)
218 {
219         string::size_type pos = scale.find('=');
220         if (pos != string::npos) {
221                 string value = scale.substr(pos + 1);
222                 if (isStrDbl(value))
223                         return convert<string>(100 * convert<double>(value));
224         }
225         // If the input string didn't match our expectations.
226         // return the default value "100"
227         return "100";
228 }
229
230
231 void handle_package(string const & name, string const & opts)
232 {
233         vector<string> options = split_options(opts);
234         add_package(name, options);
235         string scale;
236
237         // roman fonts
238         if (is_known(name, known_roman_fonts))
239                 h_font_roman = name;
240         if (name == "fourier") {
241                 h_font_roman = "utopia";
242                 // when font uses real small capitals
243                 if (opts == "expert")
244                         h_font_sc = "true";
245         }
246         if (name == "mathpazo")
247                 h_font_roman = "palatino";
248         if (name == "mathptmx")
249                 h_font_roman = "times";
250         // sansserif fonts
251         if (is_known(name, known_sans_fonts)) {
252                 h_font_sans = name;
253                 if (!opts.empty()) {
254                         scale = opts;
255                         h_font_sf_scale = scale_as_percentage(scale);
256                 }
257         }
258         // typewriter fonts
259         if (is_known(name, known_typewriter_fonts)) {
260                 h_font_typewriter = name;
261                 if (!opts.empty()) {
262                         scale = opts;
263                         h_font_tt_scale = scale_as_percentage(scale);
264                 }
265         }
266         // font uses old-style figure
267         if (name == "eco")
268                 h_font_osf = "true";
269
270         else if (name == "amsmath" || name == "amssymb")
271                 h_use_amsmath = "1";
272         else if (name == "babel" && !opts.empty()) {
273                 // check if more than one option was used - used later for inputenc
274                 // in case inputenc is parsed before babel, set the encoding to auto
275                 if (options.begin() != options.end() - 1) {
276                         one_language = false;
277                         h_inputencoding = "auto";
278                 }
279                 // only set the document language when there was not already one set
280                 // via the documentclass options
281                 // babel takes the the last language given in the documentclass options
282                 // as document language. If there is no such language option, the last
283                 // option of its \usepackage call is used.
284                 if (documentclass_language == false) {
285                         handle_opt(options, known_languages, h_language);
286                         delete_opt(options, known_languages);
287                         if (is_known(h_language, known_french_languages))
288                                 h_language = "french";
289                         else if (is_known(h_language, known_german_languages))
290                                 h_language = "german";
291                         else if (is_known(h_language, known_ngerman_languages))
292                                 h_language = "ngerman";
293                         else if (is_known(h_language, known_russian_languages))
294                                 h_language = "russian";
295                         else if (is_known(h_language, known_ukrainian_languages))
296                                 h_language = "ukrainian";
297                         h_quotes_language = h_language;
298                 }
299         }
300         else if (name == "fontenc")
301                 ; // ignore this
302         else if (name == "inputenc") {
303                 // only set when there is not more than one inputenc option
304                 // therefore check for the "," character
305                 // also only set when there is not more then one babel language option
306                 if (opts.find(",") == string::npos && one_language == true)
307                         h_inputencoding = opts;
308                 options.clear();
309         } else if (name == "makeidx")
310                 ; // ignore this
311         else if (name == "verbatim")
312                 ; // ignore this
313         else if (name == "graphicx")
314                 ; // ignore this
315         else if (is_known(name, known_languages)) {
316                 if (is_known(name, known_french_languages))
317                         h_language = "french";
318                 else if (is_known(name, known_german_languages))
319                         h_language = "german";
320                 else if (is_known(name, known_ngerman_languages))
321                         h_language = "ngerman";
322                 else if (is_known(name, known_russian_languages))
323                         h_language = "russian";
324                 else if (is_known(name, known_ukrainian_languages))
325                         h_language = "ukrainian";
326                 else
327                         h_language = name;
328                 h_quotes_language = h_language;
329
330         } else if (name == "natbib") {
331                 h_cite_engine = "natbib_authoryear";
332                 vector<string>::iterator it =
333                         find(options.begin(), options.end(), "authoryear");
334                 if (it != options.end())
335                         options.erase(it);
336                 else {
337                         it = find(options.begin(), options.end(), "numbers");
338                         if (it != options.end()) {
339                                 h_cite_engine = "natbib_numerical";
340                                 options.erase(it);
341                         }
342                 }
343         } else if (name == "jurabib") {
344                 h_cite_engine = "jurabib";
345         } else if (options.empty())
346                 h_preamble << "\\usepackage{" << name << "}\n";
347         else {
348                 h_preamble << "\\usepackage[" << opts << "]{" << name << "}\n";
349                 options.clear();
350         }
351
352         // We need to do something with the options...
353         if (!options.empty())
354                 cerr << "Ignoring options '" << join(options, ",")
355                      << "' of package " << name << '.' << endl;
356 }
357
358
359
360 void end_preamble(ostream & os, TextClass const & /*textclass*/)
361 {
362         os << "#LyX file created by tex2lyx " << PACKAGE_VERSION << "\n"
363            << "\\lyxformat 247\n"
364            << "\\begin_document\n"
365            << "\\begin_header\n"
366            << "\\textclass " << h_textclass << "\n";
367         if (!h_preamble.str().empty())
368                 os << "\\begin_preamble\n" << h_preamble.str() << "\n\\end_preamble\n";
369         if (!h_options.empty())
370                 os << "\\options " << h_options << "\n";
371         os << "\\language " << h_language << "\n"
372            << "\\inputencoding " << h_inputencoding << "\n"
373            << "\\font_roman " << h_font_roman << "\n"
374            << "\\font_sans " << h_font_sans << "\n"
375            << "\\font_typewriter " << h_font_typewriter << "\n"
376            << "\\font_default_family " << h_font_default_family << "\n"
377            << "\\font_sc " << h_font_sc << "\n"
378            << "\\font_osf " << h_font_osf << "\n"
379            << "\\font_sf_scale " << h_font_sf_scale << "\n"
380            << "\\font_tt_scale " << h_font_tt_scale << "\n"
381            << "\\graphics " << h_graphics << "\n"
382            << "\\paperfontsize " << h_paperfontsize << "\n"
383            << "\\spacing " << h_spacing << "\n"
384            << "\\papersize " << h_papersize << "\n"
385            << "\\use_geometry " << h_use_geometry << "\n"
386            << "\\use_amsmath " << h_use_amsmath << "\n"
387            << "\\cite_engine " << h_cite_engine << "\n"
388            << "\\use_bibtopic " << h_use_bibtopic << "\n"
389            << "\\paperorientation " << h_paperorientation << "\n"
390            << "\\secnumdepth " << h_secnumdepth << "\n"
391            << "\\tocdepth " << h_tocdepth << "\n"
392            << "\\paragraph_separation " << h_paragraph_separation << "\n"
393            << "\\defskip " << h_defskip << "\n"
394            << "\\quotes_language " << h_quotes_language << "\n"
395            << "\\papercolumns " << h_papercolumns << "\n"
396            << "\\papersides " << h_papersides << "\n"
397            << "\\paperpagestyle " << h_paperpagestyle << "\n"
398            << "\\tracking_changes " << h_tracking_changes << "\n"
399            << "\\output_changes " << h_output_changes << "\n"
400            << "\\end_header\n\n"
401            << "\\begin_body\n";
402         // clear preamble for subdocuments
403         h_preamble.str("");
404 }
405
406 } // anonymous namespace
407
408 TextClass const parse_preamble(Parser & p, ostream & os, string const & forceclass)
409 {
410         // initialize fixed types
411         special_columns['D'] = 3;
412         bool is_full_document = false;
413
414         // determine whether this is a full document or a fragment for inclusion
415         while (p.good()) {
416                 Token const & t = p.get_token();
417
418                 if (t.cat() == catEscape && t.cs() == "documentclass") {
419                         is_full_document = true;
420                         break;
421                 }
422         }
423         p.reset();
424
425         while (is_full_document && p.good()) {
426                 Token const & t = p.get_token();
427
428 #ifdef FILEDEBUG
429                 cerr << "t: " << t << "\n";
430 #endif
431
432                 //
433                 // cat codes
434                 //
435                 if (t.cat() == catLetter ||
436                           t.cat() == catSuper ||
437                           t.cat() == catSub ||
438                           t.cat() == catOther ||
439                           t.cat() == catMath ||
440                           t.cat() == catActive ||
441                           t.cat() == catBegin ||
442                           t.cat() == catEnd ||
443                           t.cat() == catAlign ||
444                           t.cat() == catParameter)
445                 h_preamble << t.character();
446
447                 else if (t.cat() == catSpace || t.cat() == catNewline)
448                         h_preamble << t.asInput();
449
450                 else if (t.cat() == catComment)
451                         h_preamble << t.asInput();
452
453                 else if (t.cs() == "pagestyle")
454                         h_paperpagestyle = p.verbatim_item();
455
456                 else if (t.cs() == "makeatletter") {
457                         p.setCatCode('@', catLetter);
458                 }
459
460                 else if (t.cs() == "makeatother") {
461                         p.setCatCode('@', catOther);
462                 }
463
464                 else if (t.cs() == "newcommand" 
465                          || t.cs() == "renewcommand"
466                          || t.cs() == "providecommand"
467                          || t.cs() == "newlyxcommand") {
468                         bool star = false;
469                         if (p.next_token().character() == '*') {
470                                 p.get_token();
471                                 star = true;
472                         }
473                         string const name = p.verbatim_item();
474                         string const opt1 = p.getOpt();
475                         string const opt2 = p.getFullOpt();
476                         string const body = p.verbatim_item();
477                         // font settings
478                         if (name == "\\rmdefault")
479                                 if (is_known(body, known_roman_fonts))
480                                         h_font_roman = body;
481
482                         if (name == "\\sfdefault")
483                                 if (is_known(body, known_sans_fonts))
484                                         h_font_sans = body;
485
486                         if (name == "\\ttdefault")
487                                 if (is_known(body, known_typewriter_fonts))
488                                         h_font_typewriter = body;
489
490                         if (name == "\\familydefault") {
491                                 string family = body;
492                                 // remove leading "\"
493                                 h_font_default_family = family.erase(0,1);
494                         }
495                         // only non-lyxspecific stuff
496                         if (   name != "\\noun"
497                             && name != "\\tabularnewline"
498                             && name != "\\LyX"
499                             && name != "\\lyxline"
500                             && name != "\\lyxaddress"
501                             && name != "\\lyxrightaddress"
502                             && name != "\\lyxdot"
503                             && name != "\\boldsymbol"
504                             && name != "\\lyxarrow"
505                             && name != "\\rmdefault"
506                             && name != "\\sfdefault"
507                             && name != "\\ttdefault"
508                             && name != "\\familydefault") {
509                                 ostringstream ss;
510                                 ss << '\\' << t.cs();
511                                 if (star)
512                                         ss << '*';
513                                 ss << '{' << name << '}' << opt1 << opt2
514                                    << '{' << body << "}";
515                                 h_preamble << ss.str();
516
517                                 // Add the command to the known commands
518                                 add_known_command(name, opt1, !opt2.empty());
519 /*
520                                 ostream & out = in_preamble ? h_preamble : os;
521                                 out << "\\" << t.cs() << "{" << name << "}"
522                                     << opts << "{" << body << "}";
523 */
524                         }
525                 }
526
527                 else if (t.cs() == "documentclass") {
528                         vector<string> opts = split_options(p.getArg('[', ']'));
529                         handle_opt(opts, known_fontsizes, h_paperfontsize);
530                         delete_opt(opts, known_fontsizes);
531                         // delete "pt" at the end
532                         string::size_type i = h_paperfontsize.find("pt");
533                         if (i != string::npos)
534                                 h_paperfontsize.erase(i);
535                         // to avoid that the babel options overwrite the documentclass options
536                         documentclass_language = false;
537                         handle_opt(opts, known_languages, h_language);
538                         delete_opt(opts, known_languages);
539                         if (is_known(h_language, known_french_languages))
540                                 h_language = "french";
541                         else if (is_known(h_language, known_german_languages))
542                                 h_language = "german";
543                         else if (is_known(h_language, known_ngerman_languages))
544                                 h_language = "ngerman";
545                         else if (is_known(h_language, known_russian_languages))
546                                 h_language = "russian";
547                         else if (is_known(h_language, known_ukrainian_languages))
548                                 h_language = "ukrainian";
549                         h_quotes_language = h_language;
550                         h_options = join(opts, ",");
551                         h_textclass = p.getArg('{', '}');
552                 }
553
554                 else if (t.cs() == "usepackage") {
555                         string const options = p.getArg('[', ']');
556                         string const name = p.getArg('{', '}');
557                         if (options.empty() && name.find(',')) {
558                                 vector<string> vecnames;
559                                 split(name, vecnames, ',');
560                                 vector<string>::const_iterator it  = vecnames.begin();
561                                 vector<string>::const_iterator end = vecnames.end();
562                                 for (; it != end; ++it)
563                                         handle_package(trim(*it), string());
564                         } else {
565                                 handle_package(name, options);
566                         }
567                 }
568
569                 else if (t.cs() == "newenvironment") {
570                         string const name = p.getArg('{', '}');
571                         ostringstream ss;
572                         ss << "\\newenvironment{" << name << "}";
573                         ss << p.getOpt();
574                         ss << p.getOpt();
575                         ss << '{' << p.verbatim_item() << '}';
576                         ss << '{' << p.verbatim_item() << '}';
577                         if (name != "lyxcode" && name != "lyxlist" &&
578                             name != "lyxrightadress" &&
579                             name != "lyxaddress" && name != "lyxgreyedout")
580                                 h_preamble << ss.str();
581                 }
582
583                 else if (t.cs() == "def") {
584                         string name = p.get_token().cs();
585                         while (p.next_token().cat() != catBegin)
586                                 name += p.get_token().asString();
587                         h_preamble << "\\def\\" << name << '{'
588                                    << p.verbatim_item() << "}";
589                 }
590
591                 else if (t.cs() == "newcolumntype") {
592                         string const name = p.getArg('{', '}');
593                         trim(name);
594                         int nargs = 0;
595                         string opts = p.getOpt();
596                         if (!opts.empty()) {
597                                 istringstream is(string(opts, 1));
598                                 is >> nargs;
599                         }
600                         special_columns[name[0]] = nargs;
601                         h_preamble << "\\newcolumntype{" << name << "}";
602                         if (nargs)
603                                 h_preamble << "[" << nargs << "]";
604                         h_preamble << "{" << p.verbatim_item() << "}";
605                 }
606
607                 else if (t.cs() == "setcounter") {
608                         string const name = p.getArg('{', '}');
609                         string const content = p.getArg('{', '}');
610                         if (name == "secnumdepth")
611                                 h_secnumdepth = content;
612                         else if (name == "tocdepth")
613                                 h_tocdepth = content;
614                         else
615                                 h_preamble << "\\setcounter{" << name << "}{" << content << "}";
616                 }
617
618                 else if (t.cs() == "setlength") {
619                         string const name = p.verbatim_item();
620                         string const content = p.verbatim_item();
621                         // Is this correct?
622                         if (name == "parskip")
623                                 h_paragraph_separation = "skip";
624                         else if (name == "parindent")
625                                 h_paragraph_separation = "skip";
626                         else
627                                 h_preamble << "\\setlength{" << name << "}{" << content << "}";
628                 }
629
630                 else if (t.cs() == "begin") {
631                         string const name = p.getArg('{', '}');
632                         if (name == "document")
633                                 break;
634                         h_preamble << "\\begin{" << name << "}";
635                 }
636
637                 else if (t.cs() == "jurabibsetup") {
638                         vector<string> jurabibsetup =
639                                 split_options(p.getArg('{', '}'));
640                         // add jurabibsetup to the jurabib package options
641                         add_package("jurabib", jurabibsetup);
642                         if (!jurabibsetup.empty()) {
643                                 h_preamble << "\\jurabibsetup{"
644                                            << join(jurabibsetup, ",") << '}';
645                         }
646                 }
647
648                 else if (!t.cs().empty())
649                         h_preamble << '\\' << t.cs();
650         }
651         p.skip_spaces();
652
653         // Force textclass if the user wanted it
654         if (!forceclass.empty())
655                 h_textclass = forceclass;
656         if (noweb_mode && !prefixIs(h_textclass, "literate-"))
657                 h_textclass.insert(0, "literate-");
658         FileName layoutfilename = libFileSearch("layouts", h_textclass, "layout");
659         if (layoutfilename.empty()) {
660                 cerr << "Error: Could not find layout file for textclass \"" << h_textclass << "\"." << endl;
661                 exit(1);
662         }
663         TextClass textclass;
664         textclass.read(layoutfilename);
665         if (h_papersides.empty()) {
666                 ostringstream ss;
667                 ss << textclass.sides();
668                 h_papersides = ss.str();
669         }
670         end_preamble(os, textclass);
671         return textclass;
672 }
673
674 // }])
675
676
677 } // namespace lyx