lyx_mirror/src/LaTeX.cpp
Jürgen Spitzmüller d7266ac919 In recent biber version, the log message that tells us which bib files are used has been changed. Adapt the parser, so that bib files are tracked again.
This should go to BRANCH as well

git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@40542 a592a061-630c-0410-9148-cb99ea01b6c8
2011-12-23 09:24:31 +00:00

1219 lines
36 KiB
C++
Raw Blame History

/**
* \file LaTeX.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Alfredo Braunstein
* \author Lars Gullik Bjønnes
* \author Jean-Marc Lasgouttes
* \author Angus Leeming
* \author Dekel Tsur
* \author Jürgen Spitzmüller
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "BufferList.h"
#include "LaTeX.h"
#include "LyXRC.h"
#include "DepTable.h"
#include "support/debug.h"
#include "support/convert.h"
#include "support/FileName.h"
#include "support/filetools.h"
#include "support/gettext.h"
#include "support/lstrings.h"
#include "support/Systemcall.h"
#include "support/os.h"
#include "support/regex.h"
#include <fstream>
#include <stack>
using namespace std;
using namespace lyx::support;
namespace lyx {
namespace os = support::os;
// TODO: in no particular order
// - get rid of the call to
// BufferList::updateIncludedTeXfiles, this should either
// be done before calling LaTeX::funcs or in a completely
// 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 {
docstring runMessage(unsigned int count)
{
return bformat(_("Waiting for LaTeX run number %1$d"), count);
}
} // anon namespace
/*
* CLASS TEXERRORS
*/
void TeXErrors::insertError(int line, docstring const & error_desc,
docstring const & error_text,
string const & child_name)
{
Error newerr(line, error_desc, error_text, child_name);
errors.push_back(newerr);
}
bool operator==(AuxInfo const & a, AuxInfo const & o)
{
return a.aux_file == o.aux_file
&& a.citations == o.citations
&& a.databases == o.databases
&& a.styles == o.styles;
}
bool operator!=(AuxInfo const & a, AuxInfo const & o)
{
return !(a == o);
}
/*
* CLASS LaTeX
*/
LaTeX::LaTeX(string const & latex, OutputParams const & rp,
FileName const & f, string const & p)
: cmd(latex), file(f), path(p), runparams(rp)
{
num_errors = 0;
if (prefixIs(cmd, "pdf")) { // Do we use pdflatex ?
depfile = FileName(file.absFileName() + ".dep-pdf");
output_file =
FileName(changeExtension(file.absFileName(), ".pdf"));
} else {
depfile = FileName(file.absFileName() + ".dep");
output_file =
FileName(changeExtension(file.absFileName(), ".dvi"));
}
}
void LaTeX::deleteFilesOnError() const
{
// currently just a dummy function.
// What files do we have to delete?
// This will at least make latex do all the runs
depfile.removeFile();
// but the reason for the error might be in a generated file...
// bibtex file
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"));
ind.removeFile();
// nomencl file
FileName const nls(changeExtension(file.absFileName(), ".nls"));
nls.removeFile();
// nomencl file (old version of the package)
FileName const gls(changeExtension(file.absFileName(), ".gls"));
gls.removeFile();
// Also remove the aux file
FileName const aux(changeExtension(file.absFileName(), ".aux"));
aux.removeFile();
}
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) one time
// each time the .tex file changes.
{
int scanres = NO_ERRORS;
unsigned int count = 0; // number of times run
num_errors = 0; // just to make sure.
unsigned int const MAX_RUN = 6;
DepTable head; // empty head
bool rerun = false; // rerun requested
// The class LaTeX does not know the temp path.
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
// check if any of the files mentioned in it have
// changed (done using a checksum).
// -> if changed:
// run latex once and
// remake the dependency file
// -> if not changed:
// just return there is nothing to do for us.
// ->if it doesn't exist
// make it and
// run latex once (we need to run latex once anyway) and
// remake the dependency file.
//
bool had_depfile = depfile.exists();
bool run_bibtex = false;
FileName const aux_file(changeExtension(file.absFileName(), "aux"));
if (had_depfile) {
LYXERR(Debug::DEPEND, "Dependency file exists");
// Read the dep file:
had_depfile = head.read(depfile);
}
if (had_depfile) {
// Update the checksums
head.update();
// Can't just check if anything has changed because it might
// have aborted on error last time... in which cas we need
// to re-run latex and collect the error messages
// (even if they are the same).
if (!output_file.exists()) {
LYXERR(Debug::DEPEND,
"re-running LaTeX because output file doesn't exist.");
} else if (!head.sumchange()) {
LYXERR(Debug::DEPEND, "return no_change");
return NO_CHANGE;
} else {
LYXERR(Debug::DEPEND, "Dependency file has changed");
}
if (head.extchanged(".bib") || head.extchanged(".bst"))
run_bibtex = true;
} else
LYXERR(Debug::DEPEND,
"Dependency file does not exist, or has wrong format");
/// We scan the aux file even when had_depfile = false,
/// because we can run pdflatex on the file after running latex on it,
/// in which case we will not need to run bibtex again.
vector<AuxInfo> bibtex_info_old;
if (!run_bibtex)
bibtex_info_old = scanAuxFiles(aux_file);
++count;
LYXERR(Debug::LATEX, "Run #" << count);
message(runMessage(count));
startscript();
scanres = scanLogFile(terr);
if (scanres & ERROR_RERUN) {
LYXERR(Debug::LATEX, "Rerunning LaTeX");
startscript();
scanres = scanLogFile(terr);
}
if (scanres & ERRORS) {
deleteFilesOnError();
return scanres; // return on error
}
vector<AuxInfo> const bibtex_info = scanAuxFiles(aux_file);
if (!run_bibtex && bibtex_info_old != bibtex_info)
run_bibtex = true;
// update the dependencies.
deplog(head); // reads the latex log
head.update();
// 1
// At this point we must run external programs if needed.
// makeindex will be run if a .idx file changed or was generated.
// And if there were undefined citations or changes in references
// the .aux file is checked for signs of bibtex. Bibtex is then run
// if needed.
// 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"));
rerun = idxfile.exists() && idxfile.isFileEmpty();
// run makeindex
if (head.haschanged(idxfile)) {
// no checks for now
LYXERR(Debug::LATEX, "Running MakeIndex.");
message(_("Running Index Processor."));
// onlyFileName() is needed for cygwin
rerun |= runMakeIndex(onlyFileName(idxfile.absFileName()),
runparams);
}
FileName const nlofile(changeExtension(file.absFileName(), ".nlo"));
if (head.haschanged(nlofile))
rerun |= runMakeIndexNomencl(file, ".nlo", ".nls");
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) {
// Here we must scan the .aux file and look for
// "\bibdata" and/or "\bibstyle". If one of those
// tags is found -> run bibtex and set rerun = true;
// no checks for now
LYXERR(Debug::LATEX, "Running BibTeX.");
message(_("Running BibTeX."));
updateBibtexDependencies(head, bibtex_info);
rerun |= runBibTeX(bibtex_info, runparams);
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);
}
} 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);
}
// 2
// we know on this point that latex has been run once (or we just
// returned) and the question now is to decide if we need to run
// it any more. This is done by asking if any of the files in the
// dependency file has changed. (remember that the checksum for
// a given file is reported to have changed if it just was created)
// -> if changed or rerun == true:
// run latex once more and
// update the dependency structure
// -> if not changed:
// we do nothing at this point
//
if (rerun || head.sumchange()) {
rerun = false;
++count;
LYXERR(Debug::DEPEND, "Dep. file has changed or rerun requested");
LYXERR(Debug::LATEX, "Run #" << count);
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
head.update();
} else {
LYXERR(Debug::DEPEND, "Dep. file has NOT changed");
}
// 3
// rerun bibtex?
// Complex bibliography packages such as Biblatex require
// an additional bibtex cycle sometimes.
if (scanres & UNDEF_CIT) {
// Here we must scan the .aux file and look for
// "\bibdata" and/or "\bibstyle". If one of those
// tags is found -> run bibtex and set rerun = true;
// no checks for now
LYXERR(Debug::LATEX, "Running BibTeX.");
message(_("Running BibTeX."));
updateBibtexDependencies(head, bibtex_info);
rerun |= runBibTeX(bibtex_info, runparams);
}
// 4
// The inclusion of files generated by external programs such as
// makeindex or bibtex might have done changes to pagenumbering,
// etc. And because of this we must run the external programs
// again to make sure everything is redone correctly.
// Also there should be no need to run the external programs any
// more after this.
// run makeindex if the <file>.idx has changed or was generated.
if (head.haschanged(idxfile)) {
// 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);
}
// I am not pretty sure if need this twice.
if (head.haschanged(nlofile))
rerun |= runMakeIndexNomencl(file, ".nlo", ".nls");
if (head.haschanged(glofile))
rerun |= runMakeIndexNomencl(file, ".glo", ".gls");
// 5
// we will only run latex more if the log file asks for it.
// or if the sumchange() is true.
// -> rerun asked for:
// run latex and
// remake the dependency file
// goto 2 or return if max runs are reached.
// -> rerun not asked for:
// just return (fall out of bottom of func)
//
while ((head.sumchange() || rerun || (scanres & RERUN))
&& count < MAX_RUN) {
// Yes rerun until message goes away, or until
// MAX_RUNS are reached.
rerun = false;
++count;
LYXERR(Debug::LATEX, "Run #" << count);
message(runMessage(count));
startscript();
scanres = scanLogFile(terr);
if (scanres & ERRORS) {
deleteFilesOnError();
return scanres; // return on error
}
// keep this updated
head.update();
}
// Write the dependencies to file.
head.write(depfile);
LYXERR(Debug::LATEX, "Done.");
return scanres;
}
int LaTeX::startscript()
{
// onlyFileName() is needed for cygwin
string tmp = cmd + ' '
+ quoteName(onlyFileName(file.toFilesystemEncoding()))
+ " > " + os::nulldev();
Systemcall one;
return one.startscript(Systemcall::Wait, tmp, path);
}
bool LaTeX::runMakeIndex(string const & f, OutputParams const & runparams,
string const & params)
{
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);
tmp = subst(tmp, "$$lang", runparams.document_language);
if (runparams.use_indices) {
tmp = lyxrc.splitindex_command + " -m " + quoteName(tmp);
LYXERR(Debug::LATEX,
"Multiple indices. Using splitindex command: " << tmp);
}
tmp += ' ';
tmp += quoteName(f);
tmp += params;
Systemcall one;
one.startscript(Systemcall::Wait, tmp, path);
return true;
}
bool LaTeX::runMakeIndexNomencl(FileName const & file,
string const & nlo, string const & nls)
{
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)));
tmp += " -o "
+ onlyFileName(changeExtension(file.toFilesystemEncoding(), nls));
Systemcall one;
one.startscript(Systemcall::Wait, tmp, path);
return true;
}
vector<AuxInfo> const
LaTeX::scanAuxFiles(FileName const & file)
{
vector<AuxInfo> result;
result.push_back(scanAuxFile(file));
string const basename = removeExtension(file.absFileName());
for (int i = 1; i < 1000; ++i) {
FileName const file2(basename
+ '.' + convert<string>(i)
+ ".aux");
if (!file2.exists())
break;
result.push_back(scanAuxFile(file2));
}
return result;
}
AuxInfo const LaTeX::scanAuxFile(FileName const & file)
{
AuxInfo result;
result.aux_file = file;
scanAuxFile(file, result);
return result;
}
void LaTeX::scanAuxFile(FileName const & file, AuxInfo & aux_info)
{
LYXERR(Debug::LATEX, "Scanning aux file: " << file);
ifstream ifs(file.toFilesystemEncoding().c_str());
string token;
static regex const reg1("\\\\citation\\{([^}]+)\\}");
static regex const reg2("\\\\bibdata\\{([^}]+)\\}");
static regex const reg3("\\\\bibstyle\\{([^}]+)\\}");
static regex const reg4("\\\\@input\\{([^}]+)\\}");
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(1);
while (!data.empty()) {
string citation;
data = split(data, citation, ',');
LYXERR(Debug::LATEX, "Citation: " << citation);
aux_info.citations.insert(citation);
}
} else if (regex_match(token, sub, reg2)) {
string data = sub.str(1);
// data is now all the bib files separated by ','
// get them one by one and pass them to the helper
while (!data.empty()) {
string database;
data = split(data, database, ',');
database = changeExtension(database, "bib");
LYXERR(Debug::LATEX, "BibTeX database: `" << database << '\'');
aux_info.databases.insert(database);
}
} else if (regex_match(token, sub, reg3)) {
string style = sub.str(1);
// token is now the style file
// pass it to the helper
style = changeExtension(style, "bst");
LYXERR(Debug::LATEX, "BibTeX style: `" << style << '\'');
aux_info.styles.insert(style);
} else if (regex_match(token, sub, reg4)) {
string const file2 = sub.str(1);
scanAuxFile(makeAbsPath(file2), aux_info);
}
}
}
void LaTeX::updateBibtexDependencies(DepTable & dep,
vector<AuxInfo> const & bibtex_info)
{
// Since a run of Bibtex mandates more latex runs it is ok to
// remove all ".bib" and ".bst" files.
dep.remove_files_with_extension(".bib");
dep.remove_files_with_extension(".bst");
//string aux = OnlyFileName(ChangeExtension(file, ".aux"));
for (vector<AuxInfo>::const_iterator it = bibtex_info.begin();
it != bibtex_info.end(); ++it) {
for (set<string>::const_iterator it2 = it->databases.begin();
it2 != it->databases.end(); ++it2) {
FileName const file = findtexfile(*it2, "bib");
if (!file.empty())
dep.insert(file, true);
}
for (set<string>::const_iterator it2 = it->styles.begin();
it2 != it->styles.end(); ++it2) {
FileName const file = findtexfile(*it2, "bst");
if (!file.empty())
dep.insert(file, true);
}
}
// biber writes nothing into the aux file.
// Instead, we have to scan the blg file
if (biber) {
scanBlgFile(dep);
}
}
bool LaTeX::runBibTeX(vector<AuxInfo> const & bibtex_info,
OutputParams const & runparams)
{
bool result = false;
for (vector<AuxInfo>::const_iterator it = bibtex_info.begin();
it != bibtex_info.end(); ++it) {
if (!biber && it->databases.empty())
continue;
result = true;
string tmp = runparams.use_japanese ?
lyxrc.jbibtex_command : lyxrc.bibtex_command;
if (!runparams.bibtex_command.empty())
tmp = runparams.bibtex_command;
tmp += " ";
// onlyFileName() is needed for cygwin
tmp += quoteName(onlyFileName(removeExtension(
it->aux_file.absFileName())));
Systemcall one;
one.startscript(Systemcall::Wait, tmp, path);
}
// Return whether bibtex was run
return result;
}
int LaTeX::scanLogFile(TeXErrors & terr)
{
int last_line = -1;
int line_count = 1;
int retval = NO_ERRORS;
string tmp =
onlyFileName(changeExtension(file.absFileName(), ".log"));
LYXERR(Debug::LATEX, "Log file: " << tmp);
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).*");
// 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 <pair<string, int> > child;
string token;
while (getline(ifs, token)) {
// MikTeX sometimes inserts \0 in the log file. They can't be
// removed directly with the existing string utility
// functions, so convert them first to \r, and remove all
// \r's afterwards, since we need to remove them anyway.
token = subst(token, '\0', '\r');
token = subst(token, "\r", "");
smatch sub;
LYXERR(Debug::LATEX, "Log line: " << token);
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;
if (regex_match(token.substr(i + 1, len),
sub, child_file)) {
string const name = sub.str(1);
child.push(make_pair(name, pnest));
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;
if (prefixIs(token, "LaTeX Warning:") ||
prefixIs(token, "! pdfTeX warning")) {
// Here shall we handle different
// types of warnings
retval |= LATEX_WARNING;
LYXERR(Debug::LATEX, "LaTeX Warning.");
if (contains(token, "Rerun to get cross-references")) {
retval |= RERUN;
LYXERR(Debug::LATEX, "We should rerun.");
// package clefval needs 2 latex runs before bibtex
} else if (contains(token, "Value of")
&& contains(token, "on page")
&& 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 (prefixIs(token, "Package")) {
// Package warnings
retval |= PACKAGE_WARNING;
if (contains(token, "natbib Warning:")) {
// Natbib warnings
if (contains(token, "Citation")
&& contains(token, "on page")
&& contains(token, "undefined")) {
retval |= UNDEF_CIT;
}
} 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.
LYXERR(Debug::LATEX, "We should rerun.");
retval |= RERUN;
}
} else if (prefixIs(token, "LETTRE WARNING:")) {
if (contains(token, "veuillez recompiler")) {
// lettre.cls
LYXERR(Debug::LATEX, "We should rerun.");
retval |= RERUN;
}
} else if (token[0] == '(') {
if (contains(token, "Rerun LaTeX") ||
contains(token, "Rerun to get")) {
// Used by natbib
LYXERR(Debug::LATEX, "We should rerun.");
retval |= RERUN;
}
} else if (prefixIs(token, "! ")
|| (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.
// Just get the error description:
string desc;
if (prefixIs(token, "! "))
desc = string(token, 2);
else if (fle_style)
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, "! 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;
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_local8bit("Emergency stop"),
from_local8bit(errstr),
child_name);
}
// get the next line
string tmp;
int count = 0;
do {
if (!getline(ifs, tmp))
break;
tmp = rtrim(tmp, "\r");
if (++count > 10)
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"))
retval |= ERROR_RERUN;
// get the line number:
int line = 0;
sscanf(tmp.c_str(), "l.%d", &line);
// get the rest of the message:
string errstr(tmp, tmp.find(' '));
errstr += '\n';
getline(ifs, tmp);
tmp = rtrim(tmp, "\r");
while (!contains(errstr, "l.")
&& !tmp.empty()
&& !prefixIs(tmp, "! ")
&& !contains(tmp, "(job aborted")) {
errstr += tmp;
errstr += "\n";
getline(ifs, tmp);
tmp = rtrim(tmp, "\r");
}
LYXERR(Debug::LATEX, "line: " << line << '\n'
<< "Desc: " << desc << '\n' << "Text: " << errstr);
if (line == last_line)
++line_count;
else {
line_count = 1;
last_line = line;
}
if (line_count <= 5) {
// FIXME UNICODE
// We have no idea what the encoding of
// the log file is.
// It seems that the output from the
// latex compiler itself is pure ASCII,
// but it can include bits from the
// document, so whatever encoding we
// assume here it can be wrong.
terr.insertError(line,
from_local8bit(desc),
from_local8bit(errstr),
child_name);
++num_errors;
}
}
} else {
// information messages, TeX warnings and other
// warnings we have not caught earlier.
if (prefixIs(token, "Overfull ")) {
retval |= TEX_WARNING;
} else if (prefixIs(token, "Underfull ")) {
retval |= TEX_WARNING;
} 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
retval |= NO_OUTPUT;
} else if (contains(token, "That makes 100 errors")) {
// More than 100 errors were reprted
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_local8bit("pdfTeX Error"),
from_local8bit(token),
child_name);
}
}
}
LYXERR(Debug::LATEX, "Log line: " << token);
return retval;
}
namespace {
bool insertIfExists(FileName const & absname, DepTable & head)
{
if (absname.exists() && !absname.isDirectory()) {
head.insert(absname, true);
return true;
}
return false;
}
bool handleFoundFile(string const & ff, DepTable & head)
{
// convert from native os path to unix path
string foundfile = os::internal_path(trim(ff));
LYXERR(Debug::DEPEND, "Found file: " << foundfile);
// Ok now we found a file.
// Now we should make sure that this is a file that we can
// access through the normal paths.
// We will not try any fancy search methods to
// find the file.
// (1) foundfile is an
// absolute path and should
// be inserted.
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;
while (contains(strippedfile, " ")) {
// files with spaces are often enclosed in quotation
// marks; those have to be removed
string unquoted = subst(strippedfile, "\"", "");
absname.set(unquoted);
if (insertIfExists(absname, head))
return true;
// strip off part after last space and try again
string tmp = strippedfile;
string const stripoff =
rsplit(tmp, strippedfile, ' ');
absname.set(strippedfile);
if (insertIfExists(absname, head))
return true;
}
}
}
string onlyfile = onlyFileName(foundfile);
absname = makeAbsPath(onlyfile);
// check for spaces
while (contains(foundfile, ' ')) {
if (absname.exists())
// everything o.k.
break;
else {
// files with spaces are often enclosed in quotation
// marks; those have to be removed
string unquoted = subst(foundfile, "\"", "");
absname = makeAbsPath(unquoted);
if (absname.exists())
break;
// strip off part after last space and try again
string strippedfile;
string const stripoff =
rsplit(foundfile, strippedfile, ' ');
foundfile = strippedfile;
onlyfile = onlyFileName(strippedfile);
absname = makeAbsPath(onlyfile);
}
}
// (2) foundfile is in the tmpdir
// insert it into head
if (absname.exists() && !absname.isDirectory()) {
// FIXME: This regex contained glo, but glo is used by the old
// version of nomencl.sty. Do we need to put it back?
static regex const unwanted("^.*\\.(aux|log|dvi|bbl|ind)$");
if (regex_match(onlyfile, unwanted)) {
LYXERR(Debug::DEPEND, "We don't want " << onlyfile
<< " in the dep file");
} else if (suffixIs(onlyfile, ".tex")) {
// This is a tex file generated by LyX
// and latex is not likely to change this
// during its runs.
LYXERR(Debug::DEPEND, "Tmpdir TeX file: " << onlyfile);
head.insert(absname, true);
} else {
LYXERR(Debug::DEPEND, "In tmpdir file:" << onlyfile);
head.insert(absname);
}
return true;
} else {
LYXERR(Debug::DEPEND, "Not a file or we are unable to find it.");
return false;
}
}
bool checkLineBreak(string const & ff, DepTable & head)
{
if (!contains(ff, '.'))
return false;
// if we have a dot, we let handleFoundFile decide
return handleFoundFile(ff, head);
}
} // anon namespace
void LaTeX::deplog(DepTable & head)
{
// This function reads the LaTeX log file end extracts all the
// external files used by the LaTeX run. The files are then
// entered into the dependency file.
string const logfile =
onlyFileName(changeExtension(file.absFileName(), ".log"));
static regex const reg1("File: (.+).*");
static regex const reg2("No file (.+)(.).*");
static regex const reg3("\\\\openout[0-9]+.*=.*`(.+)(..).*");
// If an index should be created, MikTex does not write a line like
// \openout# = 'sample.idx'.
// 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
// \openout# = `sample.toc'.
// but only a line like this into the log:
// \tf@toc=\write#
// 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.*");
static regex const reg6(".*\\([^)]+.*");
FileName const fn = makeAbsPath(logfile);
ifstream ifs(fn.toFilesystemEncoding().c_str());
string lastline;
while (ifs) {
// Ok, the scanning of files here is not sufficient.
// Sometimes files are named by "File:<3A> xxx" only
// So I think we should use some regexps to find files instead.
// Note: all file names and paths might contains spaces.
bool found_file = false;
string token;
getline(ifs, token);
// MikTeX sometimes inserts \0 in the log file. They can't be
// removed directly with the existing string utility
// functions, so convert them first to \r, and remove all
// \r's afterwards, since we need to remove them anyway.
token = subst(token, '\0', '\r');
token = subst(token, "\r", "");
if (token.empty() || token == ")") {
lastline = string();
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
// that there is no continued filename
if (!lastline.empty()) {
static regex const package_info("Package \\w+ Info: .*");
static regex const package_warning("Package \\w+ Warning: .*");
if (prefixIs(token, "File:") || prefixIs(token, "(Font)")
|| prefixIs(token, "Package:")
|| prefixIs(token, "Language:")
|| prefixIs(token, "LaTeX Info:")
|| prefixIs(token, "LaTeX Font Info:")
|| prefixIs(token, "\\openout[")
|| prefixIs(token, "))")
|| regex_match(token, package_info)
|| regex_match(token, package_warning))
lastline = string();
}
if (!lastline.empty())
// probably a continued filename from last line
token = lastline + token;
if (token.length() > 255) {
// string too long. Cut off.
token.erase(0, token.length() - 251);
}
smatch sub;
// (1) "File: file.ext"
if (regex_match(token, sub, reg1)) {
// check for dot
found_file = checkLineBreak(sub.str(1), head);
// However, ...
if (suffixIs(token, ")"))
// no line break for sure
// pretend we've been successfully searching
found_file = true;
// (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);
else
// we suspect a line break
found_file = false;
// (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);
else
// probable line break
found_file = false;
// (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) ||
regex_match(token, sub, regoldnomencl))
// check for dot
found_file = checkLineBreak(sub.str(1), head);
// (7) "\tf@toc=\write<nr>" (for MikTeX)
else if (regex_match(token, sub, miktexTocReg))
found_file = 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;
// (8) "(file.ext"
// note that 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;
}
}
}
if (!found_file)
// probable linebreak:
// save this line
lastline = token;
else
// no linebreak: reset
lastline = string();
}
// Make sure that the main .tex file is in the dependency file.
head.insert(file, true);
}
void LaTeX::scanBlgFile(DepTable & dep)
{
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) '([^']+).*");
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);
}
}
}
}
} // namespace lyx