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"