1 /**********************************************************************************
2 *   Copyright (C) 2003 by Jeroen Wijnhout (Jeroen.Wijnhout@kdemail.net)           *
3 *                 2011 by Michel Ludwig (michel.ludwig@kdemail.net)               *
4 ***********************************************************************************/
5 
6 /***************************************************************************
7  *                                                                         *
8  *   This program is free software; you can redistribute it and/or modify  *
9  *   it under the terms of the GNU General Public License as published by  *
10  *   the Free Software Foundation; either version 2 of the License, or     *
11  *   (at your option) any later version.                                   *
12  *                                                                         *
13  ***************************************************************************/
14 
15 #include "latexoutputparser.h"
16 
17 #include <QDir>
18 #include <QFileInfo>
19 
20 #include <KLocalizedString>
21 
22 #include "kiletool_enums.h"
23 #include "parserthread.h"
24 #include "widgets/logwidget.h"
25 
26 namespace KileParser {
27 
LaTeXOutputParserInput(const QUrl & url,KileDocument::Extensions * extensions,const QString & sourceFile,const QString & texfilename,int selrow,int docrow)28 LaTeXOutputParserInput::LaTeXOutputParserInput(const QUrl &url, KileDocument::Extensions *extensions,
29                                                                 const QString& sourceFile,
30                                                                 const QString &texfilename,
31                                                                 int selrow,
32                                                                 int docrow)
33     : ParserInput(url),
34       extensions(extensions),
35       sourceFile(sourceFile),
36       texfilename(texfilename),
37       selrow(selrow),
38       docrow(docrow)
39 {
40 }
41 
LaTeXOutputParserOutput()42 LaTeXOutputParserOutput::LaTeXOutputParserOutput()
43 {
44 }
45 
~LaTeXOutputParserOutput()46 LaTeXOutputParserOutput::~LaTeXOutputParserOutput()
47 {
48     qCDebug(LOG_KILE_PARSER);
49 }
50 
LaTeXOutputParser(ParserThread * parserThread,LaTeXOutputParserInput * input,QObject * parent)51 LaTeXOutputParser::LaTeXOutputParser(ParserThread *parserThread, LaTeXOutputParserInput *input, QObject *parent)
52     : Parser(parserThread, parent),
53       m_extensions(input->extensions),
54       m_infoList(Q_NULLPTR),
55       m_logFile(input->url.toLocalFile()),
56       texfilename(input->texfilename),
57       selrow(input->selrow),
58       docrow(input->docrow)
59 {
60     m_nErrors = 0;
61     m_nWarnings = 0;
62     m_nBadBoxes = 0;
63     setSource(input->sourceFile);
64 }
65 
~LaTeXOutputParser()66 LaTeXOutputParser::~LaTeXOutputParser()
67 {
68     qCDebug(LOG_KILE_PARSER);
69 }
70 
fileExists(const QString & name)71 bool LaTeXOutputParser::fileExists(const QString & name)
72 {
73     static QFileInfo fi;
74 
75     if (QDir::isAbsolutePath(name)) {
76         fi.setFile(name);
77         if(fi.exists() && !fi.isDir()) {
78             return true;
79         }
80         else {
81             return false;
82         }
83     }
84 
85     fi.setFile(path() + '/' + name);
86     if(fi.exists() && !fi.isDir()) {
87         return true;
88     }
89 
90     fi.setFile(path() + '/' + name + m_extensions->latexDocumentDefault());
91     if(fi.exists() && !fi.isDir()) {
92         return true;
93     }
94 
95     // try to determine the LaTeX source file
96     QStringList extlist = m_extensions->latexDocuments().split(' ');
97     for(QStringList::Iterator it = extlist.begin(); it!=extlist.end(); ++it) {
98         fi.setFile(path() + '/' + name + (*it));
99         if(fi.exists() && !fi.isDir()) {
100             return true;
101         }
102     }
103 
104     return false;
105 }
106 
107 // There are basically two ways to detect the current file TeX is processing:
108 //	1) Use \Input (i.c.w. srctex.sty or srcltx.sty) and \include exclusively. This will
109 //	cause (La)TeX to print the line ":<+ filename"  in the log file when opening a file,
110 //	":<-" when closing a file. Filenames pushed on the stack in this mode are marked
111 //	as reliable.
112 //
113 //	2) Since people will probably also use the \input command, we also have to be
114 //	to detect the old-fashioned way. TeX prints '(filename' when opening a file and a ')'
115 //	when closing one. It is impossible to detect this with 100% certainty (TeX prints many messages
116 //	and even text (a context) from the TeX source file, there could be unbalanced parentheses),
117 //	so we use a heuristic algorithm. In heuristic mode a ')' will only be considered as a signal that
118 //	TeX is closing a file if the top of the stack is not marked as "reliable".
119 //	Also, when scanning for a TeX error linenumber (which sometimes causes a context to be printed
120 //	to the log-file), updateFileStack is not called, helping not to pick up unbalanced parentheses
121 //	from the context.
updateFileStack(const QString & strLine,short & dwCookie)122 void LaTeXOutputParser::updateFileStack(const QString &strLine, short& dwCookie)
123 {
124     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::updateFileStack()================" << endl;
125     static QString strPartialFileName;
126 
127     switch (dwCookie) {
128     //we're looking for a filename
129     case Start :
130     case FileNameHeuristic :
131         //TeX is opening a file
132         if(strLine.startsWith(":<+ ")) {
133 // 				qCDebug(LOG_KILE_PARSER) << "filename detected" << endl;
134             //grab the filename, it might be a partial name (i.e. continued on the next line)
135             strPartialFileName = strLine.mid(4).trimmed();
136 
137             //change the cookie so we remember we aren't sure the filename is complete
138             dwCookie = FileName;
139         }
140         //TeX closed a file
141         else if(strLine.contains(":<-")) {
142 // 				qCDebug(LOG_KILE_PARSER) << "\tpopping : " << m_stackFile.top().file() << endl;
143             if(!m_stackFile.isEmpty()) {
144                 m_stackFile.pop();
145             }
146             dwCookie = Start;
147         }
148         else {
149             //fallback to the heuristic detection of filenames
150             updateFileStackHeuristic(strLine, dwCookie);
151         }
152         break;
153 
154     case FileName :
155         //The partial filename was followed by '(', this means that TeX is signalling it is
156         //opening the file. We are sure the filename is complete now. Don't call updateFileStackHeuristic
157         //since we don't want the filename on the stack twice.
158         if(strLine.startsWith('(') || strLine.startsWith(QLatin1String("\\openout"))) {
159             //push the filename on the stack and mark it as 'reliable'
160             m_stackFile.push(LOFStackItem(strPartialFileName, true));
161 // 				qCDebug(LOG_KILE_PARSER) << "\tpushed : " << strPartialFileName << endl;
162             strPartialFileName.clear();
163             dwCookie = Start;
164         }
165         //The partial filename was followed by an TeX error, meaning the file doesn't exist.
166         //Don't push it on the stack, instead try to detect the error.
167         else if(strLine.startsWith('!')) {
168 // 				qCDebug(LOG_KILE_PARSER) << "oops!" << endl;
169             dwCookie = Start;
170             strPartialFileName.clear();
171             detectError(strLine, dwCookie);
172         }
173         else if(strLine.startsWith(QLatin1String("No file"))) {
174 // 				qCDebug(LOG_KILE_PARSER) << "No file: " << strLine << endl;
175             dwCookie = Start;
176             strPartialFileName.clear();
177             detectWarning(strLine, dwCookie);
178         }
179         //Partial filename still isn't complete.
180         else {
181 // 				qCDebug(LOG_KILE_PARSER) << "\tpartial file name, adding" << endl;
182             strPartialFileName = strPartialFileName + strLine.trimmed();
183         }
184         break;
185 
186     default:
187         break;
188     }
189 }
190 
updateFileStackHeuristic(const QString & strLine,short & dwCookie)191 void LaTeXOutputParser::updateFileStackHeuristic(const QString &strLine, short & dwCookie)
192 {
193     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::updateFileStackHeuristic()================";
194     //qCDebug(LOG_KILE_PARSER) << strLine << dwCookie;
195     static QString strPartialFileName;
196     bool expectFileName = (dwCookie == FileNameHeuristic);
197     int index = 0;
198 
199     // handle special case (bug fix for 101810)
200     if(expectFileName && strLine.length() > 0 && strLine[0] == ')') {
201         m_stackFile.push(LOFStackItem(strPartialFileName));
202         expectFileName = false;
203         dwCookie = Start;
204     }
205 
206     //scan for parentheses and grab filenames
207     for(int i = 0; i < strLine.length(); ++i) {
208         /*
209         We're expecting a filename. If a filename really ends at this position one of the following must be true:
210         	1) Next character is a space (indicating the end of a filename (yes, there can't be spaces in the
211         	path, this is a TeX limitation).
212         comment by tbraun: there is a workround \include{{"file name"}} according to http://groups.google.com/group/comp.text.tex/browse_thread/thread/af873534f0644e4f/cd7e0cdb61a8b837?lnk=st&q=include+space+tex#cd7e0cdb61a8b837,
213         but this is currently not supported by kile.
214         	2) We're at the end of the line, the filename is probably continued on the next line.
215         	3) The TeX was closed already, signalled by the ')'.
216         */
217 
218         bool isLastChar = (i+1 == strLine.length());
219         bool nextIsTerminator = isLastChar ? false : (strLine[i+1].isSpace() || strLine[i+1] == ')');
220 
221         if(expectFileName && (isLastChar || nextIsTerminator)) {
222             qCDebug(LOG_KILE_PARSER) << "Update the partial filename " << strPartialFileName;
223             strPartialFileName =  strPartialFileName + strLine.mid(index, i-index + 1);
224 
225             if(strPartialFileName.isEmpty()) { // nothing left to do here
226                 continue;
227             }
228 
229             //FIXME: improve these heuristics
230             if((isLastChar && (i < 78)) || nextIsTerminator || fileExists(strPartialFileName)) {
231                 m_stackFile.push(LOFStackItem(strPartialFileName));
232                 //qCDebug(LOG_KILE_PARSER) << "\tpushed (i = " << i << " length = " << strLine.length() << "): " << strPartialFileName;
233                 expectFileName = false;
234                 dwCookie = Start;
235             }
236             //Guess the filename is continued on the next line, only if the current strPartialFileName does not exist, see bug # 162899
237             else if(isLastChar) {
238                 if(fileExists(strPartialFileName)) {
239                     m_stackFile.push(LOFStackItem(strPartialFileName));
240                     qCDebug(LOG_KILE_PARSER) << "pushed (i = " << i << " length = " << strLine.length() << "): " << strPartialFileName << endl;
241                     expectFileName = false;
242                     dwCookie = Start;
243                 }
244                 else {
245                     qCDebug(LOG_KILE_PARSER) << "Filename spans more than one line." << endl;
246                     dwCookie = FileNameHeuristic;
247                 }
248             }
249             //bail out
250             else {
251                 dwCookie = Start;
252                 strPartialFileName.clear();
253                 expectFileName = false;
254             }
255         }
256         //TeX is opening a file
257         else if(strLine[i] == '(') {
258             //we need to extract the filename
259             expectFileName = true;
260             strPartialFileName.clear();
261             dwCookie = Start;
262 
263             //this is were the filename is supposed to start
264             index = i + 1;
265         }
266         //TeX is closing a file
267         else if(strLine[i] == ')') {
268             //qCDebug(LOG_KILE_PARSER) << "\tpopping : " << m_stackFile.top().file();
269             //If this filename was pushed on the stack by the reliable ":<+-" method, don't pop
270             //a ":<-" will follow. This helps in preventing unbalanced ')' from popping filenames
271             //from the stack too soon.
272             if(m_stackFile.count() > 1 && !m_stackFile.top().reliable()) {
273                 m_stackFile.pop();
274             }
275             else {
276                 qCDebug(LOG_KILE_PARSER) << "\t\toh no, forget about it!";
277             }
278         }
279     }
280 }
281 
282 
flushCurrentItem()283 void LaTeXOutputParser::flushCurrentItem()
284 {
285     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::flushCurrentItem()================" << endl;
286     int nItemType = m_currentItem.type();
287 
288     while( m_stackFile.count() > 0 && !fileExists(m_stackFile.top().file()) ) {
289         m_stackFile.pop();
290     }
291 
292     QString sourceFile = (m_stackFile.isEmpty()) ? QFileInfo(source()).fileName() : m_stackFile.top().file();
293     m_currentItem.setSource(sourceFile);
294     m_currentItem.setMainSourceFile(source());
295 
296     switch (nItemType) {
297     case itmError:
298         ++m_nErrors;
299         m_infoList->push_back(m_currentItem);
300         //qCDebug(LOG_KILE_PARSER) << "Flushing Error in" << m_currentItem.source() << "@" << m_currentItem.sourceLine() << " reported in line " << m_currentItem.outputLine() <<  endl;
301         break;
302 
303     case itmWarning:
304         ++m_nWarnings;
305         m_infoList->push_back(m_currentItem);
306         //qCDebug(LOG_KILE_PARSER) << "Flushing Warning in " << m_currentItem.source() << "@" << m_currentItem.sourceLine() << " reported in line " << m_currentItem.outputLine() << endl;
307         break;
308 
309     case itmBadBox:
310         ++m_nBadBoxes;
311         m_infoList->push_back(m_currentItem);
312         //qCDebug(LOG_KILE_PARSER) << "Flushing BadBox in " << m_currentItem.source() << "@" << m_currentItem.sourceLine() << " reported in line " << m_currentItem.outputLine() << endl;
313         break;
314 
315     default:
316         break;
317     }
318     m_currentItem.clear();
319 }
320 
detectError(const QString & strLine,short & dwCookie)321 bool LaTeXOutputParser::detectError(const QString & strLine, short &dwCookie)
322 {
323     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::detectError(" << strLine.length() << ")================" << endl;
324 
325     bool found = false, flush = false;
326 
327     static QRegExp reLaTeXError("^! LaTeX Error: (.*)$", Qt::CaseInsensitive);
328     static QRegExp rePDFLaTeXError("^Error: pdflatex (.*)$", Qt::CaseInsensitive);
329     static QRegExp reTeXError("^! (.*)\\.$");
330     static QRegExp reLineNumber("^l\\.([0-9]+)(.*)");
331 
332     switch (dwCookie) {
333     case Start :
334         if(reLaTeXError.indexIn(strLine) != -1) {
335             //qCDebug(LOG_KILE_PARSER) << "\tError : " <<  reLaTeXError.cap(1) << endl;
336             m_currentItem.setMessage(reLaTeXError.cap(1));
337             found = true;
338         }
339         else if(rePDFLaTeXError.indexIn(strLine) != -1) {
340             //qCDebug(LOG_KILE_PARSER) << "\tError : " <<  rePDFLaTeXError.cap(1) << endl;
341             m_currentItem.setMessage(rePDFLaTeXError.cap(1));
342             found = true;
343         }
344         else if(reTeXError.indexIn(strLine) != -1) {
345             //qCDebug(LOG_KILE_PARSER) << "\tError : " <<  reTeXError.cap(1) << endl;
346             m_currentItem.setMessage(reTeXError.cap(1));
347             found = true;
348         }
349         if(found) {
350             dwCookie = strLine.endsWith('.') ? LineNumber : Error;
351             m_currentItem.setOutputLine(GetCurrentOutputLine());
352         }
353         break;
354 
355     case Error :
356         //qCDebug(LOG_KILE_PARSER) << "\tError (cont'd): " << strLine << endl;
357         if(strLine.endsWith('.')) {
358             dwCookie = LineNumber;
359             m_currentItem.setMessage(m_currentItem.message() + strLine);
360         }
361         else if(GetCurrentOutputLine() - m_currentItem.outputLine() > 3) {
362             qWarning() << "\tBAILING OUT: error description spans more than three lines" << endl;
363             dwCookie = Start;
364             flush = true;
365         }
366         break;
367 
368     case LineNumber :
369         //qCDebug(LOG_KILE_PARSER) << "\tLineNumber " << endl;
370         if(reLineNumber.indexIn(strLine) != -1) {
371             dwCookie = Start;
372             flush = true;
373             //qCDebug(LOG_KILE_PARSER) << "\tline number: " << reLineNumber.cap(1) << endl;
374             m_currentItem.setSourceLine(reLineNumber.cap(1).toInt());
375             m_currentItem.setMessage(m_currentItem.message() + reLineNumber.cap(2));
376         }
377         else if(GetCurrentOutputLine() - m_currentItem.outputLine() > 10) {
378             dwCookie = Start;
379             flush = true;
380             qWarning() << "\tBAILING OUT: did not detect a TeX line number for an error" << endl;
381             m_currentItem.setSourceLine(0);
382         }
383         break;
384 
385     default :
386         break;
387     }
388 
389     if(found) {
390         m_currentItem.setType(itmError);
391         m_currentItem.setOutputLine(GetCurrentOutputLine());
392     }
393 
394     if(flush) {
395         flushCurrentItem();
396     }
397 
398     return found;
399 }
400 
detectWarning(const QString & strLine,short & dwCookie)401 bool LaTeXOutputParser::detectWarning(const QString & strLine, short &dwCookie)
402 {
403     //qCDebug(LOG_KILE_PARSER) << strLine << strLine.length();
404 
405     bool found = false, flush = false;
406     QString warning;
407 
408     static QRegExp reLaTeXWarning("^(((! )?(La|pdf)TeX)|Package|Class) .*Warning.*:(.*)", Qt::CaseInsensitive);
409     static QRegExp reNoFile("No file (.*)");
410     static QRegExp 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
411 
412     switch(dwCookie) {
413     //detect the beginning of a warning
414     case Start :
415         if(reLaTeXWarning.indexIn(strLine) != -1) {
416             warning = reLaTeXWarning.cap(5);
417             //qCDebug(LOG_KILE_PARSER) << "\tWarning found: " << warning << endl;
418 
419             found = true;
420             dwCookie = Start;
421 
422             m_currentItem.setMessage(warning);
423             m_currentItem.setOutputLine(GetCurrentOutputLine());
424 
425             //do we expect a line number?
426             flush = detectLaTeXLineNumber(warning, dwCookie, strLine.length());
427         }
428         else if(reNoFile.indexIn(strLine) != -1) {
429             found = true;
430             flush = true;
431             m_currentItem.setSourceLine(0);
432             m_currentItem.setMessage(reNoFile.cap(0));
433             m_currentItem.setOutputLine(GetCurrentOutputLine());
434         }
435         else if(reNoAsyFile.indexIn(strLine) != -1) {
436             found = true;
437             flush = true;
438             m_currentItem.setSourceLine(0);
439             m_currentItem.setMessage(reNoAsyFile.cap(0));
440             m_currentItem.setOutputLine(GetCurrentOutputLine());
441         }
442 
443         break;
444 
445     //warning spans multiple lines, detect the end
446     case Warning :
447         warning = m_currentItem.message() + strLine;
448         //qCDebug(LOG_KILE_PARSER) << "'\tWarning (cont'd) : " << warning << endl;
449         flush = detectLaTeXLineNumber(warning, dwCookie, strLine.length());
450         m_currentItem.setMessage(warning);
451         break;
452 
453     default:
454         break;
455     }
456 
457     if(found) {
458         m_currentItem.setType(itmWarning);
459         m_currentItem.setOutputLine(GetCurrentOutputLine());
460     }
461 
462     if(flush) {
463         flushCurrentItem();
464     }
465 
466     return found;
467 }
468 
detectLaTeXLineNumber(QString & warning,short & dwCookie,int len)469 bool LaTeXOutputParser::detectLaTeXLineNumber(QString & warning, short & dwCookie, int len)
470 {
471     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::detectLaTeXLineNumber(" << warning.length() << ")================" << endl;
472 
473     static QRegExp reLaTeXLineNumber("(.*) on input line ([0-9]+)\\.$", Qt::CaseInsensitive);
474     static QRegExp reInternationalLaTeXLineNumber("(.*)([0-9]+)\\.$", Qt::CaseInsensitive);
475     if((reLaTeXLineNumber.indexIn(warning) != -1) || (reInternationalLaTeXLineNumber.indexIn(warning) != -1)) {
476         //qCDebug(LOG_KILE_PARSER) << "een" << endl;
477         m_currentItem.setSourceLine(reLaTeXLineNumber.cap(2).toInt());
478         warning += reLaTeXLineNumber.cap(1);
479         dwCookie = Start;
480         return true;
481     }
482     else if(warning.endsWith('.')) {
483         //qCDebug(LOG_KILE_PARSER) << "twee" << endl;
484         m_currentItem.setSourceLine(0);
485         dwCookie = Start;
486         return true;
487     }
488     //bailing out, did not find a line number
489     else if((GetCurrentOutputLine() - m_currentItem.outputLine() > 4) || (len == 0)) {
490         //qCDebug(LOG_KILE_PARSER) << "drie current " << GetCurrentOutputLine() << " " <<  m_currentItem.outputLine() << " len " << len << endl;
491         m_currentItem.setSourceLine(0);
492         dwCookie = Start;
493         return true;
494     }
495     //error message is continued on the other line
496     else {
497         //qCDebug(LOG_KILE_PARSER) << "vier" << endl;
498         dwCookie = Warning;
499         return false;
500     }
501 }
502 
detectBadBox(const QString & strLine,short & dwCookie)503 bool LaTeXOutputParser::detectBadBox(const QString & strLine, short & dwCookie)
504 {
505     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::detectBadBox(" << strLine.length() << ")================" << endl;
506 
507     bool found = false, flush = false;
508     QString badbox;
509 
510     static QRegExp reBadBox("^(Over|Under)(full \\\\[hv]box .*)", Qt::CaseInsensitive);
511 
512     switch(dwCookie) {
513     case Start :
514         if(reBadBox.indexIn(strLine) != -1) {
515             found = true;
516             dwCookie = Start;
517             badbox = strLine;
518             flush = detectBadBoxLineNumber(badbox, dwCookie, strLine.length());
519             m_currentItem.setMessage(badbox);
520         }
521         break;
522 
523     case BadBox :
524         badbox = m_currentItem.message() + strLine;
525         flush = detectBadBoxLineNumber(badbox, dwCookie, strLine.length());
526         m_currentItem.setMessage(badbox);
527         break;
528 
529     default:
530         break;
531     }
532 
533     if(found) {
534         m_currentItem.setType(itmBadBox);
535         m_currentItem.setOutputLine(GetCurrentOutputLine());
536     }
537 
538     if(flush) {
539         flushCurrentItem();
540     }
541 
542     return found;
543 }
544 
detectBadBoxLineNumber(QString & strLine,short & dwCookie,int len)545 bool LaTeXOutputParser::detectBadBoxLineNumber(QString & strLine, short & dwCookie, int len)
546 {
547     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::detectBadBoxLineNumber(" << strLine.length() << ")================" << endl;
548 
549     static QRegExp reBadBoxLines("(.*) at lines ([0-9]+)--([0-9]+)", Qt::CaseInsensitive);
550     static QRegExp reBadBoxLine("(.*) at line ([0-9]+)", Qt::CaseInsensitive);
551     //Use the following only, if you know how to get the source line for it.
552     // This is not simple, as TeX is not reporting it.
553     static QRegExp reBadBoxOutput("(.*)has occurred while \\output is active^", Qt::CaseInsensitive);
554 
555     if(reBadBoxLines.indexIn(strLine) != -1) {
556         dwCookie = Start;
557         strLine = reBadBoxLines.cap(1);
558         int n1 = reBadBoxLines.cap(2).toInt();
559         int n2 = reBadBoxLines.cap(3).toInt();
560         m_currentItem.setSourceLine(n1 < n2 ? n1 : n2);
561         return true;
562     }
563     else if(reBadBoxLine.indexIn(strLine) != -1) {
564         dwCookie = Start;
565         strLine = reBadBoxLine.cap(1);
566         m_currentItem.setSourceLine(reBadBoxLine.cap(2).toInt());
567         //qCDebug(LOG_KILE_PARSER) << "\tBadBox@" << reBadBoxLine.cap(2) << "." << endl;
568         return true;
569     }
570     else if(reBadBoxOutput.indexIn(strLine) != -1) {
571         dwCookie = Start;
572         strLine = reBadBoxLines.cap(1);
573         m_currentItem.setSourceLine(0);
574         return true;
575     }
576     //bailing out, did not find a line number
577     else if((GetCurrentOutputLine() - m_currentItem.outputLine() > 3) || (len == 0)) {
578         dwCookie = Start;
579         m_currentItem.setSourceLine(0);
580         return true;
581     }
582     else {
583         dwCookie = BadBox;
584     }
585 
586     return false;
587 }
588 
parseLine(const QString & strLine,short dwCookie)589 short LaTeXOutputParser::parseLine(const QString & strLine, short dwCookie)
590 {
591     //qCDebug(LOG_KILE_PARSER) << "==LaTeXOutputParser::parseLine(" << strLine << dwCookie << strLine.length() << ")================" << endl;
592 
593     switch (dwCookie) {
594     case Start :
595         if(!(detectBadBox(strLine, dwCookie) || detectWarning(strLine, dwCookie) || detectError(strLine, dwCookie))) {
596             updateFileStack(strLine, dwCookie);
597         }
598         break;
599 
600     case Warning :
601         detectWarning(strLine, dwCookie);
602         break;
603 
604     case Error :
605     case LineNumber :
606         detectError(strLine, dwCookie);
607         break;
608 
609     case BadBox :
610         detectBadBox(strLine, dwCookie);
611         break;
612 
613     case FileName :
614     case FileNameHeuristic :
615         updateFileStack(strLine, dwCookie);
616         break;
617 
618     default:
619         dwCookie = Start;
620         break;
621     }
622 
623     return dwCookie;
624 }
625 
parse()626 ParserOutput* LaTeXOutputParser::parse()
627 {
628     LaTeXOutputParserOutput *parserOutput = new LaTeXOutputParserOutput();
629 
630     qCDebug(LOG_KILE_PARSER);
631 
632     m_infoList = &(parserOutput->infoList);
633     m_nErrors = m_nWarnings = m_nBadBoxes = m_nParens = 0;
634     m_stackFile.clear();
635     m_stackFile.push(LOFStackItem(QFileInfo(source()).fileName(), true));
636 
637     short sCookie = 0;
638     QString s;
639     QFile f(m_logFile);
640 
641     m_log.clear();
642     m_nOutputLines = 0;
643 
644     if(!f.open(QIODevice::ReadOnly)) {
645         parserOutput->problem = i18n("Cannot open log file; did you run LaTeX?");
646         return parserOutput;
647     }
648     m_infoList = &parserOutput->infoList;
649     QTextStream t(&f);
650     while(!t.atEnd()) {
651         if(!m_parserThread->shouldContinueDocumentParsing()) {
652             qCDebug(LOG_KILE_PARSER) << "stopping...";
653             delete(parserOutput);
654             f.close();
655             return Q_NULLPTR;
656         }
657         s = t.readLine();
658         sCookie = parseLine(s.trimmed(), sCookie);
659         ++m_nOutputLines;
660 
661         m_log += s + '\n';
662     }
663     f.close();
664 
665     parserOutput->nWarnings = m_nWarnings;
666     parserOutput->nErrors = m_nErrors;
667     parserOutput->nBadBoxes = m_nBadBoxes;
668     parserOutput->logFile = m_logFile;
669 
670     // for quick preview
671     if(!texfilename.isEmpty() && selrow >= 0 && docrow >= 0) {
672         updateInfoLists(texfilename, selrow, docrow);
673     }
674 
675     return parserOutput;
676 }
677 
updateInfoLists(const QString & texfilename,int selrow,int docrow)678 void LaTeXOutputParser::updateInfoLists(const QString &texfilename, int selrow, int docrow)
679 {
680     // get a short name for the original tex file
681     QString filename = "./" + QFileInfo(texfilename).fileName();
682 // 	setSource(texfilename);
683 
684     //print detailed error info
685     for(int i = 0; i < m_infoList->count(); ++i) {
686         // perhaps correct filename and line number in OutputInfo
687         OutputInfo &info = (*m_infoList)[i];
688         info.setSource(filename);
689 
690         int linenumber = selrow + info.sourceLine() - docrow;
691         if(linenumber < 0) {
692             linenumber = 0;
693         }
694         info.setSourceLine(linenumber);
695     }
696 }
697 
698 
699 
700 /** Return number of errors etc. found in log-file. */
getErrorCount(int * errors,int * warnings,int * badboxes)701 void LaTeXOutputParser::getErrorCount(int *errors, int *warnings, int *badboxes)
702 {
703     *errors = m_nErrors;
704     *warnings = m_nWarnings;
705     *badboxes = m_nBadBoxes;
706 }
707 
GetCurrentOutputLine() const708 int LaTeXOutputParser::GetCurrentOutputLine() const
709 {
710     return m_nOutputLines;
711 }
712 
setSource(const QString & src)713 void LaTeXOutputParser::setSource(const QString &src)
714 {
715     m_source = src;
716     m_srcPath = QFileInfo(src).path();
717 }
718 
719 }
720