]> git.lyx.org Git - lyx.git/blob - src/tex2lyx/tex2lyx.cpp
Limit the nopassthurchars case in beamer to URL
[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 #include <version.h>
15
16 #include "tex2lyx.h"
17
18 #include "Context.h"
19 #include "Encoding.h"
20 #include "Layout.h"
21 #include "LayoutFile.h"
22 #include "LayoutModuleList.h"
23 #include "ModuleList.h"
24 #include "Preamble.h"
25
26 #include "support/ConsoleApplication.h"
27 #include "support/convert.h"
28 #include "support/debug.h"
29 #include "support/ExceptionMessage.h"
30 #include "support/filetools.h"
31 #include "support/lassert.h"
32 #include "support/lstrings.h"
33 #include "support/os.h"
34 #include "support/Package.h"
35 #include "support/Systemcall.h"
36
37 #include <cstdlib>
38 #include <algorithm>
39 #include <exception>
40 #include <iostream>
41 #include <string>
42 #include <sstream>
43 #include <vector>
44 #include <map>
45
46 // comment out to enable debug_messages
47 //#define FILEDEBUG
48
49 using namespace std;
50 using namespace lyx::support;
51 using namespace lyx::support::os;
52
53 namespace lyx {
54
55 string const trimSpaceAndEol(string const & a)
56 {
57         return trim(a, " \t\n\r");
58 }
59
60
61 void split(string const & s, vector<string> & result, char delim)
62 {
63         //warning_message("split 1: '" + s + "'");
64         istringstream is(s);
65         string t;
66         while (getline(is, t, delim))
67                 result.push_back(t);
68         //warning_message("split 2");
69 }
70
71
72 string join(vector<string> const & input, char const * delim)
73 {
74         ostringstream os;
75         for (size_t i = 0; i != input.size(); ++i) {
76                 if (i)
77                         os << delim;
78                 os << input[i];
79         }
80         return os.str();
81 }
82
83
84 char const * const * is_known(string const & str, char const * const * what)
85 {
86         for ( ; *what; ++what)
87                 if (str == *what)
88                         return what;
89         return 0;
90 }
91
92
93
94 // current stack of nested environments
95 vector<string> active_environments;
96
97
98 string active_environment()
99 {
100         return active_environments.empty() ? string() : active_environments.back();
101 }
102
103
104 TeX2LyXDocClass textclass;
105 CommandMap known_commands;
106 CommandMap known_environments;
107 CommandMap known_math_environments;
108 FullCommandMap possible_textclass_commands;
109 FullEnvironmentMap possible_textclass_environments;
110 FullCommandMap possible_textclass_theorems;
111 int const LYX_FORMAT = LYX_FORMAT_TEX2LYX;
112
113 /// used modules
114 LayoutModuleList used_modules;
115 vector<string> preloaded_modules;
116
117
118 void convertArgs(string const & o1, bool o2, vector<ArgumentType> & arguments)
119 {
120         // We have to handle the following cases:
121         // definition                      o1    o2    invocation result
122         // \newcommand{\foo}{bar}          ""    false \foo       bar
123         // \newcommand{\foo}[1]{bar #1}    "[1]" false \foo{x}    bar x
124         // \newcommand{\foo}[1][]{bar #1}  "[1]" true  \foo       bar
125         // \newcommand{\foo}[1][]{bar #1}  "[1]" true  \foo[x]    bar x
126         // \newcommand{\foo}[1][x]{bar #1} "[1]" true  \foo[x]    bar x
127         unsigned int nargs = 0;
128         string const opt1 = rtrim(ltrim(o1, "["), "]");
129         if (isStrUnsignedInt(opt1)) {
130                 // The command has arguments
131                 nargs = convert<unsigned int>(opt1);
132                 if (nargs > 0 && o2) {
133                         // The first argument is optional
134                         arguments.push_back(optional);
135                         --nargs;
136                 }
137         }
138         for (unsigned int i = 0; i < nargs; ++i)
139                 arguments.push_back(required);
140 }
141
142
143 void add_known_command(string const & command, string const & o1,
144                        bool o2, docstring const & definition)
145 {
146         vector<ArgumentType> arguments;
147         convertArgs(o1, o2, arguments);
148         known_commands[command] = arguments;
149         if (!definition.empty())
150                 possible_textclass_commands[command] =
151                         FullCommand(arguments, definition);
152 }
153
154
155 void add_known_environment(string const & environment, string const & o1,
156                            bool o2, docstring const & beg, docstring const &end)
157 {
158         vector<ArgumentType> arguments;
159         convertArgs(o1, o2, arguments);
160         known_environments[environment] = arguments;
161         if (!beg.empty() || ! end.empty())
162                 possible_textclass_environments[environment] =
163                         FullEnvironment(arguments, beg, end);
164 }
165
166
167 void add_known_theorem(string const & theorem, string const & o1,
168                        bool o2, docstring const & definition)
169 {
170         vector<ArgumentType> arguments;
171         convertArgs(o1, o2, arguments);
172         if (!definition.empty())
173                 possible_textclass_theorems[theorem] =
174                         FullCommand(arguments, definition);
175 }
176
177
178 Layout const * findLayoutWithoutModule(TextClass const & tc,
179                                        string const & name, bool command,
180                                        string const & latexparam)
181 {
182         for (auto const & lay : tc) {
183                 if (lay.latexname() == name &&
184                     (latexparam.empty() ||
185                      (!lay.latexparam().empty() && suffixIs(latexparam, lay.latexparam()))) &&
186                     ((command && lay.isCommand()) || (!command && lay.isEnvironment())))
187                         return &lay;
188         }
189         return 0;
190 }
191
192
193 InsetLayout const * findInsetLayoutWithoutModule(TextClass const & tc,
194                                                  string const & name, bool command,
195                                                  string const & latexparam)
196 {
197         for (auto const & ilay : tc.insetLayouts()) {
198                 if (ilay.second.latexname() == name &&
199                     (latexparam.empty() ||
200                      (!ilay.second.latexparam().empty() && suffixIs(latexparam, ilay.second.latexparam()))) &&
201                     ((command && ilay.second.latextype() == InsetLaTeXType::COMMAND) ||
202                      (!command && ilay.second.latextype() == InsetLaTeXType::ENVIRONMENT)))
203                         return &(ilay.second);
204         }
205         return 0;
206 }
207
208
209 namespace {
210
211 typedef map<string, DocumentClassPtr> ModuleMap;
212 ModuleMap modules;
213
214
215 bool addModule(string const & module, LayoutFile const & baseClass, LayoutModuleList & m, vector<string> & visited)
216 {
217         // avoid endless loop for circular dependency
218         vector<string>::const_iterator const vb = visited.begin();
219         vector<string>::const_iterator const ve = visited.end();
220         if (find(vb, ve, module) != ve) {
221                 warning_message("Circular dependency detected for module " + module);
222                 return false;
223         }
224         LyXModule const * const lm = theModuleList[module];
225         if (!lm) {
226                 warning_message("Could not find module " + module + " in module list.");
227                 return false;
228         }
229         bool foundone = false;
230         LayoutModuleList::const_iterator const exclmodstart = baseClass.excludedModules().begin();
231         LayoutModuleList::const_iterator const exclmodend = baseClass.excludedModules().end();
232         LayoutModuleList::const_iterator const provmodstart = baseClass.providedModules().begin();
233         LayoutModuleList::const_iterator const provmodend = baseClass.providedModules().end();
234         vector<string> const reqs = lm->getRequiredModules();
235         if (reqs.empty())
236                 foundone = true;
237         else {
238                 LayoutModuleList::const_iterator mit = m.begin();
239                 LayoutModuleList::const_iterator men = m.end();
240                 vector<string>::const_iterator rit = reqs.begin();
241                 vector<string>::const_iterator ren = reqs.end();
242                 for (; rit != ren; ++rit) {
243                         if (find(mit, men, *rit) != men) {
244                                 foundone = true;
245                                 break;
246                         }
247                         if (find(provmodstart, provmodend, *rit) != provmodend) {
248                                 foundone = true;
249                                 break;
250                         }
251                 }
252                 if (!foundone) {
253                         visited.push_back(module);
254                         for (rit = reqs.begin(); rit != ren; ++rit) {
255                                 if (find(exclmodstart, exclmodend, *rit) == exclmodend) {
256                                         if (addModule(*rit, baseClass, m, visited)) {
257                                                 foundone = true;
258                                                 break;
259                                         }
260                                 }
261                         }
262                         visited.pop_back();
263                 }
264         }
265         if (!foundone) {
266                 warning_message("Could not add required modules for " + module + ".");
267                 return false;
268         }
269         if (!m.moduleCanBeAdded(module, &baseClass))
270                 return false;
271         m.push_back(module);
272         return true;
273 }
274
275
276 void initModules()
277 {
278         // Create list of dummy document classes if not already done.
279         // This is needed since a module cannot be read on its own, only as
280         // part of a document class.
281         LayoutFile const & baseClass = LayoutFileList::get()[textclass.name()];
282         static bool init = true;
283         if (init) {
284                 baseClass.load();
285                 LyXModuleList::const_iterator const end = theModuleList.end();
286                 LyXModuleList::const_iterator it = theModuleList.begin();
287                 for (; it != end; ++it) {
288                         string const module = it->getID();
289                         LayoutModuleList m;
290                         vector<string> v;
291                         if (!addModule(module, baseClass, m, v))
292                                 continue;
293                         modules[module] = getDocumentClass(baseClass, m);
294                 }
295                 init = false;
296         }
297 }
298
299
300 bool addModule(string const & module)
301 {
302         initModules();
303         LayoutFile const & baseClass = LayoutFileList::get()[textclass.name()];
304         if (!used_modules.moduleCanBeAdded(module, &baseClass))
305                 return false;
306         FileName layout_file = libFileSearch("layouts", module, "module");
307         if (textclass.read(layout_file, TextClass::MODULE)) {
308                 used_modules.push_back(module);
309                 // speed up further searches:
310                 // the module does not need to be checked anymore.
311                 ModuleMap::iterator const it = modules.find(module);
312                 if (it != modules.end())
313                         modules.erase(it);
314                 return true;
315         }
316         return false;
317 }
318
319 } // namespace
320
321
322 bool checkModule(string const & name, bool command)
323 {
324         // Cache to avoid slowdown by repated searches
325         static set<string> failed[2];
326
327         // Record whether the command was actually defined in the LyX preamble
328         bool theorem = false;
329         bool preamble_def = true;
330         if (command) {
331                 if (possible_textclass_commands.find('\\' + name) == possible_textclass_commands.end())
332                         preamble_def = false;
333         } else {
334                 if (possible_textclass_environments.find(name) == possible_textclass_environments.end()) {
335                         if (possible_textclass_theorems.find(name) != possible_textclass_theorems.end())
336                                 theorem = true;
337                         else
338                                 preamble_def = false;
339                 }
340         }
341         if (failed[command].find(name) != failed[command].end())
342                 return false;
343
344         initModules();
345         LayoutFile const & baseClass = LayoutFileList::get()[textclass.name()];
346
347         // Try to find a module that defines the command.
348         // For commands with preamble definitions we prefer modules where the definition
349         // can be found in the preamble of the style that corresponds to the command. 
350         // For others we check whether the command or module requires a package that is loaded
351         // in the tex file and use a style with the respective command.
352         // This is a heuristic and different from the way how we parse the builtin
353         // commands of the text class (in that case we only compare the name), 
354         // but it is needed since it is not unlikely that two different modules define a
355         // command with the same name.
356         string found_module;
357         vector<string> potential_modules;
358         ModuleMap::iterator const end = modules.end();
359         for (ModuleMap::iterator it = modules.begin(); it != end; ++it) {
360                 string const module = it->first;
361                 if (used_modules.moduleConflicts(module, &baseClass))
362                         continue;
363                 if (findLayoutWithoutModule(textclass, name, command))
364                         continue;
365                 if (findInsetLayoutWithoutModule(textclass, name, command))
366                         continue;
367                 DocumentClassConstPtr c = it->second;
368                 Layout const * layout = findLayoutWithoutModule(*c, name, command);
369                 InsetLayout const * insetlayout = layout ? nullptr :
370                         findInsetLayoutWithoutModule(*c, name, command);
371                 docstring dpre;
372                 std::set<std::string> cmd_reqs;
373                 bool found_style = false;
374                 if (layout) {
375                         found_style = true;
376                         dpre = layout->preamble();
377                         std::set<std::string> const & lreqs = layout->required();
378                         if (!lreqs.empty())
379                                 cmd_reqs.insert(lreqs.begin(), lreqs.end());
380                 } else if (insetlayout) {
381                         found_style = true;
382                         dpre = insetlayout->preamble();
383                         std::set<std::string> lreqs = insetlayout->required();
384                         if (!lreqs.empty())
385                                 cmd_reqs.insert(lreqs.begin(), lreqs.end());
386                 }
387                 if (dpre.empty() && preamble_def)
388                         continue;
389                 bool const package_cmd = dpre.empty();
390                 bool match_req = false;
391                 if (package_cmd) {
392                         std::set<std::string> mreqs = it->second->required();
393                         if (!mreqs.empty())
394                                 cmd_reqs.insert(mreqs.begin(), mreqs.end());
395                         for (auto const & pack : cmd_reqs) {
396                                 // If a requirement of the module matches a used package
397                                 // we load the module except if we have an auto-loaded package
398                                 // which is only required generally by the module, and the module
399                                 // does not provide the [inset]layout we are looking for.
400                                 // This heuristics should
401                                 // * load modules if the provide a style we don't have in the class
402                                 // * load modules that provide a package support generally (such as fixltx2e)
403                                 // * not unnecessarily load modules just because they require a package which we
404                                 //   load anyway.
405                                 if (preamble.isPackageUsed(pack)
406                                     && (found_style || !preamble.isPackageAutoLoaded(pack))) {
407                                     if (found_style)
408                                             match_req = true;
409                                     else                
410                                             potential_modules.push_back(module);
411                                     break;
412                                 }
413                         }
414                 }
415                 bool add = match_req;
416                 if (preamble_def) {
417                         if (command) {
418                                 FullCommand const & cmd =
419                                         possible_textclass_commands['\\' + name];
420                                 if (dpre.find(cmd.def) != docstring::npos)
421                                         add = true;
422                         } else if (theorem) {
423                                 FullCommand const & thm =
424                                         possible_textclass_theorems[name];
425                                 if (dpre.find(thm.def) != docstring::npos)
426                                         add = true;
427                         } else {
428                                 FullEnvironment const & env =
429                                         possible_textclass_environments[name];
430                                 if (dpre.find(env.beg) != docstring::npos &&
431                                     dpre.find(env.end) != docstring::npos)
432                                         add = true;
433                         }
434                 }
435                 if (add) {
436                         found_module = module;
437                         break;
438                 }
439         }
440         if (found_module.empty()) {
441                 // take one of the second row
442                 if (!potential_modules.empty())
443                         found_module = potential_modules.front();  
444         }
445                 
446         if (!found_module.empty()) {
447                 vector<string> v;
448                 LayoutModuleList mods;
449                 // addModule is necessary in order to catch required modules
450                 // as well (see #11156)
451                 if (!addModule(found_module, baseClass, mods, v))
452                         return false;
453                 for (auto const & mod : mods) {
454                         if (!used_modules.moduleCanBeAdded(mod, &baseClass))
455                                 return false;
456                         FileName layout_file = libFileSearch("layouts", mod, "module");
457                         if (textclass.read(layout_file, TextClass::MODULE)) {
458                                 used_modules.push_back(mod);
459                                 // speed up further searches:
460                                 // the module does not need to be checked anymore.
461                                 ModuleMap::iterator const it = modules.find(mod);
462                                 if (it != modules.end())
463                                         modules.erase(it);
464                                 return true;
465                         }
466                 }
467         }
468         failed[command].insert(name);
469         return false;
470 }
471
472
473 bool isProvided(string const & name)
474 {
475         // This works only for features that are named like the LaTeX packages
476         return textclass.provides(name) || preamble.isPackageUsed(name);
477 }
478
479
480 bool noweb_mode = false;
481 bool pdflatex = false;
482 bool xetex = false;
483 bool is_nonCJKJapanese = false;
484 bool roundtrip = false;
485
486
487 namespace {
488
489
490 /*!
491  * Read one command definition from the syntax file
492  */
493 void read_command(Parser & p, string command, CommandMap & commands)
494 {
495         if (p.next_token().asInput() == "*") {
496                 p.get_token();
497                 command += '*';
498         }
499         vector<ArgumentType> arguments;
500         while (p.next_token().cat() == catBegin ||
501                p.next_token().asInput() == "[") {
502                 if (p.next_token().cat() == catBegin) {
503                         string const arg = p.getArg('{', '}');
504                         if (arg == "translate")
505                                 arguments.push_back(required);
506                         else if (arg == "group")
507                                 arguments.push_back(req_group);
508                         else if (arg == "item")
509                                 arguments.push_back(item);
510                         else if (arg == "displaymath")
511                                 arguments.push_back(displaymath);
512                         else
513                                 arguments.push_back(verbatim);
514                 } else {
515                         string const arg = p.getArg('[', ']');
516                         if (arg == "group")
517                                 arguments.push_back(opt_group);
518                         else
519                                 arguments.push_back(optional);
520                 }
521         }
522         commands[command] = arguments;
523 }
524
525
526 /*!
527  * Read a class of environments from the syntax file
528  */
529 void read_environment(Parser & p, string const & begin,
530                       CommandMap & environments)
531 {
532         string environment;
533         while (p.good()) {
534                 Token const & t = p.get_token();
535                 if (t.cat() == catLetter)
536                         environment += t.asInput();
537                 else if (!environment.empty()) {
538                         p.putback();
539                         read_command(p, environment, environments);
540                         environment.erase();
541                 }
542                 if (t.cat() == catEscape && t.asInput() == "\\end") {
543                         string const end = p.getArg('{', '}');
544                         if (end == begin)
545                                 return;
546                 }
547         }
548 }
549
550
551 /*!
552  * Read a list of TeX commands from a reLyX compatible syntax file.
553  * Since this list is used after all commands that have a LyX counterpart
554  * are handled, it does not matter that the "syntax.default" file
555  * has almost all of them listed. For the same reason the reLyX-specific
556  * reLyXre environment is ignored.
557  */
558 bool read_syntaxfile(FileName const & file_name)
559 {
560         ifdocstream is(file_name.toFilesystemEncoding().c_str());
561         if (!is.good()) {
562                 error_message("Could not open syntax file \""
563                               + file_name.absFileName() + "\" for reading.");
564                 return false;
565         }
566         // We can use our TeX parser, since the syntax of the layout file is
567         // modeled after TeX.
568         // Unknown tokens are just silently ignored, this helps us to skip some
569         // reLyX specific things.
570         Parser p(is, string());
571         while (p.good()) {
572                 Token const & t = p.get_token();
573                 if (t.cat() == catEscape) {
574                         string const command = t.asInput();
575                         if (command == "\\begin") {
576                                 string const name = p.getArg('{', '}');
577                                 if (name == "environments" || name == "reLyXre")
578                                         // We understand "reLyXre", but it is
579                                         // not as powerful as "environments".
580                                         read_environment(p, name,
581                                                 known_environments);
582                                 else if (name == "mathenvironments")
583                                         read_environment(p, name,
584                                                 known_math_environments);
585                         } else {
586                                 read_command(p, command, known_commands);
587                         }
588                 }
589         }
590         return true;
591 }
592
593
594 string documentclass;
595 string default_encoding;
596 bool fixed_encoding = false;
597 string syntaxfile;
598 bool copy_files = false;
599 bool overwrite_files = false;
600 bool no_warnings = false;
601 bool skip_children = false;
602 int error_code = 0;
603
604 /// return the number of arguments consumed
605 typedef int (*cmd_helper)(string const &, string const &);
606
607
608 class StopException : public exception
609 {
610         public:
611                 StopException(int status) : status_(status) {}
612                 int status() const { return status_; }
613         private:
614                 int status_;
615 };
616
617
618 /// The main application class
619 class TeX2LyXApp : public ConsoleApplication
620 {
621 public:
622         TeX2LyXApp(int & argc, char * argv[])
623                 : ConsoleApplication("tex2lyx" PROGRAM_SUFFIX, argc, argv),
624                   argc_(argc), argv_(argv)
625         {
626         }
627         void doExec() override
628         {
629                 try {
630                         int const exit_status = run();
631                         exit(exit_status);
632                 }
633                 catch (StopException & e) {
634                         exit(e.status());
635                 }
636         }
637 private:
638         void easyParse();
639         /// Do the real work
640         int run();
641         int & argc_;
642         char ** argv_;
643 };
644
645
646 int parse_help(string const &, string const &)
647 {
648         cout << "Usage: tex2lyx [options] infile.tex [outfile.lyx]\n"
649                 "Options:\n"
650                 "\t-c textclass       Declare the textclass.\n"
651                 "\t-m mod1[,mod2...]  Load the given modules.\n"
652                 "\t-copyfiles         Copy all included files to the directory of outfile.lyx.\n"
653                 "\t-e encoding        Set the default encoding (latex name).\n"
654                 "\t-fixedenc encoding Like -e, but ignore encoding changing commands while parsing.\n"
655                 "\t-f                 Force overwrite of .lyx files.\n"
656                 "\t-help              Print this message and quit.\n"
657                 "\t-n                 Translate literate programming (noweb, sweave,... ) file.\n"
658                 "\t-q                 Omit warnings.\n"
659                 "\t-roundtrip         Re-export created .lyx file infile.lyx.lyx to infile.lyx.tex.\n"
660                 "\t-skipchildren      Do not translate included child documents.\n"
661                 "\t-s syntaxfile      Read additional syntax file.\n"
662                 "\t-sysdir SYSDIR     Set system directory to SYSDIR.\n"
663                 "\t                   Default: " << package().system_support() << "\n"
664                 "\t-userdir USERDIR   Set user directory to USERDIR.\n"
665                 "\t                   Default: " << package().user_support() << "\n"
666                 "\t-version           Summarize version and build info.\n"
667                 "Paths:\n"
668                 "\tThe program searches for the files \"encodings\", \"lyxmodules.lst\",\n"
669                 "\t\"textclass.lst\", \"syntax.default\", and \"unicodesymbols\", first in\n"
670                 "\t\"USERDIR\", then in \"SYSDIR\". The subdirectories \"USERDIR/layouts\"\n"
671                 "\tand \"SYSDIR/layouts\" are searched for layout and module files.\n"
672                 "Check the tex2lyx man page for more details."
673              << endl;
674         throw StopException(error_code);
675 }
676
677
678 int parse_version(string const &, string const &)
679 {
680         cout << "tex2lyx " << lyx_version
681              << " (" << lyx_release_date << ")" << endl;
682
683         cout << lyx_version_info << endl;
684         throw StopException(error_code);
685 }
686
687
688 void error_with_message(string const & message)
689 {
690         error_message(message);
691         error_code = EXIT_FAILURE;
692         parse_help(string(), string());
693 }
694
695
696 int parse_class(string const & arg, string const &)
697 {
698         if (arg.empty())
699                 error_with_message("Missing textclass string after -c switch");
700         documentclass = arg;
701         return 1;
702 }
703
704
705 int parse_module(string const & arg, string const &)
706 {
707         split(arg, preloaded_modules, ',');
708         return 1;
709 }
710
711
712 int parse_encoding(string const & arg, string const &)
713 {
714         if (arg.empty())
715                 error_with_message("Missing encoding string after -e switch");
716         default_encoding = arg;
717         return 1;
718 }
719
720
721 int parse_fixed_encoding(string const & arg, string const &)
722 {
723         if (arg.empty())
724                 error_with_message("Missing encoding string after -fixedenc switch");
725         default_encoding = arg;
726         fixed_encoding = true;
727         return 1;
728 }
729
730
731 int parse_syntaxfile(string const & arg, string const &)
732 {
733         if (arg.empty())
734                 error_with_message("Missing syntaxfile string after -s switch");
735         syntaxfile = internal_path(arg);
736         return 1;
737 }
738
739
740 // Filled with the command line arguments "foo" of "-sysdir foo" or
741 // "-userdir foo".
742 string cl_system_support;
743 string cl_user_support;
744
745
746 int parse_sysdir(string const & arg, string const &)
747 {
748         if (arg.empty())
749                 error_with_message("Missing directory for -sysdir switch");
750         cl_system_support = internal_path(arg);
751         return 1;
752 }
753
754
755 int parse_userdir(string const & arg, string const &)
756 {
757         if (arg.empty())
758                 error_with_message("Missing directory for -userdir switch");
759         cl_user_support = internal_path(arg);
760         return 1;
761 }
762
763
764 int parse_force(string const &, string const &)
765 {
766         overwrite_files = true;
767         return 0;
768 }
769
770
771 int parse_quite(string const &, string const &)
772 {
773         no_warnings = true;
774         return 0;
775 }
776
777
778 int parse_noweb(string const &, string const &)
779 {
780         noweb_mode = true;
781         return 0;
782 }
783
784
785 int parse_skipchildren(string const &, string const &)
786 {
787         skip_children = true;
788         return 0;
789 }
790
791
792 int parse_roundtrip(string const &, string const &)
793 {
794         roundtrip = true;
795         return 0;
796 }
797
798
799 int parse_copyfiles(string const &, string const &)
800 {
801         copy_files = true;
802         return 0;
803 }
804
805
806 void TeX2LyXApp::easyParse()
807 {
808         map<string, cmd_helper> cmdmap;
809
810         cmdmap["-h"] = parse_help;
811         cmdmap["-help"] = parse_help;
812         cmdmap["--help"] = parse_help;
813         cmdmap["-v"] = parse_version;
814         cmdmap["-version"] = parse_version;
815         cmdmap["--version"] = parse_version;
816         cmdmap["-c"] = parse_class;
817         cmdmap["-m"] = parse_module;
818         cmdmap["-e"] = parse_encoding;
819         cmdmap["-fixedenc"] = parse_fixed_encoding;
820         cmdmap["-f"] = parse_force;
821         cmdmap["-s"] = parse_syntaxfile;
822         cmdmap["-n"] = parse_noweb;
823         cmdmap["-skipchildren"] = parse_skipchildren;
824         cmdmap["-sysdir"] = parse_sysdir;
825         cmdmap["-userdir"] = parse_userdir;
826         cmdmap["-q"] = parse_quite;
827         cmdmap["-roundtrip"] = parse_roundtrip;
828         cmdmap["-copyfiles"] = parse_copyfiles;
829
830         for (int i = 1; i < argc_; ++i) {
831                 map<string, cmd_helper>::const_iterator it
832                         = cmdmap.find(argv_[i]);
833
834                 // don't complain if not found - may be parsed later
835                 if (it == cmdmap.end()) {
836                         if (argv_[i][0] == '-')
837                                 error_with_message(string("Unknown option `") + argv_[i] + "'.");
838                         else
839                                 continue;
840                 }
841
842                 string arg = (i + 1 < argc_) ? os::utf8_argv(i + 1) : string();
843                 string arg2 = (i + 2 < argc_) ? os::utf8_argv(i + 2) : string();
844
845                 int const remove = 1 + it->second(arg, arg2);
846
847                 // Now, remove used arguments by shifting
848                 // the following ones remove places down.
849                 os::remove_internal_args(i, remove);
850                 argc_ -= remove;
851                 for (int j = i; j < argc_; ++j)
852                         argv_[j] = argv_[j + remove];
853                 --i;
854         }
855 }
856
857
858 // path of the first parsed file
859 string masterFilePathLyX;
860 string masterFilePathTeX;
861 // path of the currently parsed file
862 string parentFilePathTeX;
863
864 } // anonymous namespace
865
866
867 string getMasterFilePath(bool input)
868 {
869         return input ? masterFilePathTeX : masterFilePathLyX;
870 }
871
872 string getParentFilePath(bool input)
873 {
874         if (input)
875                 return parentFilePathTeX;
876         string const rel = to_utf8(makeRelPath(from_utf8(masterFilePathTeX),
877                                                from_utf8(parentFilePathTeX)));
878         if (rel.substr(0, 3) == "../") {
879                 // The parent is not below the master - keep the path
880                 return parentFilePathTeX;
881         }
882         return makeAbsPath(rel, masterFilePathLyX).absFileName();
883 }
884
885
886 bool copyFiles()
887 {
888         return copy_files;
889 }
890
891
892 bool overwriteFiles()
893 {
894         return overwrite_files;
895 }
896
897
898 bool skipChildren()
899 {
900         return skip_children;
901 }
902
903
904 bool roundtripMode()
905 {
906         return roundtrip;
907 }
908
909
910 namespace {
911
912 /*!
913  *  Reads tex input from \a is and writes lyx output to \a os.
914  *  Uses some common settings for the preamble, so this should only
915  *  be used more than once for included documents.
916  *  Caution: Overwrites the existing preamble settings if the new document
917  *  contains a preamble.
918  *  You must ensure that \p parentFilePathTeX is properly set before calling
919  *  this function!
920  */
921 bool tex2lyx(idocstream & is, ostream & os, string const & encoding,
922              string const & outfiledir)
923 {
924         Parser p(is, fixed_encoding ? default_encoding : string());
925         p.setEncoding(encoding);
926         //p.dump();
927
928         preamble.parse(p, documentclass, textclass);
929         list<string> removed_modules;
930         LayoutFile const & baseClass = LayoutFileList::get()[textclass.name()];
931         if (!used_modules.adaptToBaseClass(&baseClass, removed_modules)) {
932                 error_message("Could not load default modules for text class.");
933                 return false;
934         }
935
936         // Load preloaded modules.
937         // This needs to be done after the preamble is parsed, since the text
938         // class may not be known before. It needs to be done before parsing
939         // body, since otherwise the commands/environments provided by the
940         // modules would be parsed as ERT.
941         // Empty module names are silently skipped.
942         for (auto const & module : preloaded_modules) {
943                 if (!module.empty() && !addModule(module)) {
944                         error_message("Error: Could not load module \""
945                                       + module + "\".");
946                         return false;
947                 }
948         }
949         // Ensure that the modules are not loaded again for included files
950         preloaded_modules.clear();
951
952         active_environments.push_back("document");
953         Context context(true, textclass);
954         stringstream ss;
955         // store the document language in the context to be able to handle the
956         // commands like \foreignlanguage and \textenglish etc.
957         context.font.language = preamble.defaultLanguage();
958         // parse the main text
959         parse_text(p, ss, FLAG_END, true, context);
960         // check if we need a commented bibtex inset (biblatex)
961         check_comment_bib(ss, context);
962         if (Context::empty)
963                 // Empty document body. LyX needs at least one paragraph.
964                 context.check_layout(ss);
965         context.check_end_layout(ss);
966         ss << "\n\\end_body\n\\end_document\n";
967         active_environments.pop_back();
968
969         // We know the used modules only after parsing the full text
970         if (!used_modules.empty()) {
971                 LayoutModuleList::const_iterator const end = used_modules.end();
972                 LayoutModuleList::const_iterator it = used_modules.begin();
973                 for (; it != end; ++it)
974                         preamble.addModule(*it);
975         }
976         if (!preamble.writeLyXHeader(os, !active_environments.empty(), outfiledir)) {
977                 error_message( "Could not write LyX file header.");
978                 return false;
979         }
980
981         ss.seekg(0);
982         os << ss.str();
983 #ifdef TEST_PARSER
984         p.reset();
985         ofdocstream parsertest("parsertest.tex");
986         while (p.good())
987                 parsertest << p.get_token().asInput();
988         // <origfile> and parsertest.tex should now have identical content
989 #endif
990         return true;
991 }
992
993
994 /// convert TeX from \p infilename to LyX and write it to \p os
995 bool tex2lyx(FileName const & infilename, ostream & os, string encoding,
996              string const & outfiledir)
997 {
998         // Set a sensible default encoding.
999         // This is used until an encoding command is found.
1000         // For child documents use the encoding of the master, else try to
1001         // detect it from the preamble, since setting an encoding of an open
1002         // fstream does currently not work on OS X.
1003         // Always start with ISO-8859-1, (formerly known by its latex name
1004         // latin1), since ISO-8859-1 does not cause an iconv error if the
1005         // actual encoding is different (bug 7509).
1006         if (encoding.empty()) {
1007                 Encoding const * enc = 0;
1008                 if (preamble.inputencoding() == "auto-legacy") {
1009                         ifdocstream is(setEncoding("ISO-8859-1"));
1010                         // forbid buffering on this stream
1011                         is.rdbuf()->pubsetbuf(0, 0);
1012                         is.open(infilename.toFilesystemEncoding().c_str());
1013                         if (is.good()) {
1014                                 Parser ep(is, string());
1015                                 ep.setEncoding("ISO-8859-1");
1016                                 Preamble encodingpreamble;
1017                                 string const e = encodingpreamble
1018                                         .parseEncoding(ep, documentclass);
1019                                 if (!e.empty())
1020                                         enc = encodings.fromLyXName(e, true);
1021                         }
1022                 } else
1023                         enc = encodings.fromLyXName(
1024                                         preamble.inputencoding(), true);
1025                 if (enc)
1026                         encoding = enc->iconvName();
1027                 else
1028                         encoding = "ISO-8859-1";
1029                 // store
1030                 preamble.docencoding = encoding;
1031         }
1032
1033         ifdocstream is(setEncoding(encoding));
1034         // forbid buffering on this stream
1035         is.rdbuf()->pubsetbuf(0, 0);
1036         is.open(infilename.toFilesystemEncoding().c_str());
1037         if (!is.good()) {
1038                 error_message("Could not open input file \""
1039                               + infilename.absFileName() + "\" for reading.");
1040                 return false;
1041         }
1042         string const oldParentFilePath = parentFilePathTeX;
1043         parentFilePathTeX = onlyPath(infilename.absFileName());
1044         bool retval = tex2lyx(is, os, encoding, outfiledir);
1045         parentFilePathTeX = oldParentFilePath;
1046         return retval;
1047 }
1048
1049 } // anonymous namespace
1050
1051
1052 bool tex2lyx(string const & infilename, FileName const & outfilename,
1053              string const & encoding)
1054 {
1055         FileName ifname = FileName(infilename);
1056         // Like TeX, we consider files without extensions as *.tex files
1057         // and append the extension if the file without ext does not exist
1058         // (#12340)
1059         if (!ifname.exists() && ifname.extension().empty()) {
1060                 ifname.changeExtension("tex");
1061                 if (!ifname.exists()) {
1062                         error_message("Could not open input file \""
1063                                       + infilename + "\" for reading.");
1064                         return false;
1065                 }
1066         }
1067         if (outfilename.isReadableFile()) {
1068                 if (overwrite_files) {
1069                         warning_message("Overwriting existing file "
1070                                         + outfilename.absFileName());
1071                 } else {
1072                         error_message("Not overwriting existing file "
1073                                       + outfilename.absFileName());
1074                         return false;
1075                 }
1076         } else {
1077                 warning_message("Creating file " + outfilename.absFileName());
1078         }
1079         ofstream os(outfilename.toFilesystemEncoding().c_str());
1080         if (!os.good()) {
1081                 error_message("Could not open output file \""
1082                               + outfilename.absFileName() + "\" for writing.");
1083                 return false;
1084         }
1085
1086         debug_message("Input file: " + ifname.absFileName());
1087         debug_message("Output file: " + outfilename.absFileName());
1088
1089         return tex2lyx(ifname, os, encoding,
1090                        outfilename.onlyPath().absFileName() + '/');
1091 }
1092
1093
1094 bool tex2tex(string const & infilename, FileName const & outfilename,
1095              string const & encoding)
1096 {
1097         if (!tex2lyx(infilename, outfilename, encoding))
1098                 return false;
1099         string command = quoteName(package().lyx_binary().toFilesystemEncoding());
1100         if (overwrite_files)
1101                 command += " -f main";
1102         else
1103                 command += " -f none";
1104         if (pdflatex)
1105                 command += " -e pdflatex ";
1106         else if (xetex)
1107                 command += " -e xetex ";
1108         else
1109                 command += " -e latex ";
1110         command += quoteName(outfilename.toFilesystemEncoding());
1111         Systemcall one;
1112         if (one.startscript(Systemcall::Wait, command) == 0)
1113                 return true;
1114         error_message("Running '" + command + "' failed.");
1115         return false;
1116 }
1117
1118
1119 void warning_message(string const & message)
1120 {
1121         if (!no_warnings)
1122                 cerr << "tex2lyx warning: " << message << endl;
1123 }
1124
1125
1126 void error_message(string const & message)
1127 {
1128         cerr << "tex2lyx error: " << message << endl;
1129 }
1130
1131 #ifdef FILEDEBUG
1132 void debug_message(string const & message)
1133 {
1134         cerr << "tex2lyx debug info: " << message << endl;
1135 }
1136 #else
1137 void debug_message(string const &){}
1138 #endif
1139
1140
1141 namespace {
1142
1143 int TeX2LyXApp::run()
1144 {
1145         // qt changes this, and our numeric conversions require the C locale
1146         setlocale(LC_NUMERIC, "C");
1147
1148         try {
1149                 init_package(internal_path(os::utf8_argv(0)), string(), string());
1150         } catch (ExceptionMessage const & message) {
1151                 error_message(to_utf8(message.title_) + ":\n"
1152                               + to_utf8(message.details_));
1153                 if (message.type_ == ErrorException)
1154                         return EXIT_FAILURE;
1155         }
1156
1157         easyParse();
1158
1159         if (argc_ <= 1)
1160                 error_message("Not enough arguments.");
1161
1162         try {
1163                 init_package(internal_path(os::utf8_argv(0)),
1164                              cl_system_support, cl_user_support);
1165         } catch (ExceptionMessage const & message) {
1166                 error_message(to_utf8(message.title_) + ":\n"
1167                               + to_utf8(message.details_));
1168                 if (message.type_ == ErrorException)
1169                         return EXIT_FAILURE;
1170         }
1171
1172         // Check that user LyX directory is ok.
1173         FileName const sup = package().user_support();
1174         if (sup.exists() && sup.isDirectory()) {
1175                 string const lock_file = package().getConfigureLockName();
1176                 int fd = fileLock(lock_file.c_str());
1177                 if (configFileNeedsUpdate("lyxrc.defaults") ||
1178                     configFileNeedsUpdate("lyxmodules.lst") ||
1179                     configFileNeedsUpdate("textclass.lst") ||
1180                     configFileNeedsUpdate("packages.lst") ||
1181                     configFileNeedsUpdate("lyxciteengines.lst") ||
1182                     configFileNeedsUpdate("xtemplates.lst"))
1183                         package().reconfigureUserLyXDir("");
1184                 fileUnlock(fd, lock_file.c_str());
1185         } else
1186                 error_message("User directory does not exist.");
1187
1188         // Now every known option is parsed. Look for input and output
1189         // file name (the latter is optional).
1190         string infilename = internal_path(os::utf8_argv(1));
1191         infilename = makeAbsPath(infilename).absFileName();
1192
1193         string outfilename;
1194         if (argc_ > 2) {
1195                 outfilename = internal_path(os::utf8_argv(2));
1196                 if (outfilename != "-")
1197                         outfilename = makeAbsPath(outfilename).absFileName();
1198                 if (roundtrip) {
1199                         if (outfilename == "-") {
1200                                 error_message("Writing to standard output is "
1201                                               "not supported in roundtrip mode.");
1202                                 return EXIT_FAILURE;
1203                         }
1204                         string texfilename = changeExtension(outfilename, ".tex");
1205                         if (equivalent(FileName(infilename), FileName(texfilename))) {
1206                                 error_message("The input file `" + infilename
1207                                               + "´ would be overwritten by the TeX file exported from `"
1208                                               + outfilename + "´ in roundtrip mode.");
1209                                 return EXIT_FAILURE;
1210                         }
1211                 }
1212         } else if (roundtrip) {
1213                 // avoid overwriting the input file
1214                 outfilename = changeExtension(infilename, ".lyx.lyx");
1215         } else
1216                 outfilename = changeExtension(infilename, ".lyx");
1217
1218         // Read the syntax tables
1219         FileName const system_syntaxfile = libFileSearch("", "syntax.default");
1220         if (system_syntaxfile.empty()) {
1221                 error_message("Could not find syntax file \"syntax.default\".");
1222                 return EXIT_FAILURE;
1223         }
1224         if (!read_syntaxfile(system_syntaxfile))
1225                 return 2;
1226         if (!syntaxfile.empty())
1227                 if (!read_syntaxfile(makeAbsPath(syntaxfile)))
1228                         return 2;
1229
1230         // Read the encodings table.
1231         FileName const symbols_path = libFileSearch(string(), "unicodesymbols");
1232         if (symbols_path.empty()) {
1233                 error_message("Could not find file \"unicodesymbols\".");
1234                 return EXIT_FAILURE;
1235         }
1236         FileName const enc_path = libFileSearch(string(), "encodings");
1237         if (enc_path.empty()) {
1238                 error_message("Could not find file \"encodings\".");
1239                 return EXIT_FAILURE;
1240         }
1241         encodings.read(enc_path, symbols_path);
1242         if (!default_encoding.empty()) {
1243                 Encoding const * const enc = encodings.fromLaTeXName(
1244                         default_encoding, Encoding::any, true);
1245                 if (!enc)
1246                         error_message("Unknown LaTeX encoding `" + default_encoding + "'");
1247                 default_encoding = enc->iconvName();
1248                 if (fixed_encoding)
1249                         preamble.setInputencoding(enc->name());
1250         }
1251
1252         // Load the layouts
1253         LayoutFileList::get().read();
1254         //...and the modules
1255         theModuleList.read();
1256
1257         // The real work now.
1258         masterFilePathTeX = onlyPath(infilename);
1259         parentFilePathTeX = masterFilePathTeX;
1260         if (outfilename == "-") {
1261                 // assume same directory as input file
1262                 masterFilePathLyX = masterFilePathTeX;
1263                 if (tex2lyx(FileName(infilename), cout, default_encoding, masterFilePathLyX))
1264                         return EXIT_SUCCESS;
1265         } else {
1266                 masterFilePathLyX = onlyPath(outfilename);
1267                 if (copy_files) {
1268                         FileName const path(masterFilePathLyX);
1269                         if (!path.isDirectory()) {
1270                                 if (!path.createPath()) {
1271                                         error_message("Could not create directory for file `"
1272                                                       + masterFilePathLyX + "´.");
1273                                         return EXIT_FAILURE;
1274                                 }
1275                         }
1276                 }
1277                 if (roundtrip) {
1278                         if (tex2tex(infilename, FileName(outfilename), default_encoding))
1279                                 return EXIT_SUCCESS;
1280                 } else {
1281                         if (lyx::tex2lyx(infilename, FileName(outfilename), default_encoding))
1282                                 return EXIT_SUCCESS;
1283                 }
1284         }
1285         return EXIT_FAILURE;
1286 }
1287
1288 } // anonymous namespace
1289 } // namespace lyx
1290
1291
1292 int main(int argc, char * argv[])
1293 {
1294         //setlocale(LC_CTYPE, "");
1295
1296         lyx::lyxerr.setStream(cerr);
1297
1298         os::init(argc, &argv);
1299
1300         lyx::TeX2LyXApp app(argc, argv);
1301         return app.exec();
1302 }
1303
1304 // }])