File indexing completed on 2024-05-19 05:05:47
0001 /*************************************************************************** 0002 * SPDX-License-Identifier: GPL-2.0-or-later 0003 * * 0004 * SPDX-FileCopyrightText: 2004-2019 Thomas Fischer <fischer@unix-ag.uni-kl.de> 0005 * * 0006 * This program is free software; you can redistribute it and/or modify * 0007 * it under the terms of the GNU General Public License as published by * 0008 * the Free Software Foundation; either version 2 of the License, or * 0009 * (at your option) any later version. * 0010 * * 0011 * This program is distributed in the hope that it will be useful, * 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0014 * GNU General Public License for more details. * 0015 * * 0016 * You should have received a copy of the GNU General Public License * 0017 * along with this program; if not, see <https://www.gnu.org/licenses/>. * 0018 ***************************************************************************/ 0019 0020 #include "checkbibtex.h" 0021 0022 #include <QApplication> 0023 #include <QBuffer> 0024 #include <QTextStream> 0025 #include <QRegularExpression> 0026 0027 #include <KLocalizedString> 0028 #include <KMessageBox> 0029 0030 #include <File> 0031 #include <Entry> 0032 #include <Element> 0033 #include <Macro> 0034 #include <FileExporterBibTeXOutput> 0035 0036 CheckBibTeX::CheckBibTeXResult CheckBibTeX::checkBibTeX(QSharedPointer<Element> &element, const File *file, QWidget *parent) 0037 { 0038 /// only entries are supported, no macros, preambles, ... 0039 QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); 0040 if (entry.isNull()) 0041 return CheckBibTeXResult::InvalidData; 0042 else 0043 return checkBibTeX(entry, file, parent); 0044 } 0045 0046 CheckBibTeX::CheckBibTeXResult CheckBibTeX::checkBibTeX(QSharedPointer<Entry> &entry, const File *file, QWidget *parent) 0047 { 0048 /// disable GUI under process 0049 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); 0050 0051 /// use a dummy BibTeX file to collect all elements necessary for check 0052 File dummyFile; 0053 0054 /// create temporary entry to work with 0055 dummyFile << entry; 0056 0057 /// fetch and inser crossref'ed entry 0058 QString crossRefStr; 0059 Value crossRefVal = entry->value(Entry::ftCrossRef); 0060 if (!crossRefVal.isEmpty() && file != nullptr) { 0061 crossRefStr = PlainTextValue::text(crossRefVal); 0062 QSharedPointer<Entry> crossRefDest = file->containsKey(crossRefStr, File::ElementType::Entry).dynamicCast<Entry>(); 0063 if (!crossRefDest.isNull()) 0064 dummyFile << crossRefDest; 0065 else 0066 crossRefStr.clear(); /// memorize crossref'ing failed 0067 } 0068 0069 /// include all macro definitions, in case they are referenced 0070 if (file != nullptr) 0071 for (const auto &element : const_cast<const File &>(*file)) 0072 if (Macro::isMacro(*element)) 0073 dummyFile << element; 0074 0075 /// run special exporter to get BibTeX's output 0076 QStringList bibtexOutput; 0077 QByteArray ba; 0078 QBuffer buffer(&ba); 0079 buffer.open(QIODevice::WriteOnly); 0080 FileExporterBibTeXOutput exporter(FileExporterBibTeXOutput::OutputType::BibTeXLogFile, parent); 0081 QObject::connect(&exporter, &FileExporterBibTeXOutput::processStandardOut, parent, [&bibtexOutput](const QString & line) { 0082 bibtexOutput.append(line); 0083 }); 0084 QObject::connect(&exporter, &FileExporterBibTeXOutput::processStandardError, parent, [&bibtexOutput](const QString & line) { 0085 bibtexOutput.append(line); 0086 }); 0087 bool exporterResult = exporter.save(&buffer, &dummyFile); 0088 buffer.close(); 0089 0090 if (!exporterResult) { 0091 QApplication::restoreOverrideCursor(); 0092 KMessageBox::errorList(parent, i18n("Running BibTeX failed.\n\nSee the following output to trace the error:"), bibtexOutput, i18n("Running BibTeX failed.")); 0093 return CheckBibTeXResult::FailedToCheck; 0094 } 0095 0096 /// define variables how to parse BibTeX's output 0097 static const QString warningStart = QStringLiteral("Warning--"); 0098 static const QRegularExpression warningEmptyField(QStringLiteral("empty (\\w+) in ")); 0099 static const QRegularExpression warningEmptyField2(QStringLiteral("empty (\\w+) or (\\w+) in ")); 0100 static const QRegularExpression warningThereIsBut(QStringLiteral("there's a (\\w+) but no (\\w+) in")); 0101 static const QRegularExpression warningCantUseBoth(QStringLiteral("can't use both (\\w+) and (\\w+) fields")); 0102 static const QRegularExpression warningSort2(QStringLiteral("to sort, need (\\w+) or (\\w+) in ")); 0103 static const QRegularExpression warningSort3(QStringLiteral("to sort, need (\\w+), (\\w+), or (\\w+) in ")); 0104 static const QRegularExpression errorLine(QStringLiteral("---line (\\d+)")); 0105 0106 /// go line-by-line through BibTeX output and collect warnings/errors 0107 QStringList warnings; 0108 QString errorPlainText; 0109 for (const QString &line : const_cast<const QStringList &>(bibtexOutput)) { 0110 QRegularExpressionMatch match; 0111 if ((match = errorLine.match(line)).hasMatch()) { 0112 buffer.open(QIODevice::ReadOnly); 0113 QTextStream ts(&buffer); 0114 bool ok = false; 0115 for (int i = match.captured(1).toInt(&ok); ok && i > 1; --i) { 0116 errorPlainText = ts.readLine(); 0117 buffer.close(); 0118 } 0119 } else if (line.startsWith(QStringLiteral("Warning--"))) { 0120 /// is a warning ... 0121 0122 if ((match = warningEmptyField.match(line)).hasMatch()) { 0123 /// empty/missing field 0124 warnings << i18n("Field <b>%1</b> is empty", match.captured(1)); 0125 } else if ((match = warningEmptyField2.match(line)).hasMatch()) { 0126 /// two empty/missing fields 0127 warnings << i18n("Fields <b>%1</b> and <b>%2</b> are empty, but at least one is required", match.captured(1), match.captured(2)); 0128 } else if ((match = warningThereIsBut.match(line)).hasMatch()) { 0129 /// there is a field which exists but another does not exist 0130 warnings << i18n("Field <b>%1</b> exists, but <b>%2</b> does not exist", match.captured(1), match.captured(2)); 0131 } else if ((match = warningCantUseBoth.match(line)).hasMatch()) { 0132 /// there are two conflicting fields, only one may be used 0133 warnings << i18n("Fields <b>%1</b> and <b>%2</b> cannot be used at the same time", match.captured(1), match.captured(2)); 0134 } else if ((match = warningSort2.match(line)).hasMatch()) { 0135 /// one out of two fields missing for sorting 0136 warnings << i18n("Fields <b>%1</b> or <b>%2</b> are required to sort entry", match.captured(1), match.captured(2)); 0137 } else if ((match = warningSort3.match(line)).hasMatch()) { 0138 /// one out of three fields missing for sorting 0139 warnings << i18n("Fields <b>%1</b>, <b>%2</b>, <b>%3</b> are required to sort entry", match.captured(1), match.captured(2), match.captured(3)); 0140 } else { 0141 /// generic/unknown warning 0142 warnings << i18n("Unknown warning: %1", line.mid(warningStart.length())); 0143 } 0144 } 0145 } 0146 0147 CheckBibTeXResult result = CheckBibTeXResult::NoProblem; 0148 QApplication::restoreOverrideCursor(); 0149 if (!errorPlainText.isEmpty()) { 0150 result = CheckBibTeXResult::BibTeXWarning; 0151 KMessageBox::information(parent, i18n("<qt><p>The following error was found:</p><pre>%1</pre></qt>", errorPlainText), i18n("Errors found")); 0152 } else if (!warnings.isEmpty()) { 0153 KMessageBox::informationList(parent, i18n("The following warnings were found:"), warnings, i18n("Warnings found")); 0154 result = CheckBibTeXResult::BibTeXError; 0155 } else 0156 KMessageBox::information(parent, i18n("No warnings or errors were found.%1", crossRefStr.isEmpty() ? QString() : i18n("\n\nSome fields missing in this entry were taken from the crossref'ed entry '%1'.", crossRefStr)), i18n("No Errors or Warnings")); 0157 0158 return result; 0159 }