You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kile/src/kile/latexoutputfilter.cpp

660 lines
20 KiB

/***************************************************************************
begin : Die Sep 16 2003
copyright : (C) 2003 by Jeroen Wijnhout
email : wijnhout@science.uva.nl
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
// 2007-03-12 dani
// - use KileDocument::Extensions
#include "latexoutputfilter.h"
#include <tqfile.h>
#include <tqregexp.h>
#include <tqfileinfo.h>
#include "kiledebug.h"
#include <ktextedit.h>
#include <tdelocale.h>
#include "kiletool_enums.h"
using namespace std;
LatexOutputFilter::LatexOutputFilter(LatexOutputInfoArray* LatexOutputInfoArray, KileDocument::Extensions *extensions) :
m_nErrors(0),
m_nWarnings(0),
m_nBadBoxes(0),
m_InfoList(LatexOutputInfoArray),
m_extensions(extensions)
{
}
LatexOutputFilter::~ LatexOutputFilter()
{
}
bool LatexOutputFilter::OnPreCreate()
{
m_nErrors = 0;
m_nWarnings = 0;
m_nBadBoxes = 0;
return true;
}
bool LatexOutputFilter::fileExists(const TQString & name)
{
static TQFileInfo fi;
if (name[0] == '/' )
{
fi.setFile(name);
if ( fi.exists() && !fi.isDir() ) return true;
else return false;
}
fi.setFile(path() + '/' + name);
if ( fi.exists() && !fi.isDir() ) return true;
fi.setFile(path() + '/' + name + m_extensions->latexDocumentDefault() );
if ( fi.exists() && !fi.isDir() ) return true;
// try to determine the LaTeX source file
TQStringList extlist = TQStringList::split(" ", m_extensions->latexDocuments() );
for ( TQStringList::Iterator it=extlist.begin(); it!=extlist.end(); ++it )
{
fi.setFile( path() + '/' + name + (*it) );
if ( fi.exists() && !fi.isDir() )
return true;
}
return false;
}
// There are basically two ways to detect the current file TeX is processing:
// 1) Use \Input (i.c.w. srctex.sty or srcltx.sty) and \include exclusively. This will
// cause (La)TeX to print the line ":<+ filename" in the log file when opening a file,
// ":<-" when closing a file. Filenames pushed on the stack in this mode are marked
// as reliable.
//
// 2) Since people will probably also use the \input command, we also have to be
// to detect the old-fashioned way. TeX prints '(filename' when opening a file and a ')'
// when closing one. It is impossible to detect this with 100% certainty (TeX prints many messages
// and even text (a context) from the TeX source file, there could be unbalanced parentheses),
// so we use a heuristic algorithm. In heuristic mode a ')' will only be considered as a signal that
// TeX is closing a file if the top of the stack is not marked as "reliable".
// Also, when scanning for a TeX error linenumber (which sometimes causes a context to be printed
// to the log-file), updateFileStack is not called, helping not to pick up unbalanced parentheses
// from the context.
void LatexOutputFilter::updateFileStack(const TQString &strLine, short & dwCookie)
{
//KILE_DEBUG() << "==LatexOutputFilter::updateFileStack()================" << endl;
static TQString strPartialFileName;
switch (dwCookie)
{
//we're looking for a filename
case Start : case FileNameHeuristic :
//TeX is opening a file
if ( strLine.startsWith(":<+ ") )
{
// KILE_DEBUG() << "filename detected" << endl;
//grab the filename, it might be a partial name (i.e. continued on the next line)
strPartialFileName = strLine.mid(4).stripWhiteSpace();
//change the cookie so we remember we aren't sure the filename is complete
dwCookie = FileName;
}
//TeX closed a file
else if ( strLine.startsWith(":<-") )
{
// KILE_DEBUG() << "\tpopping : " << m_stackFile.top().file() << endl;
m_stackFile.pop();
dwCookie = Start;
}
else
//fallback to the heuristic detection of filenames
updateFileStackHeuristic(strLine, dwCookie);
break;
case FileName :
//The partial filename was followed by '(', this means that TeX is signalling it is
//opening the file. We are sure the filename is complete now. Don't call updateFileStackHeuristic
//since we don't want the filename on the stack twice.
if ( strLine.startsWith("(") || strLine.startsWith("\\openout") )
{
//push the filename on the stack and mark it as 'reliable'
m_stackFile.push(LOFStackItem(strPartialFileName, true));
// KILE_DEBUG() << "\tpushed : " << strPartialFileName << endl;
strPartialFileName = TQString();
dwCookie = Start;
}
//The partial filename was followed by an TeX error, meaning the file doesn't exist.
//Don't push it on the stack, instead try to detect the error.
else if ( strLine.startsWith("!") )
{
// KILE_DEBUG() << "oops!" << endl;
dwCookie = Start;
strPartialFileName = TQString();
detectError(strLine, dwCookie);
}
else if ( strLine.startsWith("No file") )
{
// KILE_DEBUG() << "No file: " << strLine << endl;
dwCookie = Start;
strPartialFileName = TQString();
detectWarning(strLine, dwCookie);
}
//Partial filename still isn't complete.
else
{
// KILE_DEBUG() << "\tpartial file name, adding" << endl;
strPartialFileName = strPartialFileName + strLine.stripWhiteSpace();
}
break;
default : break;
}
}
void LatexOutputFilter::updateFileStackHeuristic(const TQString &strLine, short & dwCookie)
{
//KILE_DEBUG() << "==LatexOutputFilter::updateFileStackHeuristic()================" << endl;
static TQString strPartialFileName;
bool expectFileName = (dwCookie == FileNameHeuristic);
int index = 0;
// handle special case (bug fix for 101810)
if(expectFileName && strLine[0] == ')') {
m_stackFile.push(LOFStackItem(strPartialFileName));
expectFileName = false;
dwCookie = Start;
}
//scan for parentheses and grab filenames
for (unsigned int i = 0; i < strLine.length(); ++i) {
//We're expecting a filename. If a filename really ends at this position one of the following must be true:
// 1) Next character is a space (indicating the end of a filename (yes, there can't spaces in the
// path, this is a TeX limitation).
// 2) We're at the end of the line, the filename is probably continued on the next line.
// 3) The TeX was closed already, signalled by the ')'.
if(expectFileName && (i+1 == strLine.length() || strLine[i+1].isSpace() || strLine[i+1] == ')')) {
//update the partial filename
strPartialFileName = strPartialFileName + strLine.mid(index, i-index + 1);
//FIXME: improve these heuristics
if (strLine[i+1].isSpace() || ( (i < 78) && (i+1 == strLine.length())) ||
strLine[i+1] == ')' ||
fileExists(strPartialFileName)) {
m_stackFile.push(LOFStackItem(strPartialFileName));
// KILE_DEBUG() << "\tpushed (i = " << i << " length = " << strLine.length() << "): " << strPartialFileName << endl;
expectFileName = false;
dwCookie = Start;
}
//Guess the filename is continued on the next line.
else if(i+1 == strLine.length()) {
// KILE_DEBUG() << "\tFilename spans more than one line." << endl;
dwCookie = FileNameHeuristic;
}
//bail out
else {
dwCookie = Start;
strPartialFileName = TQString();
expectFileName = false;
}
}
//TeX is opening a file
else if(strLine[i] == '(') {
//we need to extract the filename
expectFileName = true;
strPartialFileName = TQString();
dwCookie = Start;
//this is were the filename is supposed to start
index = i + 1;
}
//TeX is closing a file
else if(strLine[i] == ')') {
// KILE_DEBUG() << "\tpopping : " << m_stackFile.top().file() << endl;
//If this filename was pushed on the stack by the reliable ":<+-" method, don't pop
//a ":<-" will follow. This helps in preventing unbalanced ')' from popping filenames
//from the stack too soon.
if ( ! m_stackFile.top().reliable() )
m_stackFile.pop();
else
KILE_DEBUG() << "\t\toh no, forget about it!" << endl;
}
}
}
void LatexOutputFilter::flushCurrentItem()
{
//KILE_DEBUG() << "==LatexOutputFilter::flushCurrentItem()================" << endl;
int nItemType = m_currentItem.type();
while ( (! fileExists(m_stackFile.top().file()) ) && (m_stackFile.count() > 1) )
m_stackFile.pop();
m_currentItem.setSource(m_stackFile.top().file());
switch (nItemType)
{
case itmError:
++m_nErrors;
m_InfoList->push_back(m_currentItem);
//KILE_DEBUG() << "Flushing Error in" << m_currentItem.source() << "@" << m_currentItem.sourceLine() << " reported in line " << m_currentItem.outputLine() << endl;
break;
case itmWarning:
++m_nWarnings;
m_InfoList->push_back(m_currentItem);
//KILE_DEBUG() << "Flushing Warning in " << m_currentItem.source() << "@" << m_currentItem.sourceLine() << " reported in line " << m_currentItem.outputLine() << endl;
break;
case itmBadBox:
++m_nBadBoxes;
m_InfoList->push_back(m_currentItem);
//KILE_DEBUG() << "Flushing BadBox in " << m_currentItem.source() << "@" << m_currentItem.sourceLine() << " reported in line " << m_currentItem.outputLine() << endl;
break;
default: break;
}
m_currentItem.Clear();
}
bool LatexOutputFilter::detectError(const TQString & strLine, short &dwCookie)
{
//KILE_DEBUG() << "==LatexOutputFilter::detectError(" << strLine.length() << ")================" << endl;
bool found = false, flush = false;
static TQRegExp reLaTeXError("^! LaTeX Error: (.*)$", false);
static TQRegExp rePDFLaTeXError("^Error: pdflatex (.*)$", false);
static TQRegExp reTeXError("^! (.*)\\.$");
static TQRegExp reLineNumber("^l\\.([0-9]+)(.*)");
switch (dwCookie)
{
case Start :
if (reLaTeXError.search(strLine) != -1)
{
//KILE_DEBUG() << "\tError : " << reLaTeXError.cap(1) << endl;
m_currentItem.setMessage(reLaTeXError.cap(1));
found = true;
}
else if (rePDFLaTeXError.search(strLine) != -1)
{
//KILE_DEBUG() << "\tError : " << rePDFLaTeXError.cap(1) << endl;
m_currentItem.setMessage(rePDFLaTeXError.cap(1));
found = true;
}
else if (reTeXError.search(strLine) != -1 )
{
//KILE_DEBUG() << "\tError : " << reTeXError.cap(1) << endl;
m_currentItem.setMessage(reTeXError.cap(1));
found = true;
}
if (found)
{
dwCookie = strLine.endsWith(".") ? LineNumber : Error;
m_currentItem.setOutputLine(GetCurrentOutputLine());
}
break;
case Error :
//KILE_DEBUG() << "\tError (cont'd): " << strLine << endl;
if ( strLine.endsWith(".") )
{
dwCookie = LineNumber;
m_currentItem.setMessage(m_currentItem.message() + strLine);
}
else if ( GetCurrentOutputLine() - m_currentItem.outputLine() > 3 )
{
kdWarning() << "\tBAILING OUT: error description spans more than three lines" << endl;
dwCookie = Start;
flush = true;
}
break;
case LineNumber :
//KILE_DEBUG() << "\tLineNumber " << endl;
if ( reLineNumber.search(strLine) != -1 )
{
dwCookie = Start;
flush = true;
//KILE_DEBUG() << "\tline number: " << reLineNumber.cap(1) << endl;
m_currentItem.setSourceLine(reLineNumber.cap(1).toInt());
m_currentItem.setMessage(m_currentItem.message() + reLineNumber.cap(2));
}
else if ( GetCurrentOutputLine() - m_currentItem.outputLine() > 10 )
{
dwCookie = Start;
flush = true;
kdWarning() << "\tBAILING OUT: did not detect a TeX line number for an error" << endl;
m_currentItem.setSourceLine(0);
}
break;
default : break;
}
if (found)
{
m_currentItem.setType(itmError);
m_currentItem.setOutputLine(GetCurrentOutputLine());
}
if (flush) flushCurrentItem();
return found;
}
bool LatexOutputFilter::detectWarning(const TQString & strLine, short &dwCookie)
{
//KILE_DEBUG() << "==LatexOutputFilter::detectWarning(" << strLine.length() << ")================" << endl;
bool found = false, flush = false;
TQString warning;
static TQRegExp reLaTeXWarning("^(((! )?(La|pdf)TeX)|Package|Class) .*Warning.*:(.*)", false);
static TQRegExp reNoFile("No file (.*)");
static TQRegExp reNoAsyFile("File .* does not exist."); // FIXME can be removed when http://sourceforge.net/tracker/index.php?func=detail&aid=1772022&group_id=120000&atid=685683 has promoted to the users
switch (dwCookie)
{
//detect the beginning of a warning
case Start :
if ( reLaTeXWarning.search(strLine) != -1 )
{
warning = reLaTeXWarning.cap(5);
//KILE_DEBUG() << "\tWarning found: " << warning << endl;
found = true;
dwCookie = Start;
m_currentItem.setMessage(warning);
m_currentItem.setOutputLine(GetCurrentOutputLine());
//do we expect a line number?
flush = detectLaTeXLineNumber(warning, dwCookie, strLine.length());
}
else if ( reNoFile.search(strLine) != -1 )
{
found = true;
flush = true;
m_currentItem.setSourceLine(0);
m_currentItem.setMessage(reNoFile.cap(0));
m_currentItem.setOutputLine(GetCurrentOutputLine());
}
else if ( reNoAsyFile.search(strLine) != -1 )
{
found = true;
flush = true;
m_currentItem.setSourceLine(0);
m_currentItem.setMessage(reNoAsyFile.cap(0));
m_currentItem.setOutputLine(GetCurrentOutputLine());
}
break;
//warning spans multiple lines, detect the end
case Warning :
warning = m_currentItem.message() + strLine;
//KILE_DEBUG() << "'\tWarning (cont'd) : " << warning << endl;
flush = detectLaTeXLineNumber(warning, dwCookie, strLine.length());
m_currentItem.setMessage(warning);
break;
default : break;
}
if ( found )
{
m_currentItem.setType(itmWarning);
m_currentItem.setOutputLine(GetCurrentOutputLine());
}
if (flush) flushCurrentItem();
return found;
}
bool LatexOutputFilter::detectLaTeXLineNumber(TQString & warning, short & dwCookie, int len)
{
//KILE_DEBUG() << "==LatexOutputFilter::detectLaTeXLineNumber(" << warning.length() << ")================" << endl;
static TQRegExp reLaTeXLineNumber("(.*) on input line ([0-9]+)\\.$", false);
static TQRegExp reInternationalLaTeXLineNumber("(.*)([0-9]+)\\.$", false);
if ( (reLaTeXLineNumber.search(warning) != -1) || (reInternationalLaTeXLineNumber.search(warning) != -1) )
{
//KILE_DEBUG() << "een" << endl;
m_currentItem.setSourceLine(reLaTeXLineNumber.cap(2).toInt());
warning += reLaTeXLineNumber.cap(1);
dwCookie = Start;
return true;
}
else if ( warning.endsWith(".") )
{
//KILE_DEBUG() << "twee" << endl;
m_currentItem.setSourceLine(0);
dwCookie = Start;
return true;
}
//bailing out, did not find a line number
else if ( (GetCurrentOutputLine() - m_currentItem.outputLine() > 4) || (len == 0) )
{
//KILE_DEBUG() << "drie current " << GetCurrentOutputLine() << " " << m_currentItem.outputLine() << " len " << len << endl;
m_currentItem.setSourceLine(0);
dwCookie = Start;
return true;
}
//error message is continued on the other line
else
{
//KILE_DEBUG() << "vier" << endl;
dwCookie = Warning;
return false;
}
}
bool LatexOutputFilter::detectBadBox(const TQString & strLine, short & dwCookie)
{
//KILE_DEBUG() << "==LatexOutputFilter::detectBadBox(" << strLine.length() << ")================" << endl;
bool found = false, flush = false;
TQString badbox;
static TQRegExp reBadBox("^(Over|Under)(full \\\\[hv]box .*)", false);
switch (dwCookie)
{
case Start :
if ( reBadBox.search(strLine) != -1)
{
found = true;
dwCookie = Start;
badbox = strLine;
flush = detectBadBoxLineNumber(badbox, dwCookie, strLine.length());
m_currentItem.setMessage(badbox);
}
break;
case BadBox :
badbox = m_currentItem.message() + strLine;
flush = detectBadBoxLineNumber(badbox, dwCookie, strLine.length());
m_currentItem.setMessage(badbox);
break;
default : break;
}
if ( found )
{
m_currentItem.setType(itmBadBox);
m_currentItem.setOutputLine(GetCurrentOutputLine());
}
if (flush) flushCurrentItem();
return found;
}
bool LatexOutputFilter::detectBadBoxLineNumber(TQString & strLine, short & dwCookie, int len)
{
//KILE_DEBUG() << "==LatexOutputFilter::detectBadBoxLineNumber(" << strLine.length() << ")================" << endl;
static TQRegExp reBadBoxLines("(.*) at lines ([0-9]+)--([0-9]+)", false);
static TQRegExp reBadBoxLine("(.*) at line ([0-9]+)", false);
//Use the following only, if you know how to get the source line for it.
// This is not simple, as TeX is not reporting it.
static TQRegExp reBadBoxOutput("(.*)has occurred while \\output is active^", false);
if ( reBadBoxLines.search(strLine) != -1)
{
dwCookie = Start;
strLine = reBadBoxLines.cap(1);
int n1 = reBadBoxLines.cap(2).toInt();
int n2 = reBadBoxLines.cap(3).toInt();
m_currentItem.setSourceLine(n1 < n2 ? n1 : n2);
return true;
}
else if ( reBadBoxLine.search(strLine) != -1)
{
dwCookie = Start;
strLine = reBadBoxLine.cap(1);
m_currentItem.setSourceLine(reBadBoxLine.cap(2).toInt());
//KILE_DEBUG() << "\tBadBox@" << reBadBoxLine.cap(2) << "." << endl;
return true;
}
else if ( reBadBoxOutput.search(strLine) != -1)
{
dwCookie = Start;
strLine = reBadBoxLines.cap(1);
m_currentItem.setSourceLine(0);
return true;
}
//bailing out, did not find a line number
else if ( (GetCurrentOutputLine() - m_currentItem.outputLine() > 3) || (len == 0) )
{
dwCookie = Start;
m_currentItem.setSourceLine(0);
return true;
}
else
{
dwCookie = BadBox;
}
return false;
}
short LatexOutputFilter::parseLine(const TQString & strLine, short dwCookie)
{
//KILE_DEBUG() << "==LatexOutputFilter::parseLine(" << strLine.length() << ")================" << endl;
switch (dwCookie)
{
case Start :
if ( ! (detectBadBox(strLine, dwCookie) || detectWarning(strLine, dwCookie) || detectError(strLine, dwCookie)) )
updateFileStack(strLine, dwCookie);
break;
case Warning :
detectWarning(strLine, dwCookie);
break;
case Error : case LineNumber :
detectError(strLine, dwCookie);
break;
case BadBox :
detectBadBox(strLine, dwCookie);
break;
case FileName : case FileNameHeuristic :
updateFileStack(strLine, dwCookie);
break;
default: dwCookie = Start; break;
}
return dwCookie;
}
// split old Run() into three parts
// - Run() : parse the logfile
// - updateInfoLists() : needed by QuickPreview
// - sendProblems() : emit signals
//
// dani 18.02.2005
bool LatexOutputFilter::Run(const TQString & logfile)
{
m_InfoList->clear();
m_nErrors = m_nWarnings = m_nBadBoxes = m_nParens = 0;
m_stackFile.clear();
m_stackFile.push(LOFStackItem(TQFileInfo(source()).fileName(), true));
return OutputFilter::Run(logfile);
}
void LatexOutputFilter::updateInfoLists(const TQString &texfilename, int selrow, int docrow)
{
// get a short name for the original tex file
TQString filename = "./" + TQFileInfo(texfilename).fileName();
setSource(texfilename);
//print detailed error info
for(unsigned int i=0; i < m_InfoList->count(); ++i) {
// perhaps correct filename and line number in OutputInfo
OutputInfo *info = &(*m_InfoList)[i];
info->setSource(filename);
int linenumber = selrow + info->sourceLine() - docrow;
if ( linenumber < 0 )
linenumber = 0;
info->setSourceLine(linenumber);
}
}
void LatexOutputFilter::sendProblems()
{
TQString Message;
int type;
//print detailed error info
for(unsigned int i=0; i < m_InfoList->count(); ++i) {
Message = TQString("%1:%2:%3").arg((*m_InfoList)[i].source()).arg((*m_InfoList)[i].sourceLine()).arg((*m_InfoList)[i].message());
switch ( (*m_InfoList)[i].type() )
{
case LatexOutputInfo::itmBadBox : type = KileTool::ProblemBadBox; break;
case LatexOutputInfo::itmError : type = KileTool::ProblemError; break;
case LatexOutputInfo::itmWarning : type = KileTool::ProblemWarning; break;
default : type = KileTool::Info; break;
}
emit(problem(type, Message));
}
}
/** Return number of errors etc. found in log-file. */
void LatexOutputFilter::getErrorCount(int *errors, int *warnings, int *badboxes)
{
*errors = m_nErrors;
*warnings = m_nWarnings;
*badboxes = m_nBadBoxes;
}