]> git.lyx.org Git - lyx.git/blobdiff - src/LaTeX.cpp
Update RELEASE-NOTES
[lyx.git] / src / LaTeX.cpp
index abb8885fecf84f0df12c771f277a85670ab1c9ee..22a3a97d465022849fc0568923f635344a4b57d0 100644 (file)
@@ -49,7 +49,6 @@ namespace os = support::os;
 //   different way.
 // - the makeindex style files should be taken care of with
 //   the dependency mechanism.
-// - we should perhaps also scan the bibtex log file
 
 namespace {
 
@@ -93,8 +92,8 @@ bool operator!=(AuxInfo const & a, AuxInfo const & o)
  */
 
 LaTeX::LaTeX(string const & latex, OutputParams const & rp,
-            FileName const & f)
-       : cmd(latex), file(f), runparams(rp)
+            FileName const & f, string const & p)
+       : cmd(latex), file(f), path(p), runparams(rp), biber(false)
 {
        num_errors = 0;
        if (prefixIs(cmd, "pdf")) { // Do we use pdflatex ?
@@ -184,7 +183,7 @@ int LaTeX::run(TeXErrors & terr)
 
        bool had_depfile = depfile.exists();
        bool run_bibtex = false;
-       FileName const aux_file(changeExtension(file.absFileName(), "aux"));
+       FileName const aux_file(changeExtension(file.absFileName(), ".aux"));
 
        if (had_depfile) {
                LYXERR(Debug::DEPEND, "Dependency file exists");
@@ -279,7 +278,7 @@ int LaTeX::run(TeXErrors & terr)
        // biber writes no info to the aux file, so we just check
        // if a bcf file exists (and if it was updated)
        FileName const bcffile(changeExtension(file.absFileName(), ".bcf"));
-       bool const biber = head.exist(bcffile);
+       biber |= head.exist(bcffile);
 
        // run bibtex
        // if (scanres & UNDEF_CIT || scanres & RERUN || run_bibtex)
@@ -290,21 +289,21 @@ int LaTeX::run(TeXErrors & terr)
                // no checks for now
                LYXERR(Debug::LATEX, "Running BibTeX.");
                message(_("Running BibTeX."));
-               updateBibtexDependencies(head, bibtex_info, biber);
-               rerun |= runBibTeX(bibtex_info, runparams, biber);
-               if (biber) {
-                       // since biber writes no info to the aux file, we have
-                       // to parse the blg file (which only exists after biber
-                       // was first issued)
-                       FileName const blgfile(changeExtension(file.absFileName(), ".blg"));
-                       if (blgfile.exists())
-                               scanBlgFile(head);
+               updateBibtexDependencies(head, bibtex_info);
+               rerun |= runBibTeX(bibtex_info, runparams);
+               FileName const blgfile(changeExtension(file.absFileName(), ".blg"));
+               if (blgfile.exists()) {
+                       int bscanres = scanBlgFile(head, terr);
+                       if (bscanres & ERRORS) {
+                               deleteFilesOnError();
+                               return bscanres; // return on error
+                       }
                }
        } else if (!had_depfile) {
                /// If we run pdflatex on the file after running latex on it,
                /// then we do not need to run bibtex, but we do need to
                /// insert the .bib and .bst files into the .dep-pdf file.
-               updateBibtexDependencies(head, bibtex_info, biber);
+               updateBibtexDependencies(head, bibtex_info);
        }
 
        // 2
@@ -350,8 +349,16 @@ int LaTeX::run(TeXErrors & terr)
                // no checks for now
                LYXERR(Debug::LATEX, "Running BibTeX.");
                message(_("Running BibTeX."));
-               updateBibtexDependencies(head, bibtex_info, biber);
-               rerun |= runBibTeX(bibtex_info, runparams, biber);
+               updateBibtexDependencies(head, bibtex_info);
+               rerun |= runBibTeX(bibtex_info, runparams);
+               FileName const blgfile(changeExtension(file.absFileName(), ".blg"));
+               if (blgfile.exists()) {
+                       int bscanres = scanBlgFile(head, terr);
+                       if (bscanres & ERRORS) {
+                               deleteFilesOnError();
+                               return bscanres; // return on error
+                       }
+               }
        }
 
        // 4
@@ -421,7 +428,7 @@ int LaTeX::startscript()
                     + quoteName(onlyFileName(file.toFilesystemEncoding()))
                     + " > " + os::nulldev();
        Systemcall one;
-       return one.startscript(Systemcall::Wait, tmp);
+       return one.startscript(Systemcall::Wait, tmp, path);
 }
 
 
@@ -448,7 +455,7 @@ bool LaTeX::runMakeIndex(string const & f, OutputParams const & runparams,
        tmp += quoteName(f);
        tmp += params;
        Systemcall one;
-       one.startscript(Systemcall::Wait, tmp);
+       one.startscript(Systemcall::Wait, tmp, path);
        return true;
 }
 
@@ -464,7 +471,7 @@ bool LaTeX::runMakeIndexNomencl(FileName const & file,
        tmp += " -o "
                + onlyFileName(changeExtension(file.toFilesystemEncoding(), nls));
        Systemcall one;
-       one.startscript(Systemcall::Wait, tmp);
+       one.startscript(Systemcall::Wait, tmp, path);
        return true;
 }
 
@@ -550,8 +557,7 @@ void LaTeX::scanAuxFile(FileName const & file, AuxInfo & aux_info)
 
 
 void LaTeX::updateBibtexDependencies(DepTable & dep,
-                                    vector<AuxInfo> const & bibtex_info,
-                                    bool biber)
+                                    vector<AuxInfo> const & bibtex_info)
 {
        // Since a run of Bibtex mandates more latex runs it is ok to
        // remove all ".bib" and ".bst" files.
@@ -579,13 +585,14 @@ void LaTeX::updateBibtexDependencies(DepTable & dep,
        // biber writes nothing into the aux file.
        // Instead, we have to scan the blg file
        if (biber) {
-               scanBlgFile(dep);
+               TeXErrors terr;
+               scanBlgFile(dep, terr);
        }
 }
 
 
 bool LaTeX::runBibTeX(vector<AuxInfo> const & bibtex_info,
-                     OutputParams const & runparams, bool biber)
+                     OutputParams const & runparams)
 {
        bool result = false;
        for (vector<AuxInfo>::const_iterator it = bibtex_info.begin();
@@ -604,7 +611,7 @@ bool LaTeX::runBibTeX(vector<AuxInfo> const & bibtex_info,
                tmp += quoteName(onlyFileName(removeExtension(
                                it->aux_file.absFileName())));
                Systemcall one;
-               one.startscript(Systemcall::Wait, tmp);
+               one.startscript(Systemcall::Wait, tmp, path);
        }
        // Return whether bibtex was run
        return result;
@@ -695,6 +702,10 @@ int LaTeX::scanLogFile(TeXErrors & terr)
                                   && contains(token, "on page")
                                   && contains(token, "undefined")) {
                                retval |= UNDEF_CIT;
+                       } else if (contains(token, "Citation")
+                                  && contains(token, "on input line")
+                                  && contains(token, "undefined")) {
+                               retval |= UNDEF_CIT;
                        }
                } else if (prefixIs(token, "Package")) {
                        // Package warnings
@@ -708,6 +719,9 @@ int LaTeX::scanLogFile(TeXErrors & terr)
                                }
                        } else if (contains(token, "run BibTeX")) {
                                retval |= UNDEF_CIT;
+                       } else if (contains(token, "run Biber")) {
+                               retval |= UNDEF_CIT;
+                               biber = true;
                        } else if (contains(token, "Rerun LaTeX") ||
                                   contains(token, "Please rerun LaTeX") ||
                                   contains(token, "Rerun to get")) {
@@ -745,11 +759,24 @@ int LaTeX::scanLogFile(TeXErrors & terr)
                        if (contains(token, "LaTeX Error:"))
                                retval |= LATEX_ERROR;
 
-                       // bug 6445. At this point its not clear we finish with error.
                        if (prefixIs(token, "! File ended while scanning")){
-                               wait_for_error = desc;
-                               continue;
+                               if (prefixIs(token, "! File ended while scanning use of \\Hy@setref@link.")){
+                                       // bug 7344. We must rerun LaTeX if hyperref has been toggled.
+                                       retval |= ERROR_RERUN;
+                                       LYXERR(Debug::LATEX, "Force rerun.");
+                               } else {
+                                       // bug 6445. At this point its not clear we finish with error.
+                                       wait_for_error = desc;
+                                       continue;
+                               }
+                       }
+
+                       if (prefixIs(token, "! Paragraph ended before \\Hy@setref@link was complete.")){
+                                       // bug 7344. We must rerun LaTeX if hyperref has been toggled.
+                                       retval |= ERROR_RERUN;
+                                       LYXERR(Debug::LATEX, "Force rerun.");
                        }
+
                        if (!wait_for_error.empty() && prefixIs(token, "! Emergency stop.")){
                                retval |= LATEX_ERROR;
                                string errstr;
@@ -969,8 +996,10 @@ bool handleFoundFile(string const & ff, DepTable & head)
 }
 
 
-bool checkLineBreak(string const & ff, DepTable & head)
+bool completeFilename(string const & ff, DepTable & head)
 {
+       // If we do not find a dot, we suspect
+       // a fragmental file name
        if (!contains(ff, '.'))
                return false;
 
@@ -978,6 +1007,69 @@ bool checkLineBreak(string const & ff, DepTable & head)
        return handleFoundFile(ff, head);
 }
 
+
+int iterateLine(string const token, regex const reg, string const & closing,
+               int fragment_pos, DepTable & head)
+{
+       smatch what;
+       string::const_iterator first = token.begin();
+       string::const_iterator end = token.end();
+       bool fragment = false;
+       string last_match;
+
+       while (regex_search(first, end, what, reg)) {
+               // if we have a dot, try to handle as file
+               if (contains(what.str(1), '.')) {
+                       first = what[0].second;
+                       if (what.str(2) == closing) {
+                               handleFoundFile(what.str(1), head);
+                               // since we had a closing bracket,
+                               // do not investigate further
+                               fragment = false;
+                       } else
+                               // if we have no closing bracket,
+                               // try to handle as file nevertheless
+                               fragment = !handleFoundFile(
+                                       what.str(1) + what.str(2), head);
+               }
+               // if we do not have a dot, check if the line has
+               // a closing bracket (else, we suspect a line break)
+               else if (what.str(2) != closing) {
+                       first = what[0].second;
+                       fragment = true;
+               } else {
+                       // we have a closing bracket, so the content
+                       // is not a file name.
+                       // no need to investigate further
+                       first = what[0].second;
+                       fragment = false;
+               }
+               last_match = what.str(1);
+       }
+
+       // We need to consider the result from previous line iterations:
+       // We might not find a fragment here, but another one might follow
+       // E.g.: (filename.ext) <filenam
+       // Vice versa, we consider the search completed if a real match
+       // follows a potential fragment from a previous iteration.
+       // E.g. <some text we considered a fragment (filename.ext)
+       // result = -1 means we did not find a fragment!
+       int result = -1;
+       int last_match_pos = -1;
+       if (!last_match.empty() && token.find(last_match) != string::npos)
+               last_match_pos = int(token.find(last_match));
+       if (fragment) {
+               if (last_match_pos > fragment_pos)
+                       result = last_match_pos;
+               else
+                       result = fragment_pos;
+       } else
+               if (last_match_pos < fragment_pos)
+                       result = fragment_pos;
+
+       return result;
+}
+
 } // anon namespace
 
 
