]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/tex2lyx.cpp
* Doxy: polish html output.
[lyx.git] / src / tex2lyx / tex2lyx.cpp
1 /**
2  * \file tex2lyx.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  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 // {[(
12
13 #include <config.h>
14
15 #include "tex2lyx.h"
16 #include "Context.h"
17
18 #include "TextClass.h"
19 #include "Layout.h"
20
21 #include "support/convert.h"
22 #include "support/debug.h"
23 #include "support/ExceptionMessage.h"
24 #include "support/filetools.h"
25 #include "support/lstrings.h"
26 #include "support/os.h"
27 #include "support/Package.h"
28
29 #include <fstream>
30 #include <iostream>
31 #include <string>
32 #include <sstream>
33 #include <vector>
34 #include <map>
35
36 using namespace std;
37 using namespace lyx::support;
38 using namespace lyx::support::os;
39
40 namespace lyx {
41
42 // Hacks to allow the thing to link in the lyxlayout stuff
43 LayoutPtr captionlayout;
44
45
46 string const trim(string const & a, char const * p)
47 {
48         // BOOST_ASSERT(p);
49
50         if (a.empty() || !*p)
51                 return a;
52
53         size_t r = a.find_last_not_of(p);
54         size_t l = a.find_first_not_of(p);
55
56         // Is this the minimal test? (lgb)
57         if (r == string::npos && l == string::npos)
58                 return string();
59
60         return a.substr(l, r - l + 1);
61 }
62
63
64 void split(string const & s, vector<string> & result, char delim)
65 {
66         //cerr << "split 1: '" << s << "'\n";
67         istringstream is(s);
68         string t;
69         while (getline(is, t, delim))
70                 result.push_back(t);
71         //cerr << "split 2\n";
72 }
73
74
75 string join(vector<string> const & input, char const * delim)
76 {
77         ostringstream os;
78         for (size_t i = 0; i != input.size(); ++i) {
79                 if (i)
80                         os << delim;
81                 os << input[i];
82         }
83         return os.str();
84 }
85
86
87 char const * const * is_known(string const & str, char const * const * what)
88 {
89         for ( ; *what; ++what)
90                 if (str == *what)
91                         return what;
92         return 0;
93 }
94
95
96
97 // current stack of nested environments
98 vector<string> active_environments;
99
100
101 string active_environment()
102 {
103         return active_environments.empty() ? string() : active_environments.back();
104 }
105
106
107 CommandMap known_commands;
108 CommandMap known_environments;
109 CommandMap known_math_environments;
110
111
112 void add_known_command(string const & command, string const & o1,
113         unsigned optionalsNum)
114 {
115         // We have to handle the following cases:
116         // definition                      o1    o2    invocation result
117         // \newcommand{\foo}{bar}          ""    false \foo       bar
118         // \newcommand{\foo}[1]{bar #1}    "[1]" false \foo{x}    bar x
119         // \newcommand{\foo}[1][]{bar #1}  "[1]" true  \foo       bar
120         // \newcommand{\foo}[1][]{bar #1}  "[1]" true  \foo[x]    bar x
121         // \newcommand{\foo}[1][x]{bar #1} "[1]" true  \foo[x]    bar x
122         // and the same with \newlyxcommand
123         unsigned int nargs = 0;
124         vector<ArgumentType> arguments;
125         string const opt1 = rtrim(ltrim(o1, "["), "]");
126         if (isStrUnsignedInt(opt1)) {
127                 // The command has arguments
128                 nargs = convert<unsigned int>(opt1);
129                 for (unsigned int i = 0; i < optionalsNum; ++i) {
130                         arguments.push_back(optional);
131                         --nargs;
132                 }
133         }
134         for (unsigned int i = 0; i < nargs; ++i)
135                 arguments.push_back(required);
136         known_commands[command] = arguments;
137 }
138
139
140 bool noweb_mode = false;
141
142
143 namespace {
144
145
146 /*!
147  * Read one command definition from the syntax file
148  */
149 void read_command(Parser & p, string command, CommandMap & commands)
150 {
151         if (p.next_token().asInput() == "*") {
152                 p.get_token();
153                 command += '*';
154         }
155         vector<ArgumentType> arguments;
156         while (p.next_token().cat() == catBegin ||
157                p.next_token().asInput() == "[") {
158                 if (p.next_token().cat() == catBegin) {
159                         string const arg = p.getArg('{', '}');
160                         if (arg == "translate")
161                                 arguments.push_back(required);
162                         else
163                                 arguments.push_back(verbatim);
164                 } else {
165                         p.getArg('[', ']');
166                         arguments.push_back(optional);
167                 }
168         }
169         commands[command] = arguments;
170 }
171
172
173 /*!
174  * Read a class of environments from the syntax file
175  */
176 void read_environment(Parser & p, string const & begin,
177                       CommandMap & environments)
178 {
179         string environment;
180         while (p.good()) {
181                 Token const & t = p.get_token();
182                 if (t.cat() == catLetter)
183                         environment += t.asInput();
184                 else if (!environment.empty()) {
185                         p.putback();
186                         read_command(p, environment, environments);
187                         environment.erase();
188                 }
189                 if (t.cat() == catEscape && t.asInput() == "\\end") {
190                         string const end = p.getArg('{', '}');
191                         if (end == begin)
192                                 return;
193                 }
194         }
195 }
196
197
198 /*!
199  * Read a list of TeX commands from a reLyX compatible syntax file.
200  * Since this list is used after all commands that have a LyX counterpart
201  * are handled, it does not matter that the "syntax.default" file
202  * has almost all of them listed. For the same reason the reLyX-specific
203  * reLyXre environment is ignored.
204  */
205 void read_syntaxfile(FileName const & file_name)
206 {
207         ifstream is(file_name.toFilesystemEncoding().c_str());
208         if (!is.good()) {
209                 cerr << "Could not open syntax file \"" << file_name
210                      << "\" for reading." << endl;
211                 exit(2);
212         }
213         // We can use our TeX parser, since the syntax of the layout file is
214         // modeled after TeX.
215         // Unknown tokens are just silently ignored, this helps us to skip some
216         // reLyX specific things.
217         Parser p(is);
218         while (p.good()) {
219                 Token const & t = p.get_token();
220                 if (t.cat() == catEscape) {
221                         string const command = t.asInput();
222                         if (command == "\\begin") {
223                                 string const name = p.getArg('{', '}');
224                                 if (name == "environments" || name == "reLyXre")
225                                         // We understand "reLyXre", but it is
226                                         // not as powerful as "environments".
227                                         read_environment(p, name,
228                                                 known_environments);
229                                 else if (name == "mathenvironments")
230                                         read_environment(p, name,
231                                                 known_math_environments);
232                         } else {
233                                 read_command(p, command, known_commands);
234                         }
235                 }
236         }
237 }
238
239
240 string documentclass;
241 string syntaxfile;
242 bool overwrite_files = false;
243
244
245 /// return the number of arguments consumed
246 typedef int (*cmd_helper)(string const &, string const &);
247
248
249 int parse_help(string const &, string const &)
250 {
251         cerr << "Usage: tex2lyx [ command line switches ] <infile.tex> [<outfile.lyx>]\n"
252                 "Command line switches (case sensitive):\n"
253                 "\t-help              summarize tex2lyx usage\n"
254                 "\t-f                 Force creation of .lyx files even if they exist already\n"
255                 "\t-userdir dir       try to set user directory to dir\n"
256                 "\t-sysdir dir        try to set system directory to dir\n"
257                 "\t-c textclass       declare the textclass\n"
258                 "\t-n                 translate a noweb (aka literate programming) file.\n"
259                 "\t-s syntaxfile      read additional syntax file" << endl;
260         exit(0);
261 }
262
263
264 int parse_class(string const & arg, string const &)
265 {
266         if (arg.empty()) {
267                 cerr << "Missing textclass string after -c switch" << endl;
268                 exit(1);
269         }
270         documentclass = arg;
271         return 1;
272 }
273
274
275 int parse_syntaxfile(string const & arg, string const &)
276 {
277         if (arg.empty()) {
278                 cerr << "Missing syntaxfile string after -s switch" << endl;
279                 exit(1);
280         }
281         syntaxfile = internal_path(arg);
282         return 1;
283 }
284
285
286 // Filled with the command line arguments "foo" of "-sysdir foo" or
287 // "-userdir foo".
288 string cl_system_support;
289 string cl_user_support;
290
291
292 int parse_sysdir(string const & arg, string const &)
293 {
294         if (arg.empty()) {
295                 cerr << "Missing directory for -sysdir switch" << endl;
296                 exit(1);
297         }
298         cl_system_support = internal_path(arg);
299         return 1;
300 }
301
302
303 int parse_userdir(string const & arg, string const &)
304 {
305         if (arg.empty()) {
306                 cerr << "Missing directory for -userdir switch" << endl;
307                 exit(1);
308         }
309         cl_user_support = internal_path(arg);
310         return 1;
311 }
312
313
314 int parse_force(string const &, string const &)
315 {
316         overwrite_files = true;
317         return 0;
318 }
319
320
321 int parse_noweb(string const &, string const &)
322 {
323         noweb_mode = true;
324         return 0;
325 }
326
327
328 void easyParse(int & argc, char * argv[])
329 {
330         map<string, cmd_helper> cmdmap;
331
332         cmdmap["-c"] = parse_class;
333         cmdmap["-f"] = parse_force;
334         cmdmap["-s"] = parse_syntaxfile;
335         cmdmap["-help"] = parse_help;
336         cmdmap["--help"] = parse_help;
337         cmdmap["-n"] = parse_noweb;
338         cmdmap["-sysdir"] = parse_sysdir;
339         cmdmap["-userdir"] = parse_userdir;
340
341         for (int i = 1; i < argc; ++i) {
342                 map<string, cmd_helper>::const_iterator it
343                         = cmdmap.find(argv[i]);
344
345                 // don't complain if not found - may be parsed later
346                 if (it == cmdmap.end())
347                         continue;
348
349                 string arg(to_utf8(from_local8bit((i + 1 < argc) ? argv[i + 1] : "")));
350                 string arg2(to_utf8(from_local8bit((i + 2 < argc) ? argv[i + 2] : "")));
351
352                 int const remove = 1 + it->second(arg, arg2);
353
354                 // Now, remove used arguments by shifting
355                 // the following ones remove places down.
356                 argc -= remove;
357                 for (int j = i; j < argc; ++j)
358                         argv[j] = argv[j + remove];
359                 --i;
360         }
361 }
362
363
364 // path of the first parsed file
365 string masterFilePath;
366 // path of the currently parsed file
367 string parentFilePath;
368
369 } // anonymous namespace
370
371
372 string getMasterFilePath()
373 {
374         return masterFilePath;
375 }
376
377 string getParentFilePath()
378 {
379         return parentFilePath;
380 }
381
382
383 namespace {
384
385 /*!
386  *  Reads tex input from \a is and writes lyx output to \a os.
387  *  Uses some common settings for the preamble, so this should only
388  *  be used more than once for included documents.
389  *  Caution: Overwrites the existing preamble settings if the new document
390  *  contains a preamble.
391  *  You must ensure that \p parentFilePath is properly set before calling
392  *  this function!
393  */
394 void tex2lyx(istream & is, ostream & os)
395 {
396         Parser p(is);
397         //p.dump();
398
399         stringstream ss;
400         TextClass textclass = parse_preamble(p, ss, documentclass);
401         captionlayout = LayoutPtr(Layout::forCaption());
402
403         active_environments.push_back("document");
404         Context context(true, textclass);
405         parse_text(p, ss, FLAG_END, true, context);
406         if (Context::empty)
407                 // Empty document body. LyX needs at least one paragraph.
408                 context.check_layout(ss);
409         context.check_end_layout(ss);
410         ss << "\n\\end_body\n\\end_document\n";
411         active_environments.pop_back();
412         ss.seekg(0);
413         os << ss.str();
414 #ifdef TEST_PARSER
415         p.reset();
416         ofstream parsertest("parsertest.tex");
417         while (p.good())
418                 parsertest << p.get_token().asInput();
419         // <origfile> and parsertest.tex should now have identical content
420 #endif
421 }
422
423
424 /// convert TeX from \p infilename to LyX and write it to \p os
425 bool tex2lyx(FileName const & infilename, ostream & os)
426 {
427         ifstream is(infilename.toFilesystemEncoding().c_str());
428         if (!is.good()) {
429                 cerr << "Could not open input file \"" << infilename
430                      << "\" for reading." << endl;
431                 return false;
432         }
433         string const oldParentFilePath = parentFilePath;
434         parentFilePath = onlyPath(infilename.absFilename());
435         tex2lyx(is, os);
436         parentFilePath = oldParentFilePath;
437         return true;
438 }
439
440 } // anonymous namespace
441
442
443 bool tex2lyx(string const & infilename, FileName const & outfilename)
444 {
445         if (outfilename.isReadableFile()) {
446                 if (overwrite_files) {
447                         cerr << "Overwriting existing file "
448                              << outfilename << endl;
449                 } else {
450                         cerr << "Not overwriting existing file "
451                              << outfilename << endl;
452                         return false;
453                 }
454         } else {
455                 cerr << "Creating file " << outfilename << endl;
456         }
457         ofstream os(outfilename.toFilesystemEncoding().c_str());
458         if (!os.good()) {
459                 cerr << "Could not open output file \"" << outfilename
460                      << "\" for writing." << endl;
461                 return false;
462         }
463 #ifdef FILEDEBUG
464         cerr << "Input file: " << infilename << "\n";
465         cerr << "Output file: " << outfilename << "\n";
466 #endif
467         return tex2lyx(FileName(infilename), os);
468 }
469
470 } // namespace lyx
471
472
473 int main(int argc, char * argv[])
474 {
475         using namespace lyx;
476
477         lyxerr.setStream(cerr);
478
479         easyParse(argc, argv);
480
481         if (argc <= 1) {
482                 cerr << "Usage: tex2lyx [ command line switches ] <infile.tex> [<outfile.lyx>]\n"
483                           "See tex2lyx -help." << endl;
484                 return 2;
485         }
486
487         os::init(argc, argv);
488
489         try { init_package(internal_path(to_utf8(from_local8bit(argv[0]))),
490                 cl_system_support, cl_user_support,
491                 top_build_dir_is_two_levels_up);
492         } catch (ExceptionMessage const & message) {
493                 cerr << to_utf8(message.title_) << ":\n"
494                         << to_utf8(message.details_) << endl;
495                 if (message.type_ == ErrorException)
496                         exit(1);
497         }
498         
499         // Now every known option is parsed. Look for input and output
500         // file name (the latter is optional).
501         string infilename = internal_path(to_utf8(from_local8bit(argv[1])));
502         infilename = makeAbsPath(infilename).absFilename();
503         
504         string outfilename;
505         if (argc > 2) {
506                 outfilename = internal_path(to_utf8(from_local8bit(argv[2])));
507                 if (outfilename != "-")
508                         outfilename = makeAbsPath(outfilename).absFilename();
509         } else
510                 outfilename = changeExtension(infilename, ".lyx");
511
512         FileName const system_syntaxfile = libFileSearch("", "syntax.default");
513         if (system_syntaxfile.empty()) {
514                 cerr << "Error: Could not find syntax file \"syntax.default\"." << endl;
515                 exit(1);
516         }
517         read_syntaxfile(system_syntaxfile);
518         if (!syntaxfile.empty())
519                 read_syntaxfile(makeAbsPath(syntaxfile));
520
521         masterFilePath = onlyPath(infilename);
522         parentFilePath = masterFilePath;
523
524         if (outfilename == "-") {
525                 if (tex2lyx(FileName(infilename), cout))
526                         return EXIT_SUCCESS;
527                 else
528                         return EXIT_FAILURE;
529         } else {
530                 if (tex2lyx(infilename, FileName(outfilename)))
531                         return EXIT_SUCCESS;
532                 else
533                         return EXIT_FAILURE;
534         }
535 }
536
537 // }])