Warning, file /office/kile/src/parser/latexoutputparser.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /**********************************************************************************
0002 *   Copyright (C) 2003 by Jeroen Wijnhout (Jeroen.Wijnhout@kdemail.net)           *
0003 *                 2011 by Michel Ludwig (michel.ludwig@kdemail.net)               *
0004 ***********************************************************************************/
0005 
0006 /***************************************************************************
0007  *                                                                         *
0008  *   This program is free software; you can redistribute it and/or modify  *
0009  *   it under the terms of the GNU General Public License as published by  *
0010  *   the Free Software Foundation; either version 2 of the License, or     *
0011  *   (at your option) any later version.                                   *
0012  *                                                                         *
0013  ***************************************************************************/
0014 
0015 #include "latexoutputparser.h"
0016 
0017 #include <QDir>
0018 #include <QFileInfo>
0019 #include <QRegularExpression>
0020 
0021 #include <KLocalizedString>
0022 
0023 #include "kiletool_enums.h"
0024 #include "parserthread.h"
0025 #include "widgets/logwidget.h"
0026 
0027 namespace KileParser {
0028 
0029 LaTeXOutputParserInput::LaTeXOutputParserInput(const QUrl &url, KileDocument::Extensions *extensions,
0030                                                                 const QString& sourceFile,
0031                                                                 const QString &texfilename,
0032                                                                 int selrow,
0033                                                                 int docrow)
0034     : ParserInput(url),
0035       extensions(extensions),
0036       sourceFile(sourceFile),
0037       texfilename(texfilename),
0038       selrow(selrow),
0039       docrow(docrow)
0040 {
0041 }
0042 
0043 LaTeXOutputParserOutput::LaTeXOutputParserOutput()
0044 {
0045 }
0046 
0047 LaTeXOutputParserOutput::~LaTeXOutputParserOutput()
0048 {
0049     qCDebug(LOG_KILE_PARSER);
0050 }
0051 
0052 LaTeXOutputParser::LaTeXOutputParser(ParserThread *parserThread, LaTeXOutputParserInput *input, QObject *parent)
0053     : Parser(parserThread, parent),
0054       m_extensions(input->extensions),
0055       m_infoList(Q_NULLPTR),
0056       m_logFile(input->url.toLocalFile()),
0057       texfilename(input->texfilename),
0058       selrow(input->selrow),
0059       docrow(input->docrow)
0060 {
0061     m_nErrors = 0;
0062     m_nWarnings = 0;
0063     m_nBadBoxes = 0;
0064     setSource(input->sourceFile);
0065 }
0066 
0067 LaTeXOutputParser::~LaTeXOutputParser()
0068 {
0069     qCDebug(LOG_KILE_PARSER);
0070 }
0071 
0072 bool LaTeXOutputParser::fileExists(const QString & name)
0073 {
0074     static QFileInfo fi;
0075 
0076     if (QDir::isAbsolutePath(name)) {
0077         fi.setFile(name);
0078         if(fi.exists() && !fi.isDir()) {
0079             return true;
0080         }
0081         else {
0082             return false;
0083         }
0084     }
0085 
0086     fi.setFile(path() + '/' + name);
0087     if(fi.exists() && !fi.isDir()) {
0088         return true;
0089     }
0090 
0091     fi.setFile(path() + '/' + name + m_extensions->latexDocumentDefault());
0092     if(fi.exists() && !fi.isDir()) {
0093         return true;
0094     }
0095 
0096     // try to determine the LaTeX source file
0097     QStringList extlist = m_extensions->latexDocuments().split(' ');
0098     for(QStringList::Iterator it = extlist.begin(); it!=extlist.end(); ++it) {
0099         fi.setFile(path() + '/' + name + (*it));
0100         if(fi.exists() && !fi.isDir()) {
0101             return true;
0102         }
0103     }
0104 
0105     return false;
0106 }
0107 
0108 // There are basically two ways to detect the current file TeX is processing:
0109 //  1) Use \Input (i.c.w. srctex.sty or srcltx.sty) and \include exclusively. This will
0110 //  cause (La)TeX to print the line ":<+ filename"  in the log file when opening a file,
0111 //  ":<-" when closing a file. Filenames pushed on the stack in this mode are marked
0112 //  as reliable.
0113 //
0114 //  2) Since people will probably also use the \input command, we also have to be
0115 //  to detect the old-fashioned way. TeX prints '(filename' when opening a file and a ')'
0116 //  when closing one. It is impossible to detect this with 100% certainty (TeX prints many messages
0117 //  and even text (a context) from the TeX source file, there could be unbalanced parentheses),
0118 //  so we use a heuristic algorithm. In heuristic mode a ')' will only be considered as a signal that
0119 //  TeX is closing a file if the top of the stack is not marked as "reliable".
0120 //  Also, when scanning for a TeX error linenumber (which sometimes causes a context to be printed
0121 //  to the log-file), updateFileStack is not called, helping not to pick up unbalanced parentheses
0122 //  from the context.
0123 void LaTeXOutputParser::updateFileStack(const QString &strLine, short& dwCookie)
0124 {
0125     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::updateFileStack()================" << Qt::endl;
0126     static QString strPartialFileName;
0127 
0128     switch (dwCookie) {
0129     //we're looking for a filename
0130     case Start :
0131     case FileNameHeuristic :
0132         //TeX is opening a file
0133         if(strLine.startsWith(":<+ ")) {
0134 //              qCDebug(LOG_KILE_PARSER) << "filename detected" << Qt::endl;
0135             //grab the filename, it might be a partial name (i.e. continued on the next line)
0136             strPartialFileName = strLine.mid(4).trimmed();
0137 
0138             //change the cookie so we remember we aren't sure the filename is complete
0139             dwCookie = FileName;
0140         }
0141         //TeX closed a file
0142         else if(strLine.contains(":<-")) {
0143 //              qCDebug(LOG_KILE_PARSER) << "\tpopping : " << m_stackFile.top().file() << Qt::endl;
0144             if(!m_stackFile.isEmpty()) {
0145                 m_stackFile.pop();
0146             }
0147             dwCookie = Start;
0148         }
0149         else {
0150             //fallback to the heuristic detection of filenames
0151             updateFileStackHeuristic(strLine, dwCookie);
0152         }
0153         break;
0154 
0155     case FileName :
0156         //The partial filename was followed by '(', this means that TeX is signalling it is
0157         //opening the file. We are sure the filename is complete now. Don't call updateFileStackHeuristic
0158         //since we don't want the filename on the stack twice.
0159         if(strLine.startsWith('(') || strLine.startsWith(QLatin1String("\\openout"))) {
0160             //push the filename on the stack and mark it as 'reliable'
0161             m_stackFile.push(LOFStackItem(strPartialFileName, true));
0162 //              qCDebug(LOG_KILE_PARSER) << "\tpushed : " << strPartialFileName << Qt::endl;
0163             strPartialFileName.clear();
0164             dwCookie = Start;
0165         }
0166         //The partial filename was followed by an TeX error, meaning the file doesn't exist.
0167         //Don't push it on the stack, instead try to detect the error.
0168         else if(strLine.startsWith('!')) {
0169 //              qCDebug(LOG_KILE_PARSER) << "oops!" << Qt::endl;
0170             dwCookie = Start;
0171             strPartialFileName.clear();
0172             detectError(strLine, dwCookie);
0173         }
0174         else if(strLine.startsWith(QLatin1String("No file"))) {
0175 //              qCDebug(LOG_KILE_PARSER) << "No file: " << strLine << Qt::endl;
0176             dwCookie = Start;
0177             strPartialFileName.clear();
0178             detectWarning(strLine, dwCookie);
0179         }
0180         //Partial filename still isn't complete.
0181         else {
0182 //              qCDebug(LOG_KILE_PARSER) << "\tpartial file name, adding" << Qt::endl;
0183             strPartialFileName = strPartialFileName + strLine.trimmed();
0184         }
0185         break;
0186 
0187     default:
0188         break;
0189     }
0190 }
0191 
0192 void LaTeXOutputParser::updateFileStackHeuristic(const QString &strLine, short & dwCookie)
0193 {
0194     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::updateFileStackHeuristic()================";
0195     //qCDebug(LOG_KILE_PARSER) << strLine << dwCookie;
0196     static QString strPartialFileName;
0197     bool expectFileName = (dwCookie == FileNameHeuristic);
0198     int index = 0;
0199 
0200     // handle special case (bug fix for 101810)
0201     if(expectFileName && strLine.length() > 0 && strLine[0] == ')') {
0202         m_stackFile.push(LOFStackItem(strPartialFileName));
0203         expectFileName = false;
0204         dwCookie = Start;
0205     }
0206 
0207     //scan for parentheses and grab filenames
0208     for(int i = 0; i < strLine.length(); ++i) {
0209         /*
0210         We're expecting a filename. If a filename really ends at this position one of the following must be true:
0211             1) Next character is a space (indicating the end of a filename (yes, there can't be spaces in the
0212             path, this is a TeX limitation).
0213         comment by tbraun: there is a workaround \include{{"file name"}} according to https://groups.google.com/forum/#!topic/comp.text.tex/r4c1NPBkTk8,
0214         but this is currently not supported by kile.
0215             2) We're at the end of the line, the filename is probably continued on the next line.
0216             3) The TeX was closed already, signaled by the ')'.
0217         */
0218 
0219         bool isLastChar = (i+1 == strLine.length());
0220         bool nextIsTerminator = isLastChar ? false : (strLine[i+1].isSpace() || strLine[i+1] == ')');
0221 
0222         if(expectFileName && (isLastChar || nextIsTerminator)) {
0223             qCDebug(LOG_KILE_PARSER) << "Update the partial filename " << strPartialFileName;
0224             strPartialFileName =  strPartialFileName + strLine.mid(index, i-index + 1);
0225 
0226             if(strPartialFileName.isEmpty()) { // nothing left to do here
0227                 continue;
0228             }
0229 
0230             //FIXME: improve these heuristics
0231             if((isLastChar && (i < 78)) || nextIsTerminator || fileExists(strPartialFileName)) {
0232                 m_stackFile.push(LOFStackItem(strPartialFileName));
0233                 //qCDebug(LOG_KILE_PARSER) << "\tpushed (i = " << i << " length = " << strLine.length() << "): " << strPartialFileName;
0234                 expectFileName = false;
0235                 dwCookie = Start;
0236             }
0237             //Guess the filename is continued on the next line, only if the current strPartialFileName does not exist, see bug # 162899
0238             else if(isLastChar) {
0239                 if(fileExists(strPartialFileName)) {
0240                     m_stackFile.push(LOFStackItem(strPartialFileName));
0241                     qCDebug(LOG_KILE_PARSER) << "pushed (i = " << i << " length = " << strLine.length() << "): " << strPartialFileName << Qt::endl;
0242                     expectFileName = false;
0243                     dwCookie = Start;
0244                 }
0245                 else {
0246                     qCDebug(LOG_KILE_PARSER) << "Filename spans more than one line." << Qt::endl;
0247                     dwCookie = FileNameHeuristic;
0248                 }
0249             }
0250             //bail out
0251             else {
0252                 dwCookie = Start;
0253                 strPartialFileName.clear();
0254                 expectFileName = false;
0255             }
0256         }
0257         //TeX is opening a file
0258         else if(strLine[i] == '(') {
0259             //we need to extract the filename
0260             expectFileName = true;
0261             strPartialFileName.clear();
0262             dwCookie = Start;
0263 
0264             //this is were the filename is supposed to start
0265             index = i + 1;
0266         }
0267         //TeX is closing a file
0268         else if(strLine[i] == ')') {
0269             //qCDebug(LOG_KILE_PARSER) << "\tpopping : " << m_stackFile.top().file();
0270             //If this filename was pushed on the stack by the reliable ":<+-" method, don't pop
0271             //a ":<-" will follow. This helps in preventing unbalanced ')' from popping filenames
0272             //from the stack too soon.
0273             if(m_stackFile.count() > 1 && !m_stackFile.top().reliable()) {
0274                 m_stackFile.pop();
0275             }
0276             else {
0277                 qCDebug(LOG_KILE_PARSER) << "\t\toh no, forget about it!";
0278             }
0279         }
0280     }
0281 }
0282 
0283 
0284 void LaTeXOutputParser::flushCurrentItem()
0285 {
0286     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::flushCurrentItem()================" << Qt::endl;
0287     int nItemType = m_currentItem.type();
0288 
0289     while( m_stackFile.count() > 0 && !fileExists(m_stackFile.top().file()) ) {
0290         m_stackFile.pop();
0291     }
0292 
0293     QString sourceFile = (m_stackFile.isEmpty()) ? QFileInfo(source()).fileName() : m_stackFile.top().file();
0294     m_currentItem.setSource(sourceFile);
0295     m_currentItem.setMainSourceFile(source());
0296 
0297     switch (nItemType) {
0298     case itmError:
0299         ++m_nErrors;
0300         m_infoList->push_back(m_currentItem);
0301         //qCDebug(LOG_KILE_PARSER) << "Flushing Error in" << m_currentItem.source() << "@" << m_currentItem.sourceLine() << " reported in line " << m_currentItem.outputLine() << Qt::endl;
0302         break;
0303 
0304     case itmWarning:
0305         ++m_nWarnings;
0306         m_infoList->push_back(m_currentItem);
0307         //qCDebug(LOG_KILE_PARSER) << "Flushing Warning in " << m_currentItem.source() << "@" << m_currentItem.sourceLine() << " reported in line " << m_currentItem.outputLine() << Qt::endl;
0308         break;
0309 
0310     case itmBadBox:
0311         ++m_nBadBoxes;
0312         m_infoList->push_back(m_currentItem);
0313         //qCDebug(LOG_KILE_PARSER) << "Flushing BadBox in " << m_currentItem.source() << "@" << m_currentItem.sourceLine() << " reported in line " << m_currentItem.outputLine() << Qt::endl;
0314         break;
0315 
0316     default:
0317         break;
0318     }
0319     m_currentItem.clear();
0320 }
0321 
0322 bool LaTeXOutputParser::detectError(const QString & strLine, short &dwCookie)
0323 {
0324     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::detectError(" << strLine.length() << ")================" << Qt::endl;
0325 
0326     bool found = false, flush = false;
0327 
0328     static QRegularExpression reLaTeXError("^! LaTeX Error: (.*)$");
0329     reLaTeXError.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0330     static QRegularExpression rePDFLaTeXError("^Error: pdflatex (.*)$");
0331     rePDFLaTeXError.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0332     static QRegularExpression reTeXError("^! (.*)\\.$");
0333     static QRegularExpression reLineNumber("^l\\.([0-9]+)(.*)");
0334 
0335     switch (dwCookie) {
0336     case Start: {
0337         QRegularExpressionMatch latexErrorMatch = reLaTeXError.match(strLine);
0338         if(latexErrorMatch.hasMatch()) {
0339             //qCDebug(LOG_KILE_PARSER) << "\tError : " <<  reLaTeXError.cap(1) << Qt::endl;
0340             m_currentItem.setMessage(latexErrorMatch.captured(1));
0341             found = true;
0342         }
0343         else {
0344             QRegularExpressionMatch pdflatexErrorMatch = rePDFLaTeXError.match(strLine);
0345             if(pdflatexErrorMatch.hasMatch()) {
0346                 //qCDebug(LOG_KILE_PARSER) << "\tError : " <<  rePDFLaTeXError.cap(1) << Qt::endl;
0347                 m_currentItem.setMessage(pdflatexErrorMatch.captured(1));
0348                 found = true;
0349             }
0350             else {
0351                 QRegularExpressionMatch texErrorMatch = reTeXError.match(strLine);
0352                 if(texErrorMatch.hasMatch()) {
0353                     //qCDebug(LOG_KILE_PARSER) << "\tError : " <<  reTeXError.cap(1) << Qt::endl;
0354                     m_currentItem.setMessage(texErrorMatch.captured(1));
0355                     found = true;
0356                 }
0357             }
0358         }
0359 
0360         if(found) {
0361             dwCookie = strLine.endsWith('.') ? LineNumber : Error;
0362             m_currentItem.setOutputLine(GetCurrentOutputLine());
0363         }
0364         break;
0365     }
0366     case Error :
0367         //qCDebug(LOG_KILE_PARSER) << "\tError (cont'd): " << strLine << Qt::endl;
0368         if(strLine.endsWith('.')) {
0369             dwCookie = LineNumber;
0370             m_currentItem.setMessage(m_currentItem.message() + strLine);
0371         }
0372         else if(GetCurrentOutputLine() - m_currentItem.outputLine() > 3) {
0373             qWarning() << "\tBAILING OUT: error description spans more than three lines" << Qt::endl;
0374             dwCookie = Start;
0375             flush = true;
0376         }
0377         break;
0378 
0379     case LineNumber: {
0380         //qCDebug(LOG_KILE_PARSER) << "\tLineNumber " << Qt::endl;
0381         QRegularExpressionMatch lineNumberMatch = reLineNumber.match(strLine);
0382         if(lineNumberMatch.hasMatch()) {
0383             dwCookie = Start;
0384             flush = true;
0385             //qCDebug(LOG_KILE_PARSER) << "\tline number: " << reLineNumber.cap(1) << Qt::endl;
0386             m_currentItem.setSourceLine(lineNumberMatch.captured(1).toInt());
0387             m_currentItem.setMessage(m_currentItem.message() + lineNumberMatch.captured(2));
0388         }
0389         else if(GetCurrentOutputLine() - m_currentItem.outputLine() > 10) {
0390             dwCookie = Start;
0391             flush = true;
0392             qWarning() << "\tBAILING OUT: did not detect a TeX line number for an error" << Qt::endl;
0393             m_currentItem.setSourceLine(0);
0394         }
0395         break;
0396     }
0397 
0398     default :
0399         break;
0400     }
0401 
0402     if(found) {
0403         m_currentItem.setType(itmError);
0404         m_currentItem.setOutputLine(GetCurrentOutputLine());
0405     }
0406 
0407     if(flush) {
0408         flushCurrentItem();
0409     }
0410 
0411     return found;
0412 }
0413 
0414 bool LaTeXOutputParser::detectWarning(const QString & strLine, short &dwCookie)
0415 {
0416     //qCDebug(LOG_KILE_PARSER) << strLine << strLine.length();
0417 
0418     bool found = false, flush = false;
0419     QString warning;
0420 
0421     static QRegularExpression reLaTeXWarning("^(((! )?(La|pdf)TeX)|Package|Class) .*Warning.*:(.*)");
0422     reLaTeXWarning.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0423     static QRegularExpression reNoFile("No file (.*)");
0424     static QRegularExpression reNoAsyFile("File .* does not exist."); // FIXME can be removed when https://sourceforge.net/p/asymptote/bugs/70/ has promoted to the users
0425 
0426     switch(dwCookie) {
0427     //detect the beginning of a warning
0428     case Start: {
0429         QRegularExpressionMatch laTeXWarningMatch = reLaTeXWarning.match(strLine);
0430         if(laTeXWarningMatch.hasMatch()) {
0431             warning = laTeXWarningMatch.captured(5);
0432             //qCDebug(LOG_KILE_PARSER) << "\tWarning found: " << warning << Qt::endl;
0433 
0434             found = true;
0435             dwCookie = Start;
0436 
0437             m_currentItem.setMessage(warning);
0438             m_currentItem.setOutputLine(GetCurrentOutputLine());
0439 
0440             //do we expect a line number?
0441             flush = detectLaTeXLineNumber(warning, dwCookie, strLine.length());
0442             break;
0443         }
0444 
0445         QRegularExpressionMatch noFileMatch = reNoFile.match(strLine);
0446         if(noFileMatch.hasMatch()) {
0447             found = true;
0448             flush = true;
0449             m_currentItem.setSourceLine(0);
0450             m_currentItem.setMessage(noFileMatch.captured(0));
0451             m_currentItem.setOutputLine(GetCurrentOutputLine());
0452             break;
0453         }
0454 
0455         QRegularExpressionMatch noAsyFileMatch = reNoAsyFile.match(strLine);
0456         if(noAsyFileMatch.hasMatch()) {
0457             found = true;
0458             flush = true;
0459             m_currentItem.setSourceLine(0);
0460             m_currentItem.setMessage(noAsyFileMatch.captured(0));
0461             m_currentItem.setOutputLine(GetCurrentOutputLine());
0462         }
0463 
0464         break;
0465     }
0466 
0467     //warning spans multiple lines, detect the end
0468     case Warning :
0469         warning = m_currentItem.message() + strLine;
0470         //qCDebug(LOG_KILE_PARSER) << "'\tWarning (cont'd) : " << warning << Qt::endl;
0471         flush = detectLaTeXLineNumber(warning, dwCookie, strLine.length());
0472         m_currentItem.setMessage(warning);
0473         break;
0474 
0475     default:
0476         break;
0477     }
0478 
0479     if(found) {
0480         m_currentItem.setType(itmWarning);
0481         m_currentItem.setOutputLine(GetCurrentOutputLine());
0482     }
0483 
0484     if(flush) {
0485         flushCurrentItem();
0486     }
0487 
0488     return found;
0489 }
0490 
0491 bool LaTeXOutputParser::detectLaTeXLineNumber(QString & warning, short & dwCookie, int len)
0492 {
0493     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::detectLaTeXLineNumber(" << warning.length() << ")================" << Qt::endl;
0494 
0495     static QRegularExpression reLaTeXLineNumber("(.*) on input line ([0-9]+)\\.$");
0496     reLaTeXLineNumber.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0497     static QRegularExpression reInternationalLaTeXLineNumber("(.*)([0-9]+)\\.$");
0498     reInternationalLaTeXLineNumber.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0499 
0500     QRegularExpressionMatch laTeXLineNumberMatch = reLaTeXLineNumber.match(warning);
0501 
0502     if(laTeXLineNumberMatch.hasMatch()) {
0503         //qCDebug(LOG_KILE_PARSER) << "een" << Qt::endl;
0504         m_currentItem.setSourceLine(laTeXLineNumberMatch.captured(2).toInt());
0505         warning += laTeXLineNumberMatch.captured(1);
0506         dwCookie = Start;
0507         return true;
0508     }
0509 
0510     QRegularExpressionMatch internaltionalLaTeXLineNumberMatch = reInternationalLaTeXLineNumber.match(warning);
0511 
0512     if(internaltionalLaTeXLineNumberMatch.hasMatch()) {
0513         m_currentItem.setSourceLine(internaltionalLaTeXLineNumberMatch.captured(2).toInt());
0514         warning += internaltionalLaTeXLineNumberMatch.captured(1);
0515         dwCookie = Start;
0516         return true;
0517     }
0518 
0519     if(warning.endsWith('.')) {
0520         //qCDebug(LOG_KILE_PARSER) << "twee" << Qt::endl;
0521         m_currentItem.setSourceLine(0);
0522         dwCookie = Start;
0523         return true;
0524     }
0525 
0526     //bailing out, did not find a line number
0527     if((GetCurrentOutputLine() - m_currentItem.outputLine() > 4) || (len == 0)) {
0528         //qCDebug(LOG_KILE_PARSER) << "drie current " << GetCurrentOutputLine() << " " <<  m_currentItem.outputLine() << " len " << len << Qt::endl;
0529         m_currentItem.setSourceLine(0);
0530         dwCookie = Start;
0531         return true;
0532     }
0533 
0534     //error message is continued on the other line
0535     dwCookie = Warning;
0536     return false;
0537 }
0538 
0539 bool LaTeXOutputParser::detectBadBox(const QString & strLine, short & dwCookie)
0540 {
0541     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::detectBadBox(" << strLine.length() << ")================" << Qt::endl;
0542 
0543     bool found = false, flush = false;
0544     QString badbox;
0545 
0546     static QRegularExpression reBadBox("^(Over|Under)(full \\\\[hv]box .*)");
0547     reBadBox.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0548 
0549     switch(dwCookie) {
0550     case Start: {
0551         const QRegularExpressionMatch match = reBadBox.match(strLine);
0552         if(match.hasMatch()) {
0553             found = true;
0554             dwCookie = Start;
0555             badbox = strLine;
0556             flush = detectBadBoxLineNumber(badbox, dwCookie, strLine.length());
0557             m_currentItem.setMessage(badbox);
0558         }
0559         break;
0560     }
0561 
0562     case BadBox :
0563         badbox = m_currentItem.message() + strLine;
0564         flush = detectBadBoxLineNumber(badbox, dwCookie, strLine.length());
0565         m_currentItem.setMessage(badbox);
0566         break;
0567 
0568     default:
0569         break;
0570     }
0571 
0572     if(found) {
0573         m_currentItem.setType(itmBadBox);
0574         m_currentItem.setOutputLine(GetCurrentOutputLine());
0575     }
0576 
0577     if(flush) {
0578         flushCurrentItem();
0579     }
0580 
0581     return found;
0582 }
0583 
0584 bool LaTeXOutputParser::detectBadBoxLineNumber(QString & strLine, short & dwCookie, int len)
0585 {
0586     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::detectBadBoxLineNumber(" << strLine.length() << ")================" << Qt::endl;
0587 
0588     static QRegularExpression reBadBoxLines("(.*) at lines ([0-9]+)--([0-9]+)");
0589     reBadBoxLines.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0590     static QRegularExpression reBadBoxLine("(.*) at line ([0-9]+)");
0591     reBadBoxLine.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0592     //Use the following only, if you know how to get the source line for it.
0593     // This is not simple, as TeX is not reporting it.
0594     static QRegularExpression reBadBoxOutput("(.*)has occurred while \\output is active^");
0595     reBadBoxOutput.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
0596 
0597     QRegularExpressionMatch badBoxLinesMatch = reBadBoxLines.match(strLine);
0598     if(badBoxLinesMatch.hasMatch()) {
0599         dwCookie = Start;
0600         strLine = badBoxLinesMatch.captured(1);
0601         int n1 = badBoxLinesMatch.captured(2).toInt();
0602         int n2 = badBoxLinesMatch.captured(3).toInt();
0603         m_currentItem.setSourceLine(n1 < n2 ? n1 : n2);
0604         return true;
0605     }
0606 
0607     QRegularExpressionMatch badBoxLineMatch = reBadBoxLine.match(strLine);
0608     if(badBoxLineMatch.hasMatch()) {
0609         dwCookie = Start;
0610         strLine = badBoxLineMatch.captured(1);
0611         m_currentItem.setSourceLine(badBoxLineMatch.captured(2).toInt());
0612         //qCDebug(LOG_KILE_PARSER) << "\tBadBox@" << reBadBoxLine.cap(2) << "." << Qt::endl;
0613         return true;
0614     }
0615 
0616     QRegularExpressionMatch badBoxOutputMatch = reBadBoxOutput.match(strLine);
0617     if(badBoxOutputMatch.hasMatch()) {
0618         dwCookie = Start;
0619         strLine = badBoxOutputMatch.captured(1);
0620         m_currentItem.setSourceLine(0);
0621         return true;
0622     }
0623 
0624     //bailing out, did not find a line number
0625     if((GetCurrentOutputLine() - m_currentItem.outputLine() > 3) || (len == 0)) {
0626         dwCookie = Start;
0627         m_currentItem.setSourceLine(0);
0628         return true;
0629     }
0630 
0631     dwCookie = BadBox;
0632     return false;
0633 }
0634 
0635 short LaTeXOutputParser::parseLine(const QString & strLine, short dwCookie)
0636 {
0637     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::parseLine(" << strLine << dwCookie << strLine.length() << ")================" << Qt::endl;
0638 
0639     switch (dwCookie) {
0640     case Start :
0641         if(!(detectBadBox(strLine, dwCookie) || detectWarning(strLine, dwCookie) || detectError(strLine, dwCookie))) {
0642             updateFileStack(strLine, dwCookie);
0643         }
0644         break;
0645 
0646     case Warning :
0647         detectWarning(strLine, dwCookie);
0648         break;
0649 
0650     case Error :
0651     case LineNumber :
0652         detectError(strLine, dwCookie);
0653         break;
0654 
0655     case BadBox :
0656         detectBadBox(strLine, dwCookie);
0657         break;
0658 
0659     case FileName :
0660     case FileNameHeuristic :
0661         updateFileStack(strLine, dwCookie);
0662         break;
0663 
0664     default:
0665         dwCookie = Start;
0666         break;
0667     }
0668 
0669     return dwCookie;
0670 }
0671 
0672 ParserOutput* LaTeXOutputParser::parse()
0673 {
0674     LaTeXOutputParserOutput *parserOutput = new LaTeXOutputParserOutput();
0675 
0676     qCDebug(LOG_KILE_PARSER);
0677 
0678     m_infoList = &(parserOutput->infoList);
0679     m_nErrors = m_nWarnings = m_nBadBoxes = m_nParens = 0;
0680     m_stackFile.clear();
0681     m_stackFile.push(LOFStackItem(QFileInfo(source()).fileName(), true));
0682 
0683     short sCookie = 0;
0684     QFile f(m_logFile);
0685 
0686     m_log.clear();
0687     m_nOutputLines = 0;
0688 
0689     if(!f.open(QIODevice::ReadOnly)) {
0690         parserOutput->problem = i18n("Cannot open log file; did you run LaTeX?");
0691         return parserOutput;
0692     }
0693     m_infoList = &parserOutput->infoList;
0694     QTextStream t(&f);
0695     while(!t.atEnd()) {
0696         if(!m_parserThread->shouldContinueDocumentParsing()) {
0697             qCDebug(LOG_KILE_PARSER) << "stopping...";
0698             delete(parserOutput);
0699             f.close();
0700             return Q_NULLPTR;
0701         }
0702         QString s = t.readLine();
0703         sCookie = parseLine(s.trimmed(), sCookie);
0704         ++m_nOutputLines;
0705 
0706         m_log += s + '\n';
0707     }
0708     f.close();
0709 
0710     parserOutput->nWarnings = m_nWarnings;
0711     parserOutput->nErrors = m_nErrors;
0712     parserOutput->nBadBoxes = m_nBadBoxes;
0713     parserOutput->logFile = m_logFile;
0714 
0715     // for quick preview
0716     if(!texfilename.isEmpty() && selrow >= 0 && docrow >= 0) {
0717         updateInfoLists(texfilename, selrow, docrow);
0718     }
0719 
0720     return parserOutput;
0721 }
0722 
0723 void LaTeXOutputParser::updateInfoLists(const QString &texfilename, int selrow, int docrow)
0724 {
0725     // get a short name for the original tex file
0726     QString filename = "./" + QFileInfo(texfilename).fileName();
0727 //  setSource(texfilename);
0728 
0729     //print detailed error info
0730     for(int i = 0; i < m_infoList->count(); ++i) {
0731         // perhaps correct filename and line number in OutputInfo
0732         OutputInfo &info = (*m_infoList)[i];
0733         info.setSource(filename);
0734 
0735         int linenumber = selrow + info.sourceLine() - docrow;
0736         if(linenumber < 0) {
0737             linenumber = 0;
0738         }
0739         info.setSourceLine(linenumber);
0740     }
0741 }
0742 
0743 
0744 
0745 /** Return number of errors etc. found in log-file. */
0746 void LaTeXOutputParser::getErrorCount(int *errors, int *warnings, int *badboxes)
0747 {
0748     *errors = m_nErrors;
0749     *warnings = m_nWarnings;
0750     *badboxes = m_nBadBoxes;
0751 }
0752 
0753 int LaTeXOutputParser::GetCurrentOutputLine() const
0754 {
0755     return m_nOutputLines;
0756 }
0757 
0758 void LaTeXOutputParser::setSource(const QString &src)
0759 {
0760     m_source = src;
0761     m_srcPath = QFileInfo(src).path();
0762 }
0763 
0764 }