@@ -998,8 +1090,6 @@ void LaTeX::deplog(DepTable & head)
        // but instead only a line like this into the log:
        //   Writing index file sample.idx
        static regex const reg4("Writing index file (.+).*");
-       // files also can be enclosed in <...>
-       static regex const reg5("<([^>]+)(.).*");
        static regex const regoldnomencl("Writing glossary file (.+).*");
        static regex const regnomencl("Writing nomenclature file (.+).*");
        // If a toc should be created, MikTex does not write a line like
@@ -1009,6 +1099,9 @@ void LaTeX::deplog(DepTable & head)
        // This line is also written by tetex.
        // This line is not present if no toc should be created.
        static regex const miktexTocReg("\\\\tf@toc=\\\\write.*");
+       // file names can be enclosed in <...> (anywhere on the line)
+       static regex const reg5(".*<[^>]+.*");
+       // and also (...) anywhere on the line
        static regex const reg6(".*\\([^)]+.*");
 
        FileName const fn = makeAbsPath(logfile);
@@ -1016,10 +1109,12 @@ void LaTeX::deplog(DepTable & head)
        string lastline;
        while (ifs) {
                // Ok, the scanning of files here is not sufficient.
-               // Sometimes files are named by "File: xxx" only
-               // So I think we should use some regexps to find files instead.
+               // Sometimes files are named by "File: xxx" only
+               // Therefore we use some regexps to find files instead.
                // Note: all file names and paths might contains spaces.
-               bool found_file = false;
+               // Also, file names might be broken across lines. Therefore
+               // we mark (potential) fragments and merge those lines.
+               bool fragment = false;
                string token;
                getline(ifs, token);
                // MikTeX sometimes inserts \0 in the log file. They can't be
@@ -1033,6 +1128,10 @@ void LaTeX::deplog(DepTable & head)
                        continue;
                }
 
