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