Warning, file /office/kile/src/parser/latexparser.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 * 2005-2007 by Holger Danielsson (holger.danielsson@versanet.de) * 0004 * 2006-2019 by Michel Ludwig (michel.ludwig@kdemail.net) * 0005 ***********************************************************************************/ 0006 0007 /*************************************************************************** 0008 * * 0009 * This program is free software; you can redistribute it and/or modify * 0010 * it under the terms of the GNU General Public License as published by * 0011 * the Free Software Foundation; either version 2 of the License, or * 0012 * (at your option) any later version. * 0013 * * 0014 ***************************************************************************/ 0015 0016 #include "latexparser.h" 0017 0018 #include <QFileInfo> 0019 #include <QRegExp> 0020 0021 #include <KLocalizedString> 0022 0023 #include "codecompletion.h" 0024 #include "parserthread.h" 0025 0026 namespace KileParser { 0027 0028 LaTeXParserInput::LaTeXParserInput(const QUrl &url, const QStringList &textLines, 0029 KileDocument::Extensions *extensions, 0030 const QMap<QString, KileStructData>& dictStructLevel, 0031 bool showSectioningLabels, 0032 bool showStructureTodo) 0033 : ParserInput(url), 0034 textLines(textLines), 0035 extensions(extensions), 0036 // make a copy here as otherwise the parsing of a document that is being closed 0037 // can lead to a crash: 0038 dictStructLevel(dictStructLevel), 0039 showSectioningLabels(showSectioningLabels), 0040 showStructureTodo(showStructureTodo) 0041 { 0042 } 0043 0044 LaTeXParserOutput::LaTeXParserOutput() 0045 : bIsRoot(false) 0046 { 0047 } 0048 0049 LaTeXParserOutput::~LaTeXParserOutput() 0050 { 0051 qCDebug(LOG_KILE_PARSER); 0052 } 0053 0054 LaTeXParser::LaTeXParser(ParserThread *parserThread, LaTeXParserInput *input, 0055 QObject *parent) 0056 : Parser(parserThread, parent), 0057 m_extensions(input->extensions), 0058 m_textLines(input->textLines), 0059 m_dictStructLevel(input->dictStructLevel), 0060 m_showSectioningLabels(input->showSectioningLabels), 0061 m_showStructureTodo(input->showStructureTodo) 0062 { 0063 } 0064 0065 LaTeXParser::~LaTeXParser() 0066 { 0067 qCDebug(LOG_KILE_PARSER); 0068 } 0069 0070 BracketResult LaTeXParser::matchBracket(const QStringList& textLines, int &l, int &pos) 0071 { 0072 BracketResult result; 0073 TodoResult todo; 0074 0075 if((getTextLine(textLines, l))[pos] == '[') { 0076 result.option = Parser::matchBracket(textLines, '[', l, pos); 0077 while(l < textLines.size()) { 0078 int p = processTextline(getTextLine(textLines, l), todo).indexOf('{', pos); 0079 if(p != -1) { 0080 pos = p; 0081 break; 0082 } 0083 else { 0084 pos = 0; 0085 ++l; 0086 } 0087 } 0088 } 0089 0090 if((getTextLine(textLines, l))[pos] == '{') { 0091 result.line = l; 0092 result.col = pos; 0093 result.value = Parser::matchBracket(textLines, '{', l, pos); 0094 } 0095 0096 return result; 0097 } 0098 0099 // Skip any number of white spaces in a line 0100 int skipWS(const QString &s, int pos) { 0101 while(pos + 1 < s.length() && s.at(pos + 1).isSpace()) { 0102 ++pos; 0103 } 0104 return pos; 0105 } 0106 0107 //FIXME: this has to be completely rewritten! 0108 ParserOutput* LaTeXParser::parse() 0109 { 0110 LaTeXParserOutput *parserOutput = new LaTeXParserOutput(); 0111 0112 qCDebug(LOG_KILE_PARSER) << m_textLines; 0113 0114 QMap<QString,KileStructData>::const_iterator it; 0115 static QRegExp reCommand("(\\\\[a-zA-Z]+)\\s*\\*?\\s*(\\{|\\[)"); 0116 static QRegExp reRoot("\\\\documentclass|\\\\documentstyle"); 0117 static QRegExp reBD("\\\\begin\\s*\\{\\s*document\\s*\\}"); 0118 static QRegExp reReNewCommand("\\\\renewcommand.*$"); 0119 static QRegExp reNumOfParams("\\s*\\[([1-9]+)\\]"); 0120 static QRegExp reNumOfOptParams("\\s*\\[([1-9]+)\\]\\s*\\[([^\\{]*)\\]"); // the quantifier * isn't used by mistake, because also emtpy optional brackets are correct. 0121 0122 int bd = 0, tagLine = 0, tagCol = 0; 0123 int tagStartLine = 0, tagStartCol = 0; 0124 BracketResult result; 0125 QString m, s, shorthand; 0126 bool foundBD = false; // found \begin { document } 0127 bool fireSuspended; // found an item, but it should not be fired (this time) 0128 TodoResult todo; 0129 0130 // emit(parsingStarted(m_doc->lines())); 0131 for(int i = 0; i < m_textLines.size(); ++i) { 0132 if(!m_parserThread->shouldContinueDocumentParsing()) { 0133 qCDebug(LOG_KILE_PARSER) << "stopping..."; 0134 delete(parserOutput); 0135 return Q_NULLPTR; 0136 } 0137 0138 // emit(parsingUpdate(i)); 0139 0140 int tagStart = 0, tagEnd = 0; 0141 bool fire = true; //whether or not we should emit a foundItem signal 0142 s = processTextline(getTextLine(m_textLines, i), todo); 0143 if(todo.type != -1 && m_showStructureTodo) { 0144 QString folder = (todo.type == KileStruct::ToDo) ? "todo" : "fixme"; 0145 parserOutput->structureViewItems.push_back(new StructureViewItem(todo.comment, i+1, todo.colComment, todo.type, KileStruct::Object, i+1, todo.colTag, QString(), folder)); 0146 } 0147 0148 0149 if(s.isEmpty()) { 0150 continue; 0151 } 0152 0153 //ignore renewcommands 0154 s.remove(reReNewCommand); 0155 0156 //find all commands in this line 0157 while(tagStart != -1) { 0158 if((!foundBD) && ((bd = s.indexOf(reBD, tagEnd)) != -1)) { 0159 qCDebug(LOG_KILE_PARSER) << "\tfound \\begin{document}"; 0160 foundBD = true; 0161 parserOutput->preamble.clear(); 0162 //FIXME: improve this 0163 if(bd == 0) { 0164 if(i - 1 >= 0) { 0165 for(int j = 0; j <= i - 1; ++j) { 0166 parserOutput->preamble += getTextLine(m_textLines, j) + '\n'; 0167 } 0168 } 0169 } 0170 else { 0171 if(i - 1 >= 0) { 0172 for(int j = 0; j <= i - 1; ++j) { 0173 parserOutput->preamble += getTextLine(m_textLines, j) + '\n'; 0174 } 0175 } 0176 parserOutput->preamble += getTextLine(m_textLines, i).left(bd) + '\n'; 0177 } 0178 } 0179 0180 if((!foundBD) && (s.indexOf(reRoot, tagEnd) != -1)) { 0181 qCDebug(LOG_KILE_PARSER) << "\tsetting m_bIsRoot to true"; 0182 tagEnd += reRoot.cap(0).length(); 0183 parserOutput->bIsRoot = true; 0184 } 0185 0186 tagStart = reCommand.indexIn(s, tagEnd); 0187 m.clear(); 0188 shorthand.clear(); 0189 0190 if(tagStart != -1) { 0191 tagEnd = tagStart + reCommand.cap(0).length()-1; 0192 0193 //look up the command in the dictionary 0194 it = m_dictStructLevel.constFind(reCommand.cap(1)); 0195 0196 //if it is was a structure element, find the title (or label) 0197 if(it != m_dictStructLevel.constEnd()) { 0198 tagLine = i+1; 0199 tagCol = tagEnd+1; 0200 tagStartLine = tagLine; 0201 tagStartCol = tagStart+1; 0202 0203 if(reCommand.cap(1) != "\\frame") { 0204 result = matchBracket(m_textLines, i, tagEnd); 0205 m = result.value.trimmed(); 0206 shorthand = result.option.trimmed(); 0207 if(i >= tagLine) { //matching brackets spanned multiple lines 0208 s = getTextLine(m_textLines, i); 0209 } 0210 if(result.line > 0 || result.col > 0) { 0211 tagLine = result.line + 1; 0212 tagCol = result.col + 1; 0213 } 0214 //qCDebug(LOG_KILE_PARSER) << "\tgrabbed: " << reCommand.cap(1) << "[" << shorthand << "]{" << m << "}"; 0215 } 0216 else { 0217 m = i18n("Frame"); 0218 } 0219 } 0220 0221 //title (or label) found, add the element to the listview 0222 if(!m.isNull()) { 0223 // no problems so far ... 0224 fireSuspended = false; 0225 0226 // remove trailing ./ 0227 if((*it).type & (KileStruct::Input | KileStruct::Graphics)) { 0228 if(m.left(2) == "./") { 0229 m = m.mid(2, m.length() - 2); 0230 } 0231 } 0232 // update parameter for environments, because only 0233 // floating environments and beamer frames are passed 0234 if ( (*it).type == KileStruct::BeginEnv ) 0235 { 0236 if ( m=="figure" || m=="figure*" || m=="table" || m=="table*" ) 0237 { 0238 it = m_dictStructLevel.constFind("\\begin{" + m +'}'); 0239 } 0240 else if(m == "asy") { 0241 it = m_dictStructLevel.constFind("\\begin{" + m +'}'); 0242 parserOutput->asyFigures.append(m); 0243 } 0244 else if(m == "frame") { 0245 const QString untitledFrameDisplayName = i18n("Frame"); 0246 it = m_dictStructLevel.constFind("\\begin{frame}"); 0247 tagEnd = skipWS(s, tagEnd); 0248 // the frame may have [fragile] modifier 0249 if(tagEnd + 1 < s.size() && s.at(tagEnd + 1) == '[') { 0250 tagEnd++; 0251 Parser::matchBracket(m_textLines, '[', i, tagEnd); 0252 } 0253 tagEnd = skipWS(s, tagEnd); 0254 if(tagEnd + 1 < s.size() && s.at(tagEnd + 1) == '{') { 0255 tagEnd++; 0256 result = matchBracket(m_textLines, i, tagEnd); 0257 m = result.value.trimmed(); 0258 if(m.isEmpty()) { 0259 m = untitledFrameDisplayName; 0260 } 0261 } 0262 else { 0263 m = untitledFrameDisplayName; 0264 } 0265 } 0266 else if(m=="block" || m=="exampleblock" || m=="alertblock") { 0267 const QString untitledBlockDisplayName = i18n("Untitled Block"); 0268 it = m_dictStructLevel.constFind("\\begin{block}"); 0269 tagEnd = skipWS(s, tagEnd); 0270 if(tagEnd+1 < s.size() && s.at(tagEnd+1) == '{') { 0271 tagEnd++; 0272 result = matchBracket(m_textLines, i, tagEnd); 0273 m = result.value.trimmed(); 0274 if(m.isEmpty()) { 0275 m = untitledBlockDisplayName; 0276 } 0277 } 0278 else { 0279 m = untitledBlockDisplayName; 0280 } 0281 } 0282 else { 0283 fireSuspended = true; // only floats and beamer frames, no other environments 0284 } 0285 } 0286 0287 // tell structure view that a floating environment or a beamer frame must be closed 0288 else if ( (*it).type == KileStruct::EndEnv ) 0289 { 0290 if ( m=="figure" || m== "figure*" || m=="table" || m=="table*" || m=="asy") 0291 { 0292 it = m_dictStructLevel.constFind("\\end{float}"); 0293 } 0294 else if(m == "frame") { 0295 it = m_dictStructLevel.constFind("\\end{frame}"); 0296 } 0297 else { 0298 fireSuspended = true; // only floats, no other environments 0299 } 0300 } 0301 // sectioning commands 0302 else if((*it).type == KileStruct::Sect) { 0303 if(!shorthand.isEmpty()) { 0304 m = shorthand; 0305 } 0306 } 0307 0308 // update the label list 0309 else if((*it).type == KileStruct::Label) { 0310 parserOutput->labels.append(m); 0311 // label entry as child of sectioning 0312 if(m_showSectioningLabels) { 0313 parserOutput->structureViewItems.push_back(new StructureViewItem(m, tagLine, tagCol, KileStruct::Label, KileStruct::Object, tagStartLine, tagStartCol, "label", "root")); 0314 fireSuspended = true; 0315 } 0316 } 0317 0318 // update the references list 0319 else if((*it).type == KileStruct::Reference) { 0320 // m_references.append(m); 0321 //fireSuspended = true; // don't emit references 0322 } 0323 0324 // update the dependencies 0325 else if((*it).type == KileStruct::Input) { 0326 // \input- or \include-commands can be used without extension. So we check 0327 // if an extension exists. If not the default extension is added 0328 // ( LaTeX reference says that this is '.tex'). This assures that 0329 // all files, which are listed in the structure view, have an extension. 0330 QString ext = QFileInfo(m).completeSuffix(); 0331 if(ext.isEmpty()) { 0332 m += m_extensions->latexDocumentDefault(); 0333 } 0334 parserOutput->deps.append(m); 0335 } 0336 0337 // update the referenced Bib files 0338 else if((*it).type == KileStruct::Bibliography) { 0339 qCDebug(LOG_KILE_PARSER) << "===TeXInfo::updateStruct()===appending Bibiliograph file(s) " << m; 0340 0341 const QStringList bibs = m.split(','); 0342 0343 // assure that all files have an extension 0344 for(QString biblio : bibs) { 0345 biblio = biblio.trimmed(); 0346 { 0347 QString ext = QFileInfo(biblio).suffix(); 0348 if(ext.isEmpty()) { 0349 biblio += m_extensions->bibtexDefault(); 0350 } 0351 } 0352 parserOutput->bibliography.append(biblio); 0353 if(biblio.left(2) == "./") { 0354 biblio = biblio.mid(2, biblio.length() - 2); 0355 } 0356 parserOutput->deps.append(biblio); 0357 parserOutput->structureViewItems.push_back(new StructureViewItem(biblio, tagLine, tagCol, (*it).type, (*it).level, tagStartLine, tagStartCol, (*it).pix, (*it).folder)); 0358 } 0359 fire = false; 0360 } 0361 0362 // update the bibitem list 0363 else if((*it).type == KileStruct::BibItem) { 0364 //qCDebug(LOG_KILE_PARSER) << "\tappending bibitem " << m; 0365 parserOutput->bibItems.append(m); 0366 } 0367 0368 // update the package list 0369 else if((*it).type == KileStruct::Package) { 0370 QStringList pckgs = m.split(','); 0371 for(int p = 0; p < pckgs.count(); ++p) { 0372 QString package = pckgs[p].trimmed(); 0373 if(!package.isEmpty()) { 0374 parserOutput->packages.append(package); 0375 } 0376 } 0377 fire = false; 0378 } 0379 0380 // newcommand found, add it to the newCommands list 0381 else if((*it).type & (KileStruct::NewCommand | KileStruct::NewEnvironment)) { 0382 QString mandArgs; 0383 0384 //find how many parameters this command takes 0385 if(s.indexOf(reNumOfParams, tagEnd + 1) != -1) { 0386 QString optArg; 0387 bool ok; 0388 int noo = reNumOfParams.cap(1).toInt(&ok); 0389 0390 if(ok) { 0391 if(s.indexOf(reNumOfOptParams, tagEnd + 1) != -1) { 0392 qCDebug(LOG_KILE_PARSER) << "Opt param is " << reNumOfOptParams.cap(2) << "%EOL"; 0393 noo--; // if we have an opt argument, we have one mandatory argument less, and noo=0 can't occur because then latex complains (and we don't macht them with reNumOfParams either) 0394 optArg = '[' + reNumOfOptParams.cap(2) + ']'; 0395 } 0396 0397 for(int noo_index = 0; noo_index < noo; ++noo_index) { 0398 mandArgs += '{' + s_bullet + '}'; 0399 } 0400 0401 } 0402 if(!optArg.isEmpty()) { 0403 if((*it).type == KileStruct::NewEnvironment) { 0404 parserOutput->newCommands.append(QString("\\begin{%1}%2%3").arg(m, optArg, mandArgs)); 0405 } 0406 else { 0407 parserOutput->newCommands.append(m + optArg + mandArgs); 0408 } 0409 } 0410 } 0411 if((*it).type == KileStruct::NewEnvironment) { 0412 parserOutput->newCommands.append(QString("\\begin{%1}%3").arg(m, mandArgs)); 0413 parserOutput->newCommands.append(QString("\\end{%1}").arg(m)); 0414 } 0415 else { 0416 parserOutput->newCommands.append(m + mandArgs); 0417 } 0418 //FIXME set tagEnd to the end of the command definition 0419 break; 0420 } 0421 // and some other commands, which don't need special actions: 0422 // \caption, ... 0423 0424 // qCDebug(LOG_KILE_PARSER) << "\t\temitting: " << m; 0425 if(fire && !fireSuspended) { 0426 parserOutput->structureViewItems.push_back(new StructureViewItem(m, tagLine, tagCol, (*it).type, (*it).level, tagStartLine, tagStartCol, (*it).pix, (*it).folder)); 0427 } 0428 } //if m 0429 } // if tagStart 0430 } // while tagStart 0431 } //for 0432 0433 qCDebug(LOG_KILE_PARSER) << "done"; 0434 return parserOutput; 0435 } 0436 0437 0438 } 0439