+               // FIXME UNICODE: We assume that the file names in the log
+               // file are in the file system encoding.
+               token = to_utf8(from_filesystem8bit(token));
+
                // Sometimes, filenames are broken across lines.
                // We care for that and save suspicious lines.
                // Here we exclude some cases where we are sure
@@ -1062,106 +1161,74 @@ void LaTeX::deplog(DepTable & head)
 
                smatch sub;
 
-               // FIXME UNICODE: We assume that the file names in the log
-               // file are in the file system encoding.
-               token = to_utf8(from_filesystem8bit(token));
-
                // (1) "File: file.ext"
                if (regex_match(token, sub, reg1)) {
-                       // check for dot
-                       found_file = checkLineBreak(sub.str(1), head);
+                       // is this a fragmental file name?
+                       fragment = !completeFilename(sub.str(1), head);
                        // However, ...
                        if (suffixIs(token, ")"))
-                               // no line break for sure
-                               // pretend we've been successfully searching
-                               found_file = true;
+                               // no fragment for sure
+                               fragment = false;
                // (2) "No file file.ext"
                } else if (regex_match(token, sub, reg2)) {
                        // file names must contains a dot, line ends with dot
                        if (contains(sub.str(1), '.') && sub.str(2) == ".")
-                               found_file = handleFoundFile(sub.str(1), head);
+                               fragment = !handleFoundFile(sub.str(1), head);
                        else
                                // we suspect a line break
-                               found_file = false;
+                               fragment = true;
                // (3) "\openout<nr> = `file.ext'."
                } else if (regex_match(token, sub, reg3)) {
                        // search for closing '. at the end of the line
                        if (sub.str(2) == "\'.")
-                               found_file = handleFoundFile(sub.str(1), head);
+                               fragment = !handleFoundFile(sub.str(1), head);
                        else
-                               // probable line break
-                               found_file = false;
+                               // potential fragment
+                               fragment = true;
                // (4) "Writing index file file.ext"
                } else if (regex_match(token, sub, reg4))
