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