]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/tex2lyx.cpp
Move bind file format tag to LyXAction.cpp, and rename it.
[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
17 #include "Context.h"
18 #include "Encoding.h"
19 #include "Layout.h"
20 #include "TextClass.h"
21
22 #include "support/convert.h"
23 #include "support/debug.h"
24 #include "support/ExceptionMessage.h"
25 #include "support/filetools.h"
26 #include "support/lassert.h"
27 #include "support/lstrings.h"
28 #include "support/Messages.h"
29 #include "support/os.h"
30 #include "support/Package.h"
31 #include "support/Systemcall.h"
32
33 #include <cstdlib>
34 #include <iostream>
35 #include <string>
36 #include <sstream>
37 #include <vector>
38 #include <map>
39
40 using namespace std;
41 using namespace lyx::support;
42 using namespace lyx::support::os;
43
44 namespace lyx {
45
46 namespace frontend {
47 namespace Alert {
48         void warning(docstring const & title, docstring const & message,
49                                  bool const &)
50         {
51                 LYXERR0(title);
52                 LYXERR0(message);
53         }
54 }
55 }
56
57
58 // Dummy translation support
59 Messages messages_;
60 Messages const & getMessages(std::string const &)
61 {
62         return messages_;
63 }
64
65
66 Messages const & getGuiMessages()
67 {
68         return messages_;
69 }
70
71
72 // Keep the linker happy on Windows
73 void lyx_exit(int)
74 {}
75
76
77 string const trim(string const & a, char const * p)
78 {
79         // LASSERT(p, /**/);
80
81         if (a.empty() || !*p)
82                 return a;
83
84         size_t r = a.find_last_not_of(p);
85         size_t l = a.find_first_not_of(p);
86
87         // Is this the minimal test? (lgb)
88         if (r == string::npos && l == string::npos)
89                 return string();
90
91         return a.substr(l, r - l + 1);
92 }
93
94
95 void split(string const & s, vector<string> & result, char delim)
96 {
97         //cerr << "split 1: '" << s << "'\n";
98         istringstream is(s);
99         string t;
100         while (getline(is, t, delim))
101                 result.push_back(t);
102         //cerr << "split 2\n";
103 }
104
105
106 string join(vector<string> const & input, char const * delim)
107 {
108         ostringstream os;
109         for (size_t i = 0; i != input.size(); ++i) {
110                 if (i)
111                         os << delim;
112                 os << input[i];
113         }
114         return os.str();
115 }
116
117
118 char const * const * is_known(string const & str, char const * const * what)
119 {
120         for ( ; *what; ++what)
121                 if (str == *what)
122                         return what;
123         return 0;
124 }
125
126
127
128 // current stack of nested environments
129 vector<string> active_environments;
130
131
132 string active_environment()
133 {
134         return active_environments.empty() ? string() : active_environments.back();
135 }
136
137
138 CommandMap known_commands;
139 CommandMap known_environments;
140 CommandMap known_math_environments;
141
142
143 void add_known_command(string const & command, string const & o1,
144                        bool o2)
145 {
146         // We have to handle the following cases:
147         // definition                      o1    o2    invocation result
148         // \newcommand{\foo}{bar}          ""    false \foo       bar
149         // \newcommand{\foo}[1]{bar #1}    "[1]" false \foo{x}    bar x
150         // \newcommand{\foo}[1][]{bar #1}  "[1]" true  \foo       bar
151         // \newcommand{\foo}[1][]{bar #1}  "[1]" true  \foo[x]    bar x
152         // \newcommand{\foo}[1][x]{bar #1} "[1]" true  \foo[x]    bar x
153         unsigned int nargs = 0;
154         vector<ArgumentType> arguments;
155         string const opt1 = rtrim(ltrim(o1, "["), "]");
156         if (isStrUnsignedInt(opt1)) {
157                 // The command has arguments
158                 nargs = convert<unsigned int>(opt1);
159                 if (nargs > 0 && o2) {
160                         // The first argument is optional
161                         arguments.push_back(optional);
162                         --nargs;
163                 }
164         }
165         for (unsigned int i = 0; i < nargs; ++i)
166                 arguments.push_back(required);
167         known_commands[command] = arguments;
168 }
169
170
171 bool noweb_mode = false;
172 bool pdflatex = false;
173 bool roundtrip = false;
174
175
176 namespace {
177
178
179 /*!
180  * Read one command definition from the syntax file
181  */
182 void read_command(Parser & p, string command, CommandMap & commands)
183 {
184         if (p.next_token().asInput() == "*") {
185                 p.get_token();
186                 command += '*';
187         }
188         vector<ArgumentType> arguments;
189         while (p.next_token().cat() == catBegin ||
190                p.next_token().asInput() == "[") {
191                 if (p.next_token().cat() == catBegin) {
192                         string const arg = p.getArg('{', '}');
193                         if (arg == "translate")
194                                 arguments.push_back(required);
195                         else if (arg == "item")
196                                 arguments.push_back(item);
197                         else
198                                 arguments.push_back(verbatim);
199                 } else {
200                         p.getArg('[', ']');
201                         arguments.push_back(optional);
202                 }
203         }
204         commands[command] = arguments;
205 }
206
207
208 /*!
209  * Read a class of environments from the syntax file
210  */
211 void read_environment(Parser & p, string const & begin,
212                       CommandMap & environments)
213 {
214         string environment;
215         while (p.good()) {
216                 Token const & t = p.get_token();
217                 if (t.cat() == catLetter)
218                         environment += t.asInput();
219                 else if (!environment.empty()) {
220                         p.putback();
221                         read_command(p, environment, environments);
222                         environment.erase();
223                 }
224                 if (t.cat() == catEscape && t.asInput() == "\\end") {
225                         string const end = p.getArg('{', '}');
226                         if (end == begin)
227                                 return;
228                 }
229         }
230 }
231
232
233 /*!
234  * Read a list of TeX commands from a reLyX compatible syntax file.
235  * Since this list is used after all commands that have a LyX counterpart
236  * are handled, it does not matter that the "syntax.default" file
237  * has almost all of them listed. For the same reason the reLyX-specific
238  * reLyXre environment is ignored.
239  */
240 void read_syntaxfile(FileName const & file_name)
241 {
242         ifdocstream is(file_name.toFilesystemEncoding().c_str());
243         if (!is.good()) {
244                 cerr << "Could not open syntax file \"" << file_name
245                      << "\" for reading." << endl;
246                 exit(2);
247         }
248         // We can use our TeX parser, since the syntax of the layout file is
249         // modeled after TeX.
250         // Unknown tokens are just silently ignored, this helps us to skip some
251         // reLyX specific things.
252         Parser p(is);
253         while (p.good()) {
254                 Token const & t = p.get_token();
255                 if (t.cat() == catEscape) {
256                         string const command = t.asInput();
257                         if (command == "\\begin") {
258                                 string const name = p.getArg('{', '}');
259                                 if (name == "environments" || name == "reLyXre")
260                                         // We understand "reLyXre", but it is
261                                         // not as powerful as "environments".
262                                         read_environment(p, name,
263                                                 known_environments);
264                                 else if (name == "mathenvironments")
265                                         read_environment(p, name,
266                                                 known_math_environments);
267                         } else {
268                                 read_command(p, command, known_commands);
269                         }
270                 }
271         }
272 }
273
274
275 string documentclass;
276 string default_encoding;
277 string syntaxfile;
278 bool overwrite_files = false;
279 int error_code = 0;
280
281 /// return the number of arguments consumed
282 typedef int (*cmd_helper)(string const &, string const &);
283
284
285 int parse_help(string const &, string const &)
286 {
287         cerr << "Usage: tex2lyx [options] infile.tex [outfile.lyx]\n"
288                 "Options:\n"
289                 "\t-c textclass       Declare the textclass.\n"
290                 "\t-e encoding        Set the default encoding (latex name).\n"
291                 "\t-f                 Force overwrite of .lyx files.\n"
292                 "\t-help              Print this message and quit.\n"
293                 "\t-n                 translate a noweb (aka literate programming) file.\n"
294                 "\t-roundtrip         re-export created .lyx file infile.lyx.lyx to infile.lyx.tex.\n"
295                 "\t-s syntaxfile      read additional syntax file.\n"
296                 "\t-sysdir dir        Set system directory to DIR.\n"
297                 "\t-userdir DIR       Set user directory to DIR."
298              << endl;
299         exit(error_code);
300 }
301
302
303 void error_message(string const & message)
304 {
305         cerr << "tex2lyx: " << message << "\n\n";
306         error_code = 1;
307         parse_help(string(), string());
308 }
309
310
311 int parse_class(string const & arg, string const &)
312 {
313         if (arg.empty())
314                 error_message("Missing textclass string after -c switch");
315         documentclass = arg;
316         return 1;
317 }
318
319
320 int parse_encoding(string const & arg, string const &)
321 {
322         if (arg.empty())
323                 error_message("Missing encoding string after -e switch");
324         default_encoding = arg;
325         return 1;
326 }
327
328
329 int parse_syntaxfile(string const & arg, string const &)
330 {
331         if (arg.empty())
332                 error_message("Missing syntaxfile string after -s switch");
333         syntaxfile = internal_path(arg);
334         return 1;
335 }
336
337
338 // Filled with the command line arguments "foo" of "-sysdir foo" or
339 // "-userdir foo".
340 string cl_system_support;
341 string cl_user_support;
342
343
344 int parse_sysdir(string const & arg, string const &)
345 {
346         if (arg.empty())
347                 error_message("Missing directory for -sysdir switch");
348         cl_system_support = internal_path(arg);
349         return 1;
350 }
351
352
353 int parse_userdir(string const & arg, string const &)
354 {
355         if (arg.empty())
356                 error_message("Missing directory for -userdir switch");
357         cl_user_support = internal_path(arg);
358         return 1;
359 }
360
361
362 int parse_force(string const &, string const &)
363 {
364         overwrite_files = true;
365         return 0;
366 }
367
368
369 int parse_noweb(string const &, string const &)
370 {
371         noweb_mode = true;
372         return 0;
373 }
374
375
376 int parse_roundtrip(string const &, string const &)
377 {
378         roundtrip = true;
379         return 0;
380 }
381
382
383 void easyParse(int & argc, char * argv[])
384 {
385         map<string, cmd_helper> cmdmap;
386
387         cmdmap["-c"] = parse_class;
388         cmdmap["-e"] = parse_encoding;
389         cmdmap["-f"] = parse_force;
390         cmdmap["-s"] = parse_syntaxfile;
391         cmdmap["-help"] = parse_help;
392         cmdmap["--help"] = parse_help;
393         cmdmap["-n"] = parse_noweb;
394         cmdmap["-sysdir"] = parse_sysdir;
395         cmdmap["-userdir"] = parse_userdir;
396         cmdmap["-roundtrip"] = parse_roundtrip;
397
398         for (int i = 1; i < argc; ++i) {
399                 map<string, cmd_helper>::const_iterator it
400                         = cmdmap.find(argv[i]);
401
402                 // don't complain if not found - may be parsed later
403                 if (it == cmdmap.end()) {
404                         if (argv[i][0] == '-')
405                                 error_message(string("Unknown option `") + argv[i] + "'.");
406                         else
407                                 continue;
408                 }
409
410                 string arg = (i + 1 < argc) ? os::utf8_argv(i + 1) : string();
411                 string arg2 = (i + 2 < argc) ? os::utf8_argv(i + 2) : string();
412
413                 int const remove = 1 + it->second(arg, arg2);
414
415                 // Now, remove used arguments by shifting
416                 // the following ones remove places down.
417                 os::remove_internal_args(i, remove);
418                 argc -= remove;
419                 for (int j = i; j < argc; ++j)
420                         argv[j] = argv[j + remove];
421                 --i;
422         }
423 }
424
425
426 // path of the first parsed file
427 string masterFilePath;
428 // path of the currently parsed file
429 string parentFilePath;
430
431 } // anonymous namespace
432
433
434 string getMasterFilePath()
435 {
436         return masterFilePath;
437 }
438
439 string getParentFilePath()
440 {
441         return parentFilePath;
442 }
443
444
445 namespace {
446
447 /*!
448  *  Reads tex input from \a is and writes lyx output to \a os.
449  *  Uses some common settings for the preamble, so this should only
450  *  be used more than once for included documents.
451  *  Caution: Overwrites the existing preamble settings if the new document
452  *  contains a preamble.
453  *  You must ensure that \p parentFilePath is properly set before calling
454  *  this function!
455  */
456 void tex2lyx(idocstream & is, ostream & os, string const & encoding)
457 {
458         Parser p(is);
459         if (!encoding.empty())
460                 p.setEncoding(encoding);
461         //p.dump();
462
463         stringstream ss;
464         TeX2LyXDocClass textclass;
465         parse_preamble(p, ss, documentclass, textclass);
466
467         active_environments.push_back("document");
468         Context context(true, textclass);
469         parse_text(p, ss, FLAG_END, true, context);
470         if (Context::empty)
471                 // Empty document body. LyX needs at least one paragraph.
472                 context.check_layout(ss);
473         context.check_end_layout(ss);
474         ss << "\n\\end_body\n\\end_document\n";
475         active_environments.pop_back();
476         ss.seekg(0);
477         os << ss.str();
478 #ifdef TEST_PARSER
479         p.reset();
480         ofdocstream parsertest("parsertest.tex");
481         while (p.good())
482                 parsertest << p.get_token().asInput();
483         // <origfile> and parsertest.tex should now have identical content
484 #endif
485 }
486
487
488 /// convert TeX from \p infilename to LyX and write it to \p os
489 bool tex2lyx(FileName const & infilename, ostream & os, string const & encoding)
490 {
491         ifdocstream is;
492         // forbid buffering on this stream
493         is.rdbuf()->pubsetbuf(0,0);
494         is.open(infilename.toFilesystemEncoding().c_str());
495         if (!is.good()) {
496                 cerr << "Could not open input file \"" << infilename
497                      << "\" for reading." << endl;
498                 return false;
499         }
500         string const oldParentFilePath = parentFilePath;
501         parentFilePath = onlyPath(infilename.absFileName());
502         tex2lyx(is, os, encoding);
503         parentFilePath = oldParentFilePath;
504         return true;
505 }
506
507 } // anonymous namespace
508
509
510 bool tex2lyx(string const & infilename, FileName const & outfilename, 
511              string const & encoding)
512 {
513         if (outfilename.isReadableFile()) {
514                 if (overwrite_files) {
515                         cerr << "Overwriting existing file "
516                              << outfilename << endl;
517                 } else {
518                         cerr << "Not overwriting existing file "
519                              << outfilename << endl;
520                         return false;
521                 }
522         } else {
523                 cerr << "Creating file " << outfilename << endl;
524         }
525         ofstream os(outfilename.toFilesystemEncoding().c_str());
526         if (!os.good()) {
527                 cerr << "Could not open output file \"" << outfilename
528                      << "\" for writing." << endl;
529                 return false;
530         }
531 #ifdef FILEDEBUG
532         cerr << "Input file: " << infilename << "\n";
533         cerr << "Output file: " << outfilename << "\n";
534 #endif
535         return tex2lyx(FileName(infilename), os, encoding);
536 }
537
538
539 bool tex2tex(string const & infilename, FileName const & outfilename,
540              string const & encoding)
541 {
542         if (!tex2lyx(infilename, outfilename, encoding))
543                 return false;
544         string command = quoteName(package().lyx_binary().toFilesystemEncoding());
545         if (overwrite_files)
546                 command += " -f main";
547         else
548                 command += " -f none";
549         if (pdflatex)
550                 command += " -e pdflatex ";
551         else
552                 command += " -e latex ";
553         command += quoteName(outfilename.toFilesystemEncoding());
554         Systemcall one;
555         if (one.startscript(Systemcall::Wait, command) == 0)
556                 return true;
557         cerr << "Error: Running '" << command << "' failed." << endl;
558         return false;
559 }
560
561 } // namespace lyx
562
563
564 int main(int argc, char * argv[])
565 {
566         using namespace lyx;
567
568         //setlocale(LC_CTYPE, "");
569
570         lyxerr.setStream(cerr);
571
572         os::init(argc, argv);
573
574         easyParse(argc, argv);
575
576         if (argc <= 1) 
577                 error_message("Not enough arguments.");
578
579         try {
580                 init_package(internal_path(os::utf8_argv(0)),
581                              cl_system_support, cl_user_support,
582                              top_build_dir_is_two_levels_up);
583         } catch (ExceptionMessage const & message) {
584                 cerr << to_utf8(message.title_) << ":\n"
585                      << to_utf8(message.details_) << endl;
586                 if (message.type_ == ErrorException)
587                         return EXIT_FAILURE;
588         }
589
590         // Now every known option is parsed. Look for input and output
591         // file name (the latter is optional).
592         string infilename = internal_path(os::utf8_argv(1));
593         infilename = makeAbsPath(infilename).absFileName();
594
595         string outfilename;
596         if (roundtrip) {
597                 if (argc > 2) {
598                         // Do not allow a user supplied output filename
599                         // (otherwise it could easily happen that LyX would
600                         // overwrite the original .tex file)
601                         cerr << "Error: output filename must not be given in roundtrip mode."
602                              << endl;
603                         return EXIT_FAILURE;
604                 }
605                 outfilename = changeExtension(infilename, ".lyx.lyx");
606         } else if (argc > 2) {
607                 outfilename = internal_path(os::utf8_argv(2));
608                 if (outfilename != "-")
609                         outfilename = makeAbsPath(outfilename).absFileName();
610         } else
611                 outfilename = changeExtension(infilename, ".lyx");
612
613         // Read the syntax tables
614         FileName const system_syntaxfile = libFileSearch("", "syntax.default");
615         if (system_syntaxfile.empty()) {
616                 cerr << "Error: Could not find syntax file \"syntax.default\"." << endl;
617                 return EXIT_FAILURE;
618         }
619         read_syntaxfile(system_syntaxfile);
620         if (!syntaxfile.empty())
621                 read_syntaxfile(makeAbsPath(syntaxfile));
622
623         // Read the encodings table.
624         FileName const symbols_path = libFileSearch(string(), "unicodesymbols");
625         if (symbols_path.empty()) {
626                 cerr << "Error: Could not find file \"unicodesymbols\"." 
627                      << endl;
628                 return EXIT_FAILURE;
629         }
630         FileName const enc_path = libFileSearch(string(), "encodings");
631         if (enc_path.empty()) {
632                 cerr << "Error: Could not find file \"encodings\"." 
633                      << endl;
634                 return EXIT_FAILURE;
635         }
636         encodings.read(enc_path, symbols_path);
637         if (!default_encoding.empty() && !encodings.fromLaTeXName(default_encoding))
638                 error_message("Unknown LaTeX encoding `" + default_encoding + "'");
639
640         // The real work now.
641         masterFilePath = onlyPath(infilename);
642         parentFilePath = masterFilePath;
643         if (outfilename == "-") {
644                 if (tex2lyx(FileName(infilename), cout, default_encoding))
645                         return EXIT_SUCCESS;
646         } else if (roundtrip) {
647                 if (tex2tex(infilename, FileName(outfilename), default_encoding))
648                         return EXIT_SUCCESS;
649         } else {
650                 if (tex2lyx(infilename, FileName(outfilename), default_encoding))
651                         return EXIT_SUCCESS;
652         }
653         return EXIT_FAILURE;
654 }
655
656 // }])