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