X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FLaTeX.cpp;h=df20f7b285bb999fc579674b6c03390ee3ea4cce;hb=05282ceeec5fc9872f73f7de93136fc218f8ecb4;hp=04b63e3a6877c98e479464fde0cfe70a1e1fbfbd;hpb=d8396e9c7f2e8d225e537b279c4a7a513ddee707;p=lyx.git diff --git a/src/LaTeX.cpp b/src/LaTeX.cpp index 04b63e3a68..df20f7b285 100644 --- a/src/LaTeX.cpp +++ b/src/LaTeX.cpp @@ -18,6 +18,7 @@ #include "BufferList.h" #include "LaTeX.h" #include "LyXRC.h" +#include "LyX.h" #include "DepTable.h" #include "support/debug.h" @@ -29,12 +30,11 @@ #include "support/Systemcall.h" #include "support/os.h" -#include +#include "support/regex.h" #include +#include -using boost::regex; -using boost::smatch; using namespace std; using namespace lyx::support; @@ -50,9 +50,6 @@ namespace os = support::os; // different way. // - the makeindex style files should be taken care of with // the dependency mechanism. -// - makeindex commandline options should be supported -// - somewhere support viewing of bibtex and makeindex log files. -// - we should perhaps also scan the bibtex log file namespace { @@ -61,16 +58,17 @@ docstring runMessage(unsigned int count) return bformat(_("Waiting for LaTeX run number %1$d"), count); } -} // anon namespace +} // namespace /* * CLASS TEXERRORS */ void TeXErrors::insertError(int line, docstring const & error_desc, - docstring const & error_text) + docstring const & error_text, + string const & child_name) { - Error newerr(line, error_desc, error_text); + Error newerr(line, error_desc, error_text, child_name); errors.push_back(newerr); } @@ -95,25 +93,36 @@ 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, string const & lp, + bool const clean_start) + : cmd(latex), file(f), path(p), lpath(lp), runparams(rp), biber(false) { num_errors = 0; - if (prefixIs(cmd, "pdf")) { // Do we use pdflatex ? - depfile = FileName(file.absFilename() + ".dep-pdf"); + // lualatex can still produce a DVI with --output-format=dvi. However, + // we do not use that internally (we use the "dvilualatex" command) so + // it would only happen from a custom converter. Thus, it is better to + // guess that lualatex produces a PDF than to guess a DVI. + // FIXME we should base the extension on the output format, which we should + // get in a robust way, e.g. from the converter. + if (prefixIs(cmd, "pdf") || prefixIs(cmd, "lualatex") || prefixIs(cmd, "xelatex")) { + depfile = FileName(file.absFileName() + ".dep-pdf"); output_file = - FileName(changeExtension(file.absFilename(), ".pdf")); + FileName(changeExtension(file.absFileName(), ".pdf")); } else { - depfile = FileName(file.absFilename() + ".dep"); + depfile = FileName(file.absFileName() + ".dep"); output_file = - FileName(changeExtension(file.absFilename(), ".dvi")); + FileName(changeExtension(file.absFileName(), ".dvi")); } + if (clean_start) + removeAuxiliaryFiles(); } -void LaTeX::deleteFilesOnError() const +void LaTeX::removeAuxiliaryFiles() const { - // currently just a dummy function. + // Note that we do not always call this function when there is an error. + // For example, if there is an error but an output file is produced we + // still would like to output (export/view) the file. // What files do we have to delete? @@ -123,24 +132,35 @@ void LaTeX::deleteFilesOnError() const // but the reason for the error might be in a generated file... // bibtex file - FileName const bbl(changeExtension(file.absFilename(), ".bbl")); + FileName const bbl(changeExtension(file.absFileName(), ".bbl")); bbl.removeFile(); + // biber file + FileName const bcf(changeExtension(file.absFileName(), ".bcf")); + bcf.removeFile(); + // makeindex file - FileName const ind(changeExtension(file.absFilename(), ".ind")); + FileName const ind(changeExtension(file.absFileName(), ".ind")); ind.removeFile(); // nomencl file - FileName const nls(changeExtension(file.absFilename(), ".nls")); + FileName const nls(changeExtension(file.absFileName(), ".nls")); nls.removeFile(); // nomencl file (old version of the package) - FileName const gls(changeExtension(file.absFilename(), ".gls")); + FileName const gls(changeExtension(file.absFileName(), ".gls")); gls.removeFile(); // Also remove the aux file - FileName const aux(changeExtension(file.absFilename(), ".aux")); + FileName const aux(changeExtension(file.absFileName(), ".aux")); aux.removeFile(); + + // Also remove the .out file (e.g. hyperref bookmarks) (#9963) + FileName const out(changeExtension(file.absFileName(), ".out")); + out.removeFile(); + + // Remove the output file, which is often generated even if error + output_file.removeFile(); } @@ -148,10 +168,11 @@ int LaTeX::run(TeXErrors & terr) // We know that this function will only be run if the lyx buffer // has been changed. We also know that a newly written .tex file // is always different from the previous one because of the date - // in it. However it seems safe to run latex (at least) on time + // in it. However it seems safe to run latex (at least) one time // each time the .tex file changes. { int scanres = NO_ERRORS; + int bscanres = NO_ERRORS; unsigned int count = 0; // number of times run num_errors = 0; // just to make sure. unsigned int const MAX_RUN = 6; @@ -159,11 +180,9 @@ int LaTeX::run(TeXErrors & terr) bool rerun = false; // rerun requested // The class LaTeX does not know the temp path. - theBufferList().updateIncludedTeXfiles(FileName::getcwd().absFilename(), + theBufferList().updateIncludedTeXfiles(FileName::getcwd().absFileName(), runparams); - // Never write the depfile if an error was encountered. - // 0 // first check if the file dependencies exist: // ->If it does exist @@ -182,7 +201,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"); @@ -218,26 +237,23 @@ int LaTeX::run(TeXErrors & terr) /// in which case we will not need to run bibtex again. vector bibtex_info_old; if (!run_bibtex) - bibtex_info_old = scanAuxFiles(aux_file); + bibtex_info_old = scanAuxFiles(aux_file, runparams.only_childbibs); ++count; LYXERR(Debug::LATEX, "Run #" << count); message(runMessage(count)); - startscript(); + int exit_code = startscript(); + scanres = scanLogFile(terr); if (scanres & ERROR_RERUN) { LYXERR(Debug::LATEX, "Rerunning LaTeX"); - startscript(); + terr.clearErrors(); + exit_code = startscript(); scanres = scanLogFile(terr); } - if (scanres & ERRORS) { - deleteFilesOnError(); - return scanres; // return on error - } - - vector const bibtex_info = scanAuxFiles(aux_file); + vector const bibtex_info = scanAuxFiles(aux_file, runparams.only_childbibs); if (!run_bibtex && bibtex_info_old != bibtex_info) run_bibtex = true; @@ -254,7 +270,7 @@ int LaTeX::run(TeXErrors & terr) // memoir (at least) writes an empty *idx file in the first place. // A second latex run is needed. - FileName const idxfile(changeExtension(file.absFilename(), ".idx")); + FileName const idxfile(changeExtension(file.absFileName(), ".idx")); rerun = idxfile.exists() && idxfile.isFileEmpty(); // run makeindex @@ -262,17 +278,28 @@ int LaTeX::run(TeXErrors & terr) // no checks for now LYXERR(Debug::LATEX, "Running MakeIndex."); message(_("Running Index Processor.")); - // onlyFilename() is needed for cygwin - rerun |= runMakeIndex(onlyFilename(idxfile.absFilename()), + // onlyFileName() is needed for cygwin + rerun |= runMakeIndex(onlyFileName(idxfile.absFileName()), runparams); } - FileName const nlofile(changeExtension(file.absFilename(), ".nlo")); - if (head.haschanged(nlofile)) + FileName const nlofile(changeExtension(file.absFileName(), ".nlo")); + // 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")); + FileName const glofile(changeExtension(file.absFileName(), ".glo")); if (head.haschanged(glofile)) rerun |= runMakeIndexNomencl(file, ".glo", ".gls"); + // check if we're using biber instead of bibtex + // 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")); + biber |= head.exist(bcffile); + // run bibtex // if (scanres & UNDEF_CIT || scanres & RERUN || run_bibtex) if (scanres & UNDEF_CIT || run_bibtex) { @@ -284,6 +311,9 @@ int LaTeX::run(TeXErrors & terr) message(_("Running BibTeX.")); updateBibtexDependencies(head, bibtex_info); rerun |= runBibTeX(bibtex_info, runparams); + FileName const blgfile(changeExtension(file.absFileName(), ".blg")); + if (blgfile.exists()) + bscanres = scanBlgFile(head, terr); } 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 @@ -311,10 +341,6 @@ int LaTeX::run(TeXErrors & terr) message(runMessage(count)); startscript(); scanres = scanLogFile(terr); - if (scanres & ERRORS) { - deleteFilesOnError(); - return scanres; // return on error - } // update the depedencies deplog(head); // reads the latex log @@ -322,7 +348,7 @@ int LaTeX::run(TeXErrors & terr) } else { LYXERR(Debug::DEPEND, "Dep. file has NOT changed"); } - + // 3 // rerun bibtex? // Complex bibliography packages such as Biblatex require @@ -336,6 +362,9 @@ int LaTeX::run(TeXErrors & terr) message(_("Running BibTeX.")); updateBibtexDependencies(head, bibtex_info); rerun |= runBibTeX(bibtex_info, runparams); + FileName const blgfile(changeExtension(file.absFileName(), ".blg")); + if (blgfile.exists()) + bscanres = scanBlgFile(head, terr); } // 4 @@ -351,9 +380,9 @@ int LaTeX::run(TeXErrors & terr) // no checks for now LYXERR(Debug::LATEX, "Running MakeIndex."); message(_("Running Index Processor.")); - // onlyFilename() is needed for cygwin - rerun = runMakeIndex(onlyFilename(changeExtension( - file.absFilename(), ".idx")), runparams); + // onlyFileName() is needed for cygwin + rerun = runMakeIndex(onlyFileName(changeExtension( + file.absFileName(), ".idx")), runparams); } // I am not pretty sure if need this twice. @@ -382,10 +411,6 @@ int LaTeX::run(TeXErrors & terr) message(runMessage(count)); startscript(); scanres = scanLogFile(terr); - if (scanres & ERRORS) { - deleteFilesOnError(); - return scanres; // return on error - } // keep this updated head.update(); @@ -393,19 +418,31 @@ int LaTeX::run(TeXErrors & terr) // Write the dependencies to file. head.write(depfile); + + if (exit_code) { + // add flag here, just before return, instead of when exit_code + // is defined because scanres is sometimes overwritten above + // (e.g. rerun) + scanres |= NONZERO_ERROR; + } + LYXERR(Debug::LATEX, "Done."); + + if (bscanres & ERRORS) + return bscanres; // return on error + return scanres; } int LaTeX::startscript() { - // onlyFilename() is needed for cygwin + // onlyFileName() is needed for cygwin string tmp = cmd + ' ' - + quoteName(onlyFilename(file.toFilesystemEncoding())) + + quoteName(onlyFileName(file.toFilesystemEncoding())) + " > " + os::nulldev(); Systemcall one; - return one.startscript(Systemcall::Wait, tmp); + return one.startscript(Systemcall::Wait, tmp, path, lpath); } @@ -415,6 +452,9 @@ bool LaTeX::runMakeIndex(string const & f, OutputParams const & runparams, string tmp = runparams.use_japanese ? lyxrc.jindex_command : lyxrc.index_command; + if (!runparams.index_command.empty()) + tmp = runparams.index_command; + LYXERR(Debug::LATEX, "idx file has been made, running index processor (" << tmp << ") on file " << f); @@ -429,7 +469,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, lpath); return true; } @@ -440,24 +480,38 @@ bool LaTeX::runMakeIndexNomencl(FileName const & file, LYXERR(Debug::LATEX, "Running MakeIndex for nomencl."); message(_("Running MakeIndex for nomencl.")); string tmp = lyxrc.nomencl_command + ' '; - // onlyFilename() is needed for cygwin - tmp += quoteName(onlyFilename(changeExtension(file.absFilename(), nlo))); + // onlyFileName() is needed for cygwin + tmp += quoteName(onlyFileName(changeExtension(file.absFileName(), nlo))); tmp += " -o " - + onlyFilename(changeExtension(file.toFilesystemEncoding(), nls)); + + onlyFileName(changeExtension(file.toFilesystemEncoding(), nls)); Systemcall one; - one.startscript(Systemcall::Wait, tmp); + one.startscript(Systemcall::Wait, tmp, path, lpath); return true; } vector const -LaTeX::scanAuxFiles(FileName const & file) +LaTeX::scanAuxFiles(FileName const & file, bool const only_childbibs) { vector result; + // With chapterbib, we have to bibtex all children's aux files + // but _not_ the master's! + if (only_childbibs) { + for (string const &s: children) { + FileName fn = + makeAbsPath(s, file.onlyPath().realPath()); + fn.changeExtension("aux"); + if (fn.exists()) + result.push_back(scanAuxFile(fn)); + } + return result; + } + result.push_back(scanAuxFile(file)); - string const basename = removeExtension(file.absFilename()); + // This is for bibtopic + string const basename = removeExtension(file.absFileName()); for (int i = 1; i < 1000; ++i) { FileName const file2(basename + '.' + convert(i) @@ -537,7 +591,7 @@ void LaTeX::updateBibtexDependencies(DepTable & dep, // remove all ".bib" and ".bst" files. dep.remove_files_with_extension(".bib"); dep.remove_files_with_extension(".bst"); - //string aux = OnlyFilename(ChangeExtension(file, ".aux")); + //string aux = OnlyFileName(ChangeExtension(file, ".aux")); for (vector::const_iterator it = bibtex_info.begin(); it != bibtex_info.end(); ++it) { @@ -555,6 +609,13 @@ void LaTeX::updateBibtexDependencies(DepTable & dep, dep.insert(file, true); } } + + // biber writes nothing into the aux file. + // Instead, we have to scan the blg file + if (biber) { + TeXErrors terr; + scanBlgFile(dep, terr); + } } @@ -564,18 +625,17 @@ bool LaTeX::runBibTeX(vector const & bibtex_info, bool result = false; for (vector::const_iterator it = bibtex_info.begin(); it != bibtex_info.end(); ++it) { - if (it->databases.empty()) + if (!biber && it->databases.empty()) continue; result = true; - string tmp = runparams.use_japanese ? - lyxrc.jbibtex_command : lyxrc.bibtex_command; + string tmp = runparams.bibtex_command; tmp += " "; - // onlyFilename() is needed for cygwin - tmp += quoteName(onlyFilename(removeExtension( - it->aux_file.absFilename()))); + // onlyFileName() is needed for cygwin + tmp += quoteName(onlyFileName(removeExtension( + it->aux_file.absFileName()))); Systemcall one; - one.startscript(Systemcall::Wait, tmp); + one.startscript(Systemcall::Wait, tmp, path, lpath); } // Return whether bibtex was run return result; @@ -588,12 +648,23 @@ int LaTeX::scanLogFile(TeXErrors & terr) int line_count = 1; int retval = NO_ERRORS; string tmp = - onlyFilename(changeExtension(file.absFilename(), ".log")); + onlyFileName(changeExtension(file.absFileName(), ".log")); LYXERR(Debug::LATEX, "Log file: " << tmp); FileName const fn = FileName(makeAbsPath(tmp)); + // FIXME we should use an ifdocstream here and a docstring for token + // below. The encoding of the log file depends on the _output_ (font) + // encoding of the TeX file (T1, TU etc.). See #10728. ifstream ifs(fn.toFilesystemEncoding().c_str()); bool fle_style = false; - static regex file_line_error(".+\\.\\D+:[0-9]+: (.+)"); + static regex const file_line_error(".+\\.\\D+:[0-9]+: (.+)"); + static regex const child_file("[^0-9]*([0-9]+[A-Za-z]*_.+\\.tex).*"); + // Flag for 'File ended while scanning' message. + // We need to wait for subsequent processing. + string wait_for_error; + string child_name; + int pnest = 0; + stack > child; + children.clear(); string token; while (getline(ifs, token)) { @@ -610,6 +681,34 @@ int LaTeX::scanLogFile(TeXErrors & terr) if (token.empty()) continue; + // Track child documents + for (size_t i = 0; i < token.length(); ++i) { + if (token[i] == '(') { + ++pnest; + size_t j = token.find('(', i + 1); + size_t len = j == string::npos + ? token.substr(i + 1).length() + : j - i - 1; + string const substr = token.substr(i + 1, len); + if (regex_match(substr, sub, child_file)) { + string const name = sub.str(1); + // Sometimes also masters have a name that matches + // (if their name starts with a number and _) + if (name != file.onlyFileName()) { + child.push(make_pair(name, pnest)); + children.push_back(name); + } + i += len; + } + } else if (token[i] == ')') { + if (!child.empty() + && child.top().second == pnest) + child.pop(); + --pnest; + } + } + child_name = child.empty() ? empty_string() : child.top().first; + if (contains(token, "file:line:error style messages enabled")) fle_style = true; @@ -628,10 +727,18 @@ int LaTeX::scanLogFile(TeXErrors & terr) && contains(token, "undefined")) { retval |= ERROR_RERUN; LYXERR(Debug::LATEX, "Force rerun."); + // package etaremune + } else if (contains(token, "Etaremune labels have changed")) { + retval |= ERROR_RERUN; + LYXERR(Debug::LATEX, "Force rerun."); } else if (contains(token, "Citation") && 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 @@ -645,7 +752,11 @@ 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")) { // at least longtable.sty and bibtopic.sty // might use this. @@ -666,7 +777,9 @@ int LaTeX::scanLogFile(TeXErrors & terr) retval |= RERUN; } } else if (prefixIs(token, "! ") - || (fle_style && regex_match(token, sub, file_line_error))) { + || (fle_style + && regex_match(token, sub, file_line_error) + && !contains(token, "pdfTeX warning"))) { // Ok, we have something that looks like a TeX Error // but what do we really have. @@ -678,22 +791,74 @@ int LaTeX::scanLogFile(TeXErrors & terr) desc = sub.str(); if (contains(token, "LaTeX Error:")) retval |= LATEX_ERROR; + + if (prefixIs(token, "! File ended while scanning")) { + 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, "! Incomplete \\if")) { + // bug 10666. 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; + int count = 0; + errstr = wait_for_error; + wait_for_error.clear(); + do { + if (!getline(ifs, tmp)) + break; + tmp = rtrim(tmp, "\r"); + errstr += "\n" + tmp; + if (++count > 5) + break; + } while (!contains(tmp, "(job aborted")); + + terr.insertError(0, + from_ascii("Emergency stop"), + from_local8bit(errstr), + child_name); + } + // get the next line string tmp; int count = 0; do { if (!getline(ifs, tmp)) break; - if (++count > 10) + tmp = rtrim(tmp, "\r"); + // 15 is somewhat arbitrarily chosen, based on practice. + // We used 10 for 14 years and increased it to 15 when we + // saw one case. + if (++count > 15) break; } while (!prefixIs(tmp, "l.")); if (prefixIs(tmp, "l.")) { // 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; @@ -702,6 +867,7 @@ int LaTeX::scanLogFile(TeXErrors & terr) string errstr(tmp, tmp.find(' ')); errstr += '\n'; getline(ifs, tmp); + tmp = rtrim(tmp, "\r"); while (!contains(errstr, "l.") && !tmp.empty() && !prefixIs(tmp, "! ") @@ -709,6 +875,7 @@ int LaTeX::scanLogFile(TeXErrors & terr) errstr += tmp; errstr += "\n"; getline(ifs, tmp); + tmp = rtrim(tmp, "\r"); } LYXERR(Debug::LATEX, "line: " << line << '\n' << "Desc: " << desc << '\n' << "Text: " << errstr); @@ -729,7 +896,8 @@ int LaTeX::scanLogFile(TeXErrors & terr) // assume here it can be wrong. terr.insertError(line, from_local8bit(desc), - from_local8bit(errstr)); + from_local8bit(errstr), + child_name); ++num_errors; } } @@ -743,12 +911,50 @@ int LaTeX::scanLogFile(TeXErrors & terr) } else if (contains(token, "Rerun to get citations")) { // Natbib seems to use this. retval |= UNDEF_CIT; - } else if (contains(token, "No pages of output")) { - // A dvi file was not created + } else if (contains(token, "No pages of output") + || contains(token, "no pages of output")) { + // No output file (e.g. the DVI or PDF) was created retval |= NO_OUTPUT; + } else if (contains(token, "Error 256 (driver return code)")) { + // This is a xdvipdfmx driver error reported by XeTeX. + // We have to check whether an output PDF file was created. + FileName pdffile = file; + pdffile.changeExtension("pdf"); + if (!pdffile.exists()) + // No output PDF file was created (see #10076) + retval |= NO_OUTPUT; } else if (contains(token, "That makes 100 errors")) { - // More than 100 errors were reprted + // More than 100 errors were reported retval |= TOO_MANY_ERRORS; + } else if (prefixIs(token, "!pdfTeX error:")) { + // otherwise we dont catch e.g.: + // !pdfTeX error: pdflatex (file feyn10): Font feyn10 at 600 not found + retval |= ERRORS; + terr.insertError(0, + from_ascii("pdfTeX Error"), + from_local8bit(token), + child_name); + } else if (!ignore_missing_glyphs + && prefixIs(token, "Missing character: There is no ") + && !contains(token, "nullfont")) { + // Warning about missing glyph in selected font + // may be dataloss (bug 9610) + // but can be ignored for 'nullfont' (bug 10394). + // as well as for ZERO WIDTH NON-JOINER (0x200C) which is + // missing in many fonts and output for ligature break (bug 10727). + // Since this error only occurs with utf8 output, we can safely assume + // that the log file is utf8-encoded + docstring const utoken = from_utf8(token); + if (!contains(utoken, 0x200C)) { + retval |= LATEX_ERROR; + terr.insertError(0, + from_ascii("Missing glyphs!"), + utoken, + child_name); + } + } else if (!wait_for_error.empty()) { + // We collect information until we know we have an error. + wait_for_error += token + '\n'; } } } @@ -785,12 +991,13 @@ bool handleFoundFile(string const & ff, DepTable & head) // (1) foundfile is an // absolute path and should // be inserted. - FileName absname(foundfile); - if (absname.isAbsolute()) { + FileName absname; + if (FileName::isAbsolute(foundfile)) { LYXERR(Debug::DEPEND, "AbsolutePath file: " << foundfile); // On initial insert we want to do the update at once // since this file cannot be a file generated by // the latex run. + absname.set(foundfile); if (!insertIfExists(absname, head)) { // check for spaces string strippedfile = foundfile; @@ -803,8 +1010,7 @@ bool handleFoundFile(string const & ff, DepTable & head) return true; // strip off part after last space and try again string tmp = strippedfile; - string const stripoff = - rsplit(tmp, strippedfile, ' '); + rsplit(tmp, strippedfile, ' '); absname.set(strippedfile); if (insertIfExists(absname, head)) return true; @@ -812,7 +1018,7 @@ bool handleFoundFile(string const & ff, DepTable & head) } } - string onlyfile = onlyFilename(foundfile); + string onlyfile = onlyFileName(foundfile); absname = makeAbsPath(onlyfile); // check for spaces @@ -829,10 +1035,9 @@ bool handleFoundFile(string const & ff, DepTable & head) break; // strip off part after last space and try again string strippedfile; - string const stripoff = - rsplit(foundfile, strippedfile, ' '); + rsplit(foundfile, strippedfile, ' '); foundfile = strippedfile; - onlyfile = onlyFilename(strippedfile); + onlyfile = onlyFileName(strippedfile); absname = makeAbsPath(onlyfile); } } @@ -864,8 +1069,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; @@ -873,7 +1080,79 @@ bool checkLineBreak(string const & ff, DepTable & head) return handleFoundFile(ff, head); } -} // anon namespace + +int iterateLine(string const & token, regex const & reg, string const & opening, + 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 (what.str(2) == opening) { + // if we have another opening bracket, + // we might have a nested file chain + // as is (file.ext (subfile.ext)) + fragment = !handleFoundFile(rtrim(what.str(1)), head); + // decrease first position by one in order to + // consider the opening delimiter on next iteration + if (first > token.begin()) + --first; + } 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; +} + +} // namespace void LaTeX::deplog(DepTable & head) @@ -883,7 +1162,7 @@ void LaTeX::deplog(DepTable & head) // entered into the dependency file. string const logfile = - onlyFilename(changeExtension(file.absFilename(), ".log")); + onlyFileName(changeExtension(file.absFileName(), ".log")); static regex const reg1("File: (.+).*"); static regex const reg2("No file (.+)(.).*"); @@ -893,8 +1172,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 @@ -904,6 +1181,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); @@ -911,10 +1191,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 @@ -928,6 +1210,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 @@ -957,106 +1243,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 succesfully 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( - file.absFilename(), ".toc")), head); + fragment = !handleFoundFile(onlyFileName(changeExtension( + file.absFileName(), ".toc")), head); else // not found, but we won't check further - // pretend we've been succesfully 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 succesfully 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 @@ -1069,4 +1323,73 @@ void LaTeX::deplog(DepTable & head) } +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|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; + // FIXME UNICODE: We assume that citation keys and filenames + // 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(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; +} + + } // namespace lyx