File indexing completed on 2024-06-02 05:09:33
0001 /*************************************************************************** 0002 * SPDX-License-Identifier: GPL-2.0-or-later 0003 * * 0004 * SPDX-FileCopyrightText: 2004-2023 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 "onlinesearchspringerlink.h" 0021 0022 #ifdef HAVE_QTWIDGETS 0023 #include <QFormLayout> 0024 #include <QSpinBox> 0025 #include <QLineEdit> 0026 #include <QLabel> 0027 #endif // HAVE_QTWIDGETS 0028 #include <QRegularExpression> 0029 #include <QNetworkRequest> 0030 #include <QNetworkAccessManager> 0031 #include <QNetworkReply> 0032 #include <QUrlQuery> 0033 #include <QXmlStreamReader> 0034 0035 #ifdef HAVE_KF 0036 #include <KLocalizedString> 0037 #include <KConfigGroup> 0038 #else // HAVE_KF 0039 #define i18n(text) QObject::tr(text) 0040 #endif // HAVE_KF 0041 0042 #include <KBibTeX> 0043 #include <Encoder> 0044 #include <FileImporterBibTeX> 0045 #include "internalnetworkaccessmanager.h" 0046 #include "onlinesearchabstract_p.h" 0047 #include "logging_networking.h" 0048 0049 #ifdef HAVE_QTWIDGETS 0050 /** 0051 * @author Thomas Fischer <fischer@unix-ag.uni-kl.de> 0052 */ 0053 class OnlineSearchSpringerLink::Form : public OnlineSearchAbstract::Form 0054 { 0055 Q_OBJECT 0056 0057 private: 0058 QString configGroupName; 0059 0060 void loadState() { 0061 KConfigGroup configGroup(d->config, configGroupName); 0062 lineEditFreeText->setText(configGroup.readEntry(QStringLiteral("free"), QString())); 0063 lineEditTitle->setText(configGroup.readEntry(QStringLiteral("title"), QString())); 0064 lineEditBookTitle->setText(configGroup.readEntry(QStringLiteral("bookTitle"), QString())); 0065 lineEditAuthorEditor->setText(configGroup.readEntry(QStringLiteral("authorEditor"), QString())); 0066 lineEditYear->setText(configGroup.readEntry(QStringLiteral("year"), QString())); 0067 numResultsField->setValue(configGroup.readEntry(QStringLiteral("numResults"), 10)); 0068 } 0069 0070 public: 0071 QLineEdit *lineEditFreeText, *lineEditTitle, *lineEditBookTitle, *lineEditAuthorEditor, *lineEditYear; 0072 QSpinBox *numResultsField; 0073 0074 Form(QWidget *parent) 0075 : OnlineSearchAbstract::Form(parent), configGroupName(QStringLiteral("Search Engine SpringerLink")) { 0076 QFormLayout *layout = new QFormLayout(this); 0077 layout->setContentsMargins(0, 0, 0, 0); 0078 0079 lineEditFreeText = new QLineEdit(this); 0080 lineEditFreeText->setClearButtonEnabled(true); 0081 QLabel *label = new QLabel(i18n("Free Text:"), this); 0082 label->setBuddy(lineEditFreeText); 0083 layout->addRow(label, lineEditFreeText); 0084 connect(lineEditFreeText, &QLineEdit::returnPressed, this, &OnlineSearchSpringerLink::Form::returnPressed); 0085 0086 lineEditTitle = new QLineEdit(this); 0087 lineEditTitle->setClearButtonEnabled(true); 0088 label = new QLabel(i18n("Title:"), this); 0089 label->setBuddy(lineEditTitle); 0090 layout->addRow(label, lineEditTitle); 0091 connect(lineEditTitle, &QLineEdit::returnPressed, this, &OnlineSearchSpringerLink::Form::returnPressed); 0092 0093 lineEditBookTitle = new QLineEdit(this); 0094 lineEditBookTitle->setClearButtonEnabled(true); 0095 label = new QLabel(i18n("Book/Journal title:"), this); 0096 label->setBuddy(lineEditBookTitle); 0097 layout->addRow(label, lineEditBookTitle); 0098 connect(lineEditBookTitle, &QLineEdit::returnPressed, this, &OnlineSearchSpringerLink::Form::returnPressed); 0099 0100 lineEditAuthorEditor = new QLineEdit(this); 0101 lineEditAuthorEditor->setClearButtonEnabled(true); 0102 label = new QLabel(i18n("Author or Editor:"), this); 0103 label->setBuddy(lineEditAuthorEditor); 0104 layout->addRow(label, lineEditAuthorEditor); 0105 connect(lineEditAuthorEditor, &QLineEdit::returnPressed, this, &OnlineSearchSpringerLink::Form::returnPressed); 0106 0107 lineEditYear = new QLineEdit(this); 0108 lineEditYear->setClearButtonEnabled(true); 0109 label = new QLabel(i18n("Year:"), this); 0110 label->setBuddy(lineEditYear); 0111 layout->addRow(label, lineEditYear); 0112 connect(lineEditYear, &QLineEdit::returnPressed, this, &OnlineSearchSpringerLink::Form::returnPressed); 0113 0114 numResultsField = new QSpinBox(this); 0115 label = new QLabel(i18n("Number of Results:"), this); 0116 label->setBuddy(numResultsField); 0117 layout->addRow(label, numResultsField); 0118 numResultsField->setMinimum(3); 0119 numResultsField->setMaximum(100); 0120 0121 lineEditFreeText->setFocus(Qt::TabFocusReason); 0122 0123 loadState(); 0124 } 0125 0126 bool readyToStart() const override { 0127 return !(lineEditFreeText->text().isEmpty() && lineEditTitle->text().isEmpty() && lineEditBookTitle->text().isEmpty() && lineEditAuthorEditor->text().isEmpty()); 0128 } 0129 0130 void copyFromEntry(const Entry &entry) override { 0131 lineEditTitle->setText(PlainTextValue::text(entry[Entry::ftTitle])); 0132 QString bookTitle = PlainTextValue::text(entry[Entry::ftBookTitle]); 0133 if (bookTitle.isEmpty()) 0134 bookTitle = PlainTextValue::text(entry[Entry::ftJournal]); 0135 lineEditBookTitle->setText(bookTitle); 0136 lineEditAuthorEditor->setText(d->authorLastNames(entry).join(QStringLiteral(" "))); 0137 } 0138 0139 void saveState() { 0140 KConfigGroup configGroup(d->config, configGroupName); 0141 configGroup.writeEntry(QStringLiteral("free"), lineEditFreeText->text()); 0142 configGroup.writeEntry(QStringLiteral("title"), lineEditTitle->text()); 0143 configGroup.writeEntry(QStringLiteral("bookTitle"), lineEditBookTitle->text()); 0144 configGroup.writeEntry(QStringLiteral("authorEditor"), lineEditAuthorEditor->text()); 0145 configGroup.writeEntry(QStringLiteral("year"), lineEditYear->text()); 0146 configGroup.writeEntry(QStringLiteral("numResults"), numResultsField->value()); 0147 d->config->sync(); 0148 } 0149 }; 0150 #endif // HAVE_QTWIDGETS 0151 0152 class OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate 0153 { 0154 private: 0155 static const QString springerLinkQueryBaseUrl; 0156 0157 public: 0158 static const QString springerMetadataKey; 0159 #ifdef HAVE_QTWIDGETS 0160 OnlineSearchSpringerLink::Form *form; 0161 #endif // HAVE_QTWIDGETS 0162 0163 OnlineSearchSpringerLinkPrivate(OnlineSearchSpringerLink *) 0164 #ifdef HAVE_QTWIDGETS 0165 : form(nullptr) 0166 #endif // HAVE_QTWIDGETS 0167 { 0168 // nothing 0169 } 0170 0171 #ifdef HAVE_QTWIDGETS 0172 QUrl buildQueryUrl() { 0173 if (form == nullptr) return QUrl(); 0174 0175 QUrl queryUrl{QUrl::fromUserInput(springerLinkQueryBaseUrl)}; 0176 0177 QString queryString = form->lineEditFreeText->text(); 0178 0179 const QStringList titleChunks = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditTitle->text()); 0180 for (const QString &titleChunk : titleChunks) { 0181 queryString += QString(QStringLiteral(" title:%1")).arg(Encoder::instance().convertToPlainAscii(titleChunk)); 0182 } 0183 0184 const QStringList bookTitleChunks = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditBookTitle->text()); 0185 for (const QString &titleChunk : bookTitleChunks) { 0186 queryString += QString(QStringLiteral(" ( journal:%1 OR book:%1 )")).arg(Encoder::instance().convertToPlainAscii(titleChunk)); 0187 } 0188 0189 const QStringList authors = OnlineSearchAbstract::splitRespectingQuotationMarks(form->lineEditAuthorEditor->text()); 0190 for (const QString &author : authors) { 0191 queryString += QString(QStringLiteral(" name:%1")).arg(Encoder::instance().convertToPlainAscii(author)); 0192 } 0193 0194 const QString year = form->lineEditYear->text(); 0195 if (!year.isEmpty()) 0196 queryString += QString(QStringLiteral(" year:%1")).arg(year); 0197 0198 const int numResults = form->numResultsField->value(); 0199 0200 queryString = queryString.simplified(); 0201 QUrlQuery query(queryUrl); 0202 query.addQueryItem(QStringLiteral("q"), queryString); 0203 query.addQueryItem(QStringLiteral("p"), QString::number(numResults)); 0204 query.addQueryItem(QStringLiteral("api_key"), springerMetadataKey); 0205 queryUrl.setQuery(query); 0206 0207 return queryUrl; 0208 } 0209 #endif // HAVE_QTWIDGETS 0210 0211 QUrl buildQueryUrl(const QMap<QueryKey, QString> &query, int numResults) { 0212 QUrl queryUrl{QUrl::fromUserInput(springerLinkQueryBaseUrl)}; 0213 QString queryString = query[QueryKey::FreeText]; 0214 0215 const QStringList titleChunks = OnlineSearchAbstract::splitRespectingQuotationMarks(query[QueryKey::Title]); 0216 for (const QString &titleChunk : titleChunks) { 0217 queryString += QString(QStringLiteral(" title:%1")).arg(Encoder::instance().convertToPlainAscii(titleChunk)); 0218 } 0219 0220 const QStringList authors = OnlineSearchAbstract::splitRespectingQuotationMarks(query[QueryKey::Author]); 0221 for (const QString &author : authors) { 0222 queryString += QString(QStringLiteral(" name:%1")).arg(Encoder::instance().convertToPlainAscii(author)); 0223 } 0224 0225 QString year = query[QueryKey::Year]; 0226 if (!year.isEmpty()) { 0227 static const QRegularExpression yearRegExp(QStringLiteral("\\b(18|19|20)[0-9]{2}\\b")); 0228 const QRegularExpressionMatch yearRegExpMatch = yearRegExp.match(year); 0229 if (yearRegExpMatch.hasMatch()) { 0230 year = yearRegExpMatch.captured(0); 0231 queryString += QString(QStringLiteral(" year:%1")).arg(year); 0232 } 0233 } 0234 0235 queryString = queryString.simplified(); 0236 QUrlQuery q(queryUrl); 0237 q.addQueryItem(QStringLiteral("q"), queryString); 0238 q.addQueryItem(QStringLiteral("p"), QString::number(numResults)); 0239 q.addQueryItem(QStringLiteral("api_key"), springerMetadataKey); 0240 queryUrl.setQuery(q); 0241 0242 return queryUrl; 0243 } 0244 0245 static QByteArray rewriteXMLformatting(const QByteArray &input) { 0246 QByteArray result{input}; 0247 static const QSet<const char *> lookInsideTags{"xhtml:body", "dc:title"}; 0248 0249 int p1 = -1; 0250 while ((p1 = result.indexOf('<', p1 + 1)) >= 0) { 0251 bool surroundingTagFound = false; 0252 for (const char *surroundingTag : lookInsideTags) { 0253 if (qstrncmp(surroundingTag, result.constData() + p1 + 1, qstrlen(surroundingTag)) == 0) 0254 { 0255 surroundingTagFound = true; 0256 p1 = result.indexOf("<", p1 + 7); 0257 while (p1 >= 0) { 0258 if (result.length() > p1 + 1 && result[p1 + 1] == '/' && qstrncmp(surroundingTag, result.constData() + p1 + 2, qstrlen(surroundingTag)) == 0) 0259 break; 0260 0261 bool encounteredKnowTag = false; 0262 0263 // There are some HTML tags like <h1>..</h1> or <p>..</p> that shall get removed, but not any text inside 0264 // Exception are headings with 'standard' text like <h1>Abstract</h1> which is to be removed completely 0265 static const QSet<const char *> ignoredTags{"h1", "h2", "h3", "p"}; 0266 const int tagOffset = result[p1 + 1] == '/' ? p1 + 2 : p1 + 1; 0267 for (const char *tag : ignoredTags) { 0268 if (qstrncmp(tag, result.constData() + tagOffset, qstrlen(tag)) == 0) { 0269 int p2 = result.indexOf(">", tagOffset); 0270 if (qstrncmp(result.constData() + p2 + 1, "Abstract<", 9) == 0 || qstrncmp(result.constData() + p2 + 1, "A bstract<", 10) == 0) { 0271 p2 = result.indexOf(">", p2 + 9); 0272 result = result.left(p1) + result.mid(p2 + 1); 0273 } else { 0274 const char *replacement = (result[p1 + 1] == '/' && qstrncmp(tag, "p", 1) == 0) ? "\n" : ""; 0275 result = result.left(p1) + replacement + result.mid(p2 + 1); 0276 } 0277 encounteredKnowTag = true; 0278 --p1; 0279 break; 0280 } 0281 } 0282 0283 // Remove some tags including their content, for example <CitationRef ..>..</CitationRef> 0284 static const QSet<const char *> removeTagsWithContent{"CitationRef"}; 0285 if (!encounteredKnowTag) 0286 for (const char *tag : removeTagsWithContent) { 0287 if (qstrncmp(tag, result.constData() + p1 + 1, qstrlen(tag)) == 0) { 0288 int p2 = p1; 0289 while ((p2 = result.indexOf('<', p2 + 1)) >= 0) { 0290 if (result.length() > p2 + 1 && result[p2 + 1] == '/' && qstrncmp(tag, result.constData() + p2 + 2, qstrlen(tag)) == 0) 0291 break; 0292 } 0293 if (p2 > p1) 0294 p2 = result.indexOf('>', p2 + 1); 0295 if (p2 > p1) { 0296 result = result.left(p1) + result.mid(p2 + 1); 0297 encounteredKnowTag = true; 0298 --p1; 0299 } else 0300 qCWarning(LOG_KBIBTEX_NETWORKING) << "Could not find closing tag for" << tag; 0301 break; 0302 } 0303 } 0304 0305 // Special treatment of <InlineEquation>, which possibly contains some TeX code that should be preserved 0306 if (!encounteredKnowTag && qstrncmp("InlineEquation", result.constData() + p1 + 1, 14) == 0) { 0307 const int pmax = result.indexOf("</InlineEquation", p1 + 5); 0308 if (pmax > p1) { 0309 const int pend = result.indexOf('>', pmax + 12); 0310 int p2 = result.indexOf("Format=\"TEX\"", p1 + 4); 0311 if (p1 < p2 && p2 < pmax && pmax < pend) { 0312 p2 = result.indexOf('>', p2 + 9); 0313 const int p3 = result.indexOf('<', p2 + 1); 0314 if (p1 < p2 && p2 < p3 && p3 < pmax) { 0315 QByteArray equation = result.mid(p2 + 1, p3 - p2 - 1); 0316 if (equation.length() > 4 && equation.startsWith("$$") && equation.endsWith("$$")) 0317 equation = equation.mid(1, equation.length() - 2); 0318 if (equation.length() > 4 && equation.startsWith("$ ") && equation.endsWith(" $")) 0319 equation = "$" + equation.mid(2, equation.length() - 4).trimmed() + "$"; 0320 result = result.left(p1) + equation + result.mid(pend + 1); 0321 encounteredKnowTag = true; 0322 --p1; 0323 } 0324 } 0325 } 0326 } 0327 0328 // Special treatment of <Emphasis>, which possibly contains some TeX code that should be preserved 0329 if (!encounteredKnowTag && qstrncmp("Emphasis", result.constData() + p1 + 1, 8) == 0) { 0330 const int pmax = result.indexOf("</Emphasis", p1 + 5); 0331 if (pmax > p1) { 0332 const int pend = result.indexOf('>', pmax + 8); 0333 const int p2 = result.indexOf('>', p1 + 8); 0334 if (p1 < p2 && p2 < pmax && pmax < pend) { 0335 const int p3 = result.indexOf("Type=\"Italic\"", p1 + 6); 0336 const char *emphCommand = (p3 > p1 && p3 < pmax) ? "\\textit{" : "\\emph{"; 0337 result = result.left(p1) + emphCommand + result.mid(p2 + 1, pmax - p2 - 1) + "}" + result.mid(pend + 1); 0338 encounteredKnowTag = true; 0339 --p1; 0340 } 0341 } 0342 } 0343 0344 // Special treatment of <Superscript>, which possibly contains some TeX code that should be preserved 0345 if (!encounteredKnowTag && qstrncmp("Superscript", result.constData() + p1 + 1, 11) == 0) { 0346 const int pmax = result.indexOf("</Superscript", p1 + 5); 0347 if (pmax > p1) { 0348 const int pend = result.indexOf('>', pmax + 8); 0349 const int p2 = result.indexOf('>', p1 + 8); 0350 const bool isCitationRef = qstrncmp(result.constData() + p1 + 13, "<CitationRef", 12) == 0; 0351 if (p1 < p2 && p2 < pmax && pmax < pend) { 0352 result = result.left(p1) + (isCitationRef ? QByteArray() : ("\\textsuperscript{" + result.mid(p2 + 1, pmax - p2 - 1) + "}")) + result.mid(pend + 1); 0353 encounteredKnowTag = true; 0354 --p1; 0355 } 0356 } 0357 } 0358 0359 // Special treatment of <Subscript>, which possibly contains some TeX code that should be preserved 0360 if (!encounteredKnowTag && qstrncmp("Subscript", result.constData() + p1 + 1, 8) == 0) { 0361 const int pmax = result.indexOf("</Subscript", p1 + 5); 0362 if (pmax > p1) { 0363 const int pend = result.indexOf('>', pmax + 8); 0364 const int p2 = result.indexOf('>', p1 + 8); 0365 if (p1 < p2 && p2 < pmax && pmax < pend) { 0366 result = result.left(p1) + "\\textsubscript{" + result.mid(p2 + 1, pmax - p2 - 1) + "}" + result.mid(pend + 1); 0367 encounteredKnowTag = true; 0368 --p1; 0369 } 0370 } 0371 } 0372 0373 if (!encounteredKnowTag) { 0374 // Encountered a tag that is neither known to be ignored or removed including content 0375 qCWarning(LOG_KBIBTEX_NETWORKING) << "Found unknown tag in SpringerLink data:" << result.mid(p1, 64); 0376 } 0377 p1 = result.indexOf("<", p1 + 1); 0378 } 0379 break; 0380 } 0381 } 0382 if (!surroundingTagFound) 0383 ++p1; 0384 } 0385 0386 return OnlineSearchAbstract::htmlEntityToUnicode(result); 0387 } 0388 0389 QVector<QSharedPointer<Entry>> parsePAM(const QByteArray &xmlData, bool *ok) { 0390 QVector<QSharedPointer<Entry>> result; 0391 0392 // Source code generated by Python script 'onlinesearch-parser-generator.py' 0393 // using information from configuration file 'onlinesearchspringerlink-parser.in.cpp' 0394 #include "onlinesearch/onlinesearchspringerlink-parser.generated.cpp" 0395 0396 return result; 0397 } 0398 }; 0399 0400 0401 const QString OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate::springerLinkQueryBaseUrl{QStringLiteral("https://api.springernature.com/metadata/pam")}; 0402 const QString OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate::springerMetadataKey(InternalNetworkAccessManager::reverseObfuscate("\xce\xb8\x4d\x2c\x8d\xba\xa9\xc4\x61\x9\x58\x6c\xbb\xde\x86\xb5\xb1\xc6\x15\x71\x76\x45\xd\x79\x12\x65\x95\xe1\x5d\x2f\x1d\x24\x10\x72\x2a\x5e\x69\x4\xdc\xba\xab\xc3\x28\x58\x8a\xfa\x5e\x69")); 0403 0404 OnlineSearchSpringerLink::OnlineSearchSpringerLink(QObject *parent) 0405 : OnlineSearchAbstract(parent), d(new OnlineSearchSpringerLink::OnlineSearchSpringerLinkPrivate(this)) 0406 { 0407 /// nothing 0408 } 0409 0410 OnlineSearchSpringerLink::~OnlineSearchSpringerLink() 0411 { 0412 delete d; 0413 } 0414 0415 #ifdef HAVE_QTWIDGETS 0416 void OnlineSearchSpringerLink::startSearchFromForm() 0417 { 0418 m_hasBeenCanceled = false; 0419 Q_EMIT progress(curStep = 0, numSteps = 1); 0420 0421 QUrl springerLinkSearchUrl = d->buildQueryUrl(); 0422 0423 QNetworkRequest request(springerLinkSearchUrl); 0424 QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); 0425 InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); 0426 connect(reply, &QNetworkReply::finished, this, &OnlineSearchSpringerLink::doneFetchingPAM); 0427 0428 if (d->form != nullptr) d->form->saveState(); 0429 0430 refreshBusyProperty(); 0431 } 0432 #endif // HAVE_QTWIDGETS 0433 0434 void OnlineSearchSpringerLink::startSearch(const QMap<QueryKey, QString> &query, int numResults) 0435 { 0436 m_hasBeenCanceled = false; 0437 0438 QUrl springerLinkSearchUrl = d->buildQueryUrl(query, numResults); 0439 0440 Q_EMIT progress(curStep = 0, numSteps = 1); 0441 QNetworkRequest request(springerLinkSearchUrl); 0442 QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); 0443 InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply); 0444 connect(reply, &QNetworkReply::finished, this, &OnlineSearchSpringerLink::doneFetchingPAM); 0445 0446 refreshBusyProperty(); 0447 } 0448 0449 QString OnlineSearchSpringerLink::label() const 0450 { 0451 #ifdef HAVE_KF 0452 return i18n("SpringerLink"); 0453 #else // HAVE_KF 0454 //= onlinesearch-springerlink-label 0455 return QObject::tr("SpringerLink"); 0456 #endif // HAVE_KF 0457 } 0458 0459 #ifdef HAVE_QTWIDGETS 0460 OnlineSearchAbstract::Form *OnlineSearchSpringerLink::customWidget(QWidget *parent) 0461 { 0462 if (d->form == nullptr) 0463 d->form = new Form(parent); 0464 return d->form; 0465 } 0466 #endif // HAVE_QTWIDGETS 0467 0468 QUrl OnlineSearchSpringerLink::homepage() const 0469 { 0470 return QUrl(QStringLiteral("https://link.springer.com/")); 0471 } 0472 0473 #ifdef BUILD_TESTING 0474 QVector<QSharedPointer<Entry> > OnlineSearchSpringerLink::parsePAM(const QByteArray &xmlData, bool *ok) 0475 { 0476 return d->parsePAM(xmlData, ok); 0477 } 0478 0479 QByteArray OnlineSearchSpringerLink::rewriteXMLformatting(const QByteArray &input) 0480 { 0481 return OnlineSearchSpringerLinkPrivate::rewriteXMLformatting(input); 0482 } 0483 #endif // BUILD_TESTING 0484 0485 void OnlineSearchSpringerLink::doneFetchingPAM() 0486 { 0487 QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); 0488 if (handleErrors(reply)) { 0489 const QByteArray xmlCode{OnlineSearchSpringerLinkPrivate::rewriteXMLformatting(reply->readAll())}; 0490 0491 bool ok = false; 0492 const QVector<QSharedPointer<Entry>> entries = d->parsePAM(xmlCode, &ok); 0493 0494 if (ok) { 0495 for (const auto &entry : entries) 0496 publishEntry(entry); 0497 stopSearch(resultNoError); 0498 } else { 0499 qCWarning(LOG_KBIBTEX_NETWORKING) << "Failed to parse XML data from" << InternalNetworkAccessManager::removeApiKey(reply->url()).toDisplayString(); 0500 stopSearch(resultUnspecifiedError); 0501 } 0502 } 0503 0504 refreshBusyProperty(); 0505 } 0506 0507 #include "onlinesearchspringerlink.moc"