X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FLaTeX.cpp;h=e787e1ad5176e513bfd1d6432f9a2cd00103e910;hb=dae8555234f0e5c8358b91c307d91b219c664617;hp=abb8885fecf84f0df12c771f277a85670ab1c9ee;hpb=2e18f6a671a111b30838c48dc86c4783919ebdf6;p=lyx.git diff --git a/src/LaTeX.cpp b/src/LaTeX.cpp index abb8885fec..e787e1ad51 100644 --- a/src/LaTeX.cpp +++ b/src/LaTeX.cpp @@ -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"); @@ -269,7 +268,12 @@ int LaTeX::run(TeXErrors & terr) runparams); } FileName const nlofile(changeExtension(file.absFileName(), ".nlo")); - if (head.haschanged(nlofile)) + // If all nomencl entries are removed, nomencl writes an empty nlo file. + // DepTable::hasChanged() returns false in this case, since it does not + // distinguish empty files from non-existing files. This is why we need + // the extra checks here (to trigger a rerun). Cf. discussions in #8905. + // FIXME: Sort out the real problem in DepTable. + if (head.haschanged(nlofile) || (nlofile.exists() && nlofile.isFileEmpty())) rerun |= runMakeIndexNomencl(file, ".nlo", ".nls"); FileName const glofile(changeExtension(file.absFileName(), ".glo")); if (head.haschanged(glofile)) @@ -279,7 +283,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 +294,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 +354,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 +433,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 +460,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 +476,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 +562,7 @@ void LaTeX::scanAuxFile(FileName const & file, AuxInfo & aux_info) void LaTeX::updateBibtexDependencies(DepTable & dep, - vector const & bibtex_info, - bool biber) + vector const & bibtex_info) { // Since a run of Bibtex mandates more latex runs it is ok to // remove all ".bib" and ".bst" files. @@ -579,13 +590,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 const & bibtex_info, - OutputParams const & runparams, bool biber) + OutputParams const & runparams) { bool result = false; for (vector::const_iterator it = bibtex_info.begin(); @@ -604,7 +616,7 @@ bool LaTeX::runBibTeX(vector 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; @@ -622,8 +634,8 @@ int LaTeX::scanLogFile(TeXErrors & terr) FileName const fn = FileName(makeAbsPath(tmp)); ifstream ifs(fn.toFilesystemEncoding().c_str()); bool fle_style = false; - static regex file_line_error(".+\\.\\D+:[0-9]+: (.+)"); - static regex child_file(".*([0-9]+[A-Za-z]*_.+\\.tex).*"); + static regex const file_line_error(".+\\.\\D+:[0-9]+: (.+)"); + static regex const child_file(".*([0-9]+[A-Za-z]*_.+\\.tex).*"); // Flag for 'File ended while scanning' message. // We need to wait for subsequent processing. string wait_for_error; @@ -654,8 +666,8 @@ int LaTeX::scanLogFile(TeXErrors & terr) size_t len = j == string::npos ? token.substr(i + 1).length() : j - i - 1; - if (regex_match(token.substr(i + 1, len), - sub, child_file)) { + string const substr = token.substr(i + 1, len); + if (regex_match(substr, sub, child_file)) { string const name = sub.str(1); child.push(make_pair(name, pnest)); i += len; @@ -695,6 +707,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 +724,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 +764,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; @@ -784,9 +816,11 @@ int LaTeX::scanLogFile(TeXErrors & terr) // we have a latex error retval |= TEX_ERROR; if (contains(desc, - "Package babel Error: You haven't defined the language") || - contains(desc, - "Package babel Error: You haven't loaded the option")) + "Package babel Error: You haven't defined the language") + || contains(desc, + "Package babel Error: You haven't loaded the option") + || contains(desc, + "Package babel Error: Unknown language")) retval |= ERROR_RERUN; // get the line number: int line = 0; @@ -969,8 +1003,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 +1014,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) 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 +1097,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 +1106,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 +1116,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 +1135,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 +1168,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 = `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) "" - 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" (for MikTeX) + // fragmental file name? + fragment= !completeFilename(sub.str(1), head); + // (6) "\tf@toc=\write" (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) "" + // 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 const 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; - } - } + static regex const reg6_1("\\(([^()]+)(.)"); + 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 +1248,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 +1272,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; }