]> git.lyx.org Git - lyx.git/blobdiff - src/tex2lyx/tex2lyx.C
Fix bug 2667
[lyx.git] / src / tex2lyx / tex2lyx.C
index 2ba23661a9b79062d59e16474b811f5d1f55f09f..09dcf7cbbcf35c7678056ef9b73df038005e3d31 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <boost/function.hpp>
 #include <boost/filesystem/operations.hpp>
+#include <boost/filesystem/path.hpp>
 
 #include <cctype>
 #include <fstream>
@@ -51,10 +52,13 @@ using std::string;
 using std::vector;
 using std::map;
 
+using lyx::support::changeExtension;
 using lyx::support::isStrUnsignedInt;
 using lyx::support::ltrim;
+using lyx::support::makeAbsPath;
+using lyx::support::onlyPath;
 using lyx::support::rtrim;
-using lyx::support::IsFileReadable;
+using lyx::support::isFileReadable;
 
 namespace fs = boost::filesystem;
 
@@ -124,11 +128,13 @@ string active_environment()
 }
 
 
-map<string, vector<ArgumentType> > known_commands;
+CommandMap known_commands;
+CommandMap known_environments;
+CommandMap known_math_environments;
 
 
 void add_known_command(string const & command, string const & o1,
-                       bool o2)
+                      bool o2)
 {
        // We have to handle the following cases:
        // definition                      o1    o2    invocation result
@@ -158,21 +164,72 @@ void add_known_command(string const & command, string const & o1,
 namespace {
 
 
+/*!
+ * Read one command definition from the syntax file
+ */
+void read_command(Parser & p, string command, CommandMap & commands) {
+       if (p.next_token().asInput() == "*") {
+               p.get_token();
+               command += '*';
+       }
+       vector<ArgumentType> arguments;
+       while (p.next_token().cat() == catBegin ||
+              p.next_token().asInput() == "[") {
+               if (p.next_token().cat() == catBegin) {
+                       string const arg = p.getArg('{', '}');
+                       if (arg == "translate")
+                               arguments.push_back(required);
+                       else
+                               arguments.push_back(verbatim);
+               } else {
+                       p.getArg('[', ']');
+                       arguments.push_back(optional);
+               }
+       }
+       commands[command] = arguments;
+}
+
+
+/*!
+ * Read a class of environments from the syntax file
+ */
+void read_environment(Parser & p, string const & begin,
+                     CommandMap & environments)
+{
+       string environment;
+       while (p.good()) {
+               Token const & t = p.get_token();
+               if (t.cat() == catLetter)
+                       environment += t.asInput();
+               else if (!environment.empty()) {
+                       p.putback();
+                       read_command(p, environment, environments);
+                       environment.erase();
+               }
+               if (t.cat() == catEscape && t.asInput() == "\\end") {
+                       string const end = p.getArg('{', '}');
+                       if (end == begin)
+                               return;
+               }
+       }
+}
+
+
 /*!
  * Read a list of TeX commands from a reLyX compatible syntax file.
  * Since this list is used after all commands that have a LyX counterpart
- * are handled, it does not matter that the "syntax.default" file from reLyX
+ * are handled, it does not matter that the "syntax.default" file
  * has almost all of them listed. For the same reason the reLyX-specific
  * reLyXre environment is ignored.
  */
 void read_syntaxfile(string const & file_name)
 {
-       if (!IsFileReadable(file_name)) {
+       ifstream is(file_name.c_str());
+       if (!is.good()) {
                cerr << "Could not open syntax file \"" << file_name
                     << "\" for reading." << endl;
                exit(2);
        }
-       ifstream is(file_name.c_str());
        // We can use our TeX parser, since the syntax of the layout file is
        // modeled after TeX.
        // Unknown tokens are just silently ignored, this helps us to skip some
@@ -181,27 +238,20 @@ void read_syntaxfile(string const & file_name)
        while (p.good()) {
                Token const & t = p.get_token();
                if (t.cat() == catEscape) {
-                       string command = t.asInput();
-                       if (p.next_token().asInput() == "*") {
-                               p.get_token();
-                               command += '*';
-                       }
-                       p.skip_spaces();
-                       vector<ArgumentType> arguments;
-                       while (p.next_token().cat() == catBegin ||
-                              p.next_token().asInput() == "[") {
-                               if (p.next_token().cat() == catBegin) {
-                                       string const arg = p.getArg('{', '}');
-                                       if (arg == "translate")
-                                               arguments.push_back(required);
-                                       else
-                                               arguments.push_back(verbatim);
-                               } else {
-                                       p.getArg('[', ']');
-                                       arguments.push_back(optional);
-                               }
+                       string const command = t.asInput();
+                       if (command == "\\begin") {
+                               string const name = p.getArg('{', '}');
+                               if (name == "environments" || name == "reLyXre")
+                                       // We understand "reLyXre", but it is
+                                       // not as powerful as "environments".
+                                       read_environment(p, name,
+                                               known_environments);
+                               else if (name == "mathenvironments")
+                                       read_environment(p, name,
+                                               known_math_environments);
+                       } else {
+                               read_command(p, command, known_commands);
                        }
-                       known_commands[command] = arguments;
                }
        }
 }
@@ -218,14 +268,14 @@ typedef boost::function<int(string const &, string const &)> cmd_helper;
 
 int parse_help(string const &, string const &)
 {
-       cerr << "Usage: tex2lyx [ command line switches ] <infile.tex>\n"
-               "Command line switches (case sensitive):\n"
-               "\t-help              summarize tex2lyx usage\n"
-               "\t-f                 Force creation of .lyx files even if they exist already\n"
+       cerr << "Usage: tex2lyx [ command line switches ] <infile.tex> [<outfile.lyx>]\n"
+               "Command line switches (case sensitive):\n"
+               "\t-help              summarize tex2lyx usage\n"
+               "\t-f                 Force creation of .lyx files even if they exist already\n"
                "\t-userdir dir       try to set user directory to dir\n"
                "\t-sysdir dir        try to set system directory to dir\n"
-               "\t-c textclass       declare the textclass\n"
-               "\t-s syntaxfile      read additional syntax file" << endl;
+               "\t-c textclass       declare the textclass\n"
+               "\t-s syntaxfile      read additional syntax file" << endl;
        exit(0);
 }
 
@@ -322,8 +372,10 @@ void easyParse(int & argc, char * argv[])
 }
 
 
-// path of the parsed file
+// path of the first parsed file
 string masterFilePath;
+// path of the currently parsed file
+string parentFilePath;
 
 } // anonymous namespace
 
@@ -333,7 +385,23 @@ string getMasterFilePath()
        return masterFilePath;
 }
 
+string getParentFilePath()
+{
+       return parentFilePath;
+}
+
+
+namespace {
 
+/*!
+ *  Reads tex input from \a is and writes lyx output to \a os.
+ *  Uses some common settings for the preamble, so this should only
+ *  be used more than once for included documents.
+ *  Caution: Overwrites the existing preamble settings if the new document
+ *  contains a preamble.
+ *  You must ensure that \p parentFilePath is properly set before calling
+ *  this function!
+ */
 void tex2lyx(std::istream &is, std::ostream &os)
 {
        Parser p(is);
@@ -345,6 +413,9 @@ void tex2lyx(std::istream &is, std::ostream &os)
        active_environments.push_back("document");
        Context context(true, textclass);
        parse_text(p, ss, FLAG_END, true, context);
+       if (Context::empty)
+               // Empty document body. LyX needs at least one paragraph.
+               context.check_layout(ss);
        context.check_end_layout(ss);
        ss << "\n\\end_body\n\\end_document\n";
        active_environments.pop_back();
@@ -360,39 +431,82 @@ void tex2lyx(std::istream &is, std::ostream &os)
 }
 
 
-bool tex2lyx(string const &infilename, string const &outfilename)
+/// convert TeX from \p infilename to LyX and write it to \p os
+bool tex2lyx(string const &infilename, std::ostream &os)
 {
-       if (!(IsFileReadable(infilename) && fs::is_writable(outfilename))) {
+       BOOST_ASSERT(lyx::support::absolutePath(infilename));
+       ifstream is(infilename.c_str());
+       if (!is.good()) {
+               cerr << "Could not open input file \"" << infilename
+                    << "\" for reading." << endl;
                return false;
        }
-       if (!overwrite_files && IsFileReadable(outfilename)) {
-               cerr << "Not overwriting existing file " << outfilename << "\n";
-               return false;
+       string const oldParentFilePath = parentFilePath;
+       parentFilePath = onlyPath(infilename);
+       tex2lyx(is, os);
+       parentFilePath = oldParentFilePath;
+       return true;
+}
+
+} // anonymous namespace
+
+
+bool tex2lyx(string const &infilename, string const &outfilename)
+{
+       if (isFileReadable(outfilename)) {
+               if (overwrite_files) {
+                       cerr << "Overwriting existing file "
+                            << outfilename << endl;
+               } else {
+                       cerr << "Not overwriting existing file "
+                            << outfilename << endl;
+                       return false;
+               }
+       } else {
+               cerr << "Creating file " << outfilename << endl;
        }
-       ifstream is(infilename.c_str());
        ofstream os(outfilename.c_str());
+       if (!os.good()) {
+               cerr << "Could not open output file \"" << outfilename
+                    << "\" for writing." << endl;
+               return false;
+       }
 #ifdef FILEDEBUG
-       cerr << "File: " << infilename << "\n";
+       cerr << "Input file: " << infilename << "\n";
+       cerr << "Output file: " << outfilename << "\n";
 #endif
-       tex2lyx(is, os);
-       return true;
+       return tex2lyx(infilename, os);
 }
 
 
 int main(int argc, char * argv[])
 {
+       fs::path::default_name_check(fs::no_check);
+
        easyParse(argc, argv);
 
        if (argc <= 1) {
-               cerr << "Usage: tex2lyx [ command line switches ] <infile.tex>\n"
-                         "See tex2lyx -help." << endl;
+               cerr << "Usage: tex2lyx [ command line switches ] <infile.tex> [<outfile.lyx>]\n"
+                         "See tex2lyx -help." << endl;
                return 2;
        }
 
        lyx::support::os::init(argc, argv);
-       lyx::support::init_package(argv[0], cl_system_support, cl_user_support);
-
-       string const system_syntaxfile = lyx::support::LibFileSearch("reLyX", "syntax.default");
+       lyx::support::init_package(argv[0], cl_system_support, cl_user_support,
+                                  lyx::support::top_build_dir_is_two_levels_up);
+
+       // Now every known option is parsed. Look for input and output
+       // file name (the latter is optional).
+       string const infilename = makeAbsPath(argv[1]);
+       string outfilename;
+       if (argc > 2) {
+               outfilename = argv[2];
+               if (outfilename != "-")
+                       outfilename = makeAbsPath(argv[2]);
+       } else
+               outfilename = changeExtension(infilename, ".lyx");
+
+       string const system_syntaxfile = lyx::support::libFileSearch("", "syntax.default");
        if (system_syntaxfile.empty()) {
                cerr << "Error: Could not find syntax file \"syntax.default\"." << endl;
                exit(1);
@@ -401,21 +515,20 @@ int main(int argc, char * argv[])
        if (!syntaxfile.empty())
                read_syntaxfile(syntaxfile);
 
-       if (!IsFileReadable(argv[1])) {
-               cerr << "Could not open input file \"" << argv[1]
-                    << "\" for reading." << endl;
-               return 2;
+       masterFilePath = onlyPath(infilename);
+       parentFilePath = masterFilePath;
+
+       if (outfilename == "-") {
+               if (tex2lyx(infilename, cout))
+                       return EXIT_SUCCESS;
+               else
+                       return EXIT_FAILURE;
+       } else {
+               if (tex2lyx(infilename, outfilename))
+                       return EXIT_SUCCESS;
+               else
+                       return EXIT_FAILURE;
        }
-
-       if (lyx::support::AbsolutePath(argv[1]))
-               masterFilePath = lyx::support::OnlyPath(argv[1]);
-       else
-               masterFilePath = lyx::support::getcwd();
-
-       ifstream is(argv[1]);
-       tex2lyx(is, cout);
-
-       return 0;
 }
 
 // }])