-                       // check for dot
-                       found_file = checkLineBreak(sub.str(1), head);
-               // (5) "<file.ext>"
-               else if (regex_match(token, sub, reg5)) {
-                       // search for closing '>' and dot ('*.*>') at the eol
-                       if (contains(sub.str(1), '.') && sub.str(2) == ">")
-                               found_file = handleFoundFile(sub.str(1), head);
-                       else
-                               // probable line break
-                               found_file = false;
-               // (6) "Writing nomenclature file file.ext"
-               } else if (regex_match(token, sub, regnomencl) ||
+                       // fragmential file name?
+                       fragment = !completeFilename(sub.str(1), head);
+               // (5) "Writing nomenclature file file.ext"
+               else if (regex_match(token, sub, regnomencl) ||
                           regex_match(token, sub, regoldnomencl))
-                       // check for dot
-                       found_file = checkLineBreak(sub.str(1), head);
-               // (7) "\tf@toc=\write<nr>" (for MikTeX)
+                       // fragmental file name?
+                       fragment= !completeFilename(sub.str(1), head);
+               // (6) "\tf@toc=\write<nr>" (for MikTeX)
                else if (regex_match(token, sub, miktexTocReg))
-                       found_file = handleFoundFile(onlyFileName(changeExtension(
+                       fragment = !handleFoundFile(onlyFileName(changeExtension(
                                                file.absFileName(), ".toc")), head);
                else
                        // not found, but we won't check further
-                       // pretend we've been successfully searching
-                       found_file = true;
+                       fragment = false;
+
+               int fragment_pos = -1;
+               // (7) "<file.ext>"
+               // We can have several of these on one line
+               // (and in addition to those above)
+               if (regex_match(token, sub, reg5)) {
+                       // search for strings in <...>
+                       static regex reg5_1("<([^>]+)(.)");
+                       fragment_pos = iterateLine(token, reg5_1, ">",
+                                                  fragment_pos, head);
+                       fragment = (fragment_pos != -1);
+               }
 
-               // (8) "(file.ext"
-               // note that we can have several of these on one line
+               // (8) "(file.ext)"
+               // We can have several of these on one line
                // this must be queried separated, because of
                // cases such as "File: file.ext (type eps)"
                // where "File: file.ext" would be skipped
                if (regex_match(token, sub, reg6)) {
                        // search for strings in (...)
                        static regex reg6_1("\\(([^()]+)(.)");
-                       smatch what;
-                       string::const_iterator first = token.begin();
-                       string::const_iterator end = token.end();
-
-                       while (regex_search(first, end, what, reg6_1)) {
-                               // if we have a dot, try to handle as file
-                               if (contains(what.str(1), '.')) {
-                                       first = what[0].second;
-                                       if (what.str(2) == ")") {
-                                               handleFoundFile(what.str(1), head);
-                                               // since we had a closing bracket,
-                                               // do not investigate further
-                                               found_file = true;
-                                       } else
-                                               // if we have no closing bracket,
-                                               // try to handle as file nevertheless
-                                               found_file = handleFoundFile(
-                                                       what.str(1) + what.str(2), head);
-                               }
-                               // if we do not have a dot, check if the line has
-                               // a closing bracket (else, we suspect a line break)
-                               else if (what.str(2) != ")") {
-                                       first = what[0].second;
-                                       found_file = false;
-                               } else {
-                                       // we have a closing bracket, so the content
-                                       // is not a file name.
-                                       // no need to investigate further
-                                       // pretend we've been successfully searching
-                                       first = what[0].second;
-                                       found_file = true;
-                               }
-                       }
+                       fragment_pos = iterateLine(token, reg6_1, ")",
+                                                  fragment_pos, head);
+                       fragment = (fragment_pos != -1);
                }
 
-               if (!found_file)
-                       // probable linebreak:
+               if (fragment)
+                       // probable linebreak within file name:
                        // save this line
                        lastline = token;
                else
@@ -1174,15 +1241,23 @@ void LaTeX::deplog(DepTable & head)
 }
 
 
-void LaTeX::scanBlgFile(DepTable & dep)
+int LaTeX::scanBlgFile(DepTable & dep, TeXErrors & terr)
 {
        FileName const blg_file(changeExtension(file.absFileName(), "blg"));
        LYXERR(Debug::LATEX, "Scanning blg file: " << blg_file);
 
        ifstream ifs(blg_file.toFilesystemEncoding().c_str());
        string token;
-       static regex const reg1(".*Found bibtex data file '([^']+).*");
+       static regex const reg1(".*Found (bibtex|BibTeX) data (file|source) '([^']+).*");
+       static regex const bibtexError("^(.*---line [0-9]+ of file).*$");
+       static regex const bibtexError2("^(.*---while reading file).*$");
+       static regex const bibtexError3("(A bad cross reference---).*");
+       static regex const bibtexError4("(Sorry---you've exceeded BibTeX's).*");
+       static regex const bibtexError5("\\*Please notify the BibTeX maintainer\\*");
+       static regex const biberError("^.*> (FATAL|ERROR) - (.*)$");
+       int retval = NO_ERRORS;
 
+       string prevtoken;
        while (getline(ifs, token)) {
                token = rtrim(token, "\r");
                smatch sub;
@@ -1190,13 +1265,48 @@ void LaTeX::scanBlgFile(DepTable & dep)
                // in the aux file are in the file system encoding.
                token = to_utf8(from_filesystem8bit(token));
                if (regex_match(token, sub, reg1)) {
-                       string data = sub.str(1);
+                       string data = sub.str(3);
                        if (!data.empty()) {
                                LYXERR(Debug::LATEX, "Found bib file: " << data);
                                handleFoundFile(data, dep);
                        }
                }
-       } 
+               else if (regex_match(token, sub, bibtexError)
+                        || regex_match(token, sub, bibtexError2)
+                        || regex_match(token, sub, bibtexError4)
+                        || regex_match(token, sub, bibtexError5)) {
+                       retval |= BIBTEX_ERROR;
+                       string errstr = N_("BibTeX error: ") + token;
+                       string message;
+                       if ((prefixIs(token, "while executing---line")
+                            || prefixIs(token, "---line ")
+                            || prefixIs(token, "*Please notify the BibTeX"))
+                           && !prevtoken.empty()) {
+                               errstr = N_("BibTeX error: ") + prevtoken;
+                               message = prevtoken + '\n';
+                       }
+                       message += token;
+                       terr.insertError(0,
+                                        from_local8bit(errstr),
+                                        from_local8bit(message));
+               } else if (regex_match(prevtoken, sub, bibtexError3)) {
+                       retval |= BIBTEX_ERROR;
+                       string errstr = N_("BibTeX error: ") + prevtoken;
+                       string message = prevtoken + '\n' + token;
+                       terr.insertError(0,
+                                        from_local8bit(errstr),
+                                        from_local8bit(message));
+               } else if (regex_match(token, sub, biberError)) {
+                       retval |= BIBTEX_ERROR;
+                       string errstr = N_("Biber error: ") + sub.str(2);
+                       string message = token;
+                       terr.insertError(0,
+                                        from_local8bit(errstr),
+                                        from_local8bit(message));
+               }
+               prevtoken = token;
+       }
+       return retval;
 }