File indexing completed on 2024-05-12 05:09:41

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "srufetcher.h"
0026 #include "../fieldformat.h"
0027 #include "../collection.h"
0028 #include "../translators/tellico_xml.h"
0029 #include "../translators/xslthandler.h"
0030 #include "../translators/tellicoimporter.h"
0031 #include "../utils/guiproxy.h"
0032 #include "../gui/lineedit.h"
0033 #include "../gui/combobox.h"
0034 #include "../gui/stringmapwidget.h"
0035 #include "../utils/string_utils.h"
0036 #include "../utils/lccnvalidator.h"
0037 #include "../utils/isbnvalidator.h"
0038 #include "../utils/datafileregistry.h"
0039 #include "../tellico_debug.h"
0040 
0041 #include <KLocalizedString>
0042 #include <KIO/Job>
0043 #include <KJobUiDelegate>
0044 #include <KJobWidgets/KJobWidgets>
0045 #include <KConfigGroup>
0046 #include <KComboBox>
0047 #include <KAcceleratorManager>
0048 
0049 #include <QSpinBox>
0050 #include <QLabel>
0051 #include <QGridLayout>
0052 #include <QFile>
0053 #include <QUrlQuery>
0054 #include <QDomDocument>
0055 
0056 namespace {
0057   // 7090 was the old default port, but that was just because LoC used it
0058   // let's use default HTTP port of 80 now
0059   static const int SRU_DEFAULT_PORT = 80;
0060   static const int SRU_MAX_RECORDS = 25;
0061 }
0062 
0063 using namespace Tellico;
0064 using Tellico::Fetch::SRUFetcher;
0065 
0066 SRUFetcher::SRUFetcher(QObject* parent_)
0067     : Fetcher(parent_), m_port(SRU_DEFAULT_PORT), m_job(nullptr), m_MARCXMLHandler(nullptr), m_MODSHandler(nullptr), m_SRWHandler(nullptr), m_started(false) {
0068 }
0069 
0070 SRUFetcher::SRUFetcher(const QString& name_, const QString& host_, uint port_, const QString& path_,
0071                        const QString& format_, QObject* parent_) : Fetcher(parent_),
0072       m_scheme(QStringLiteral("http")), m_host(host_), m_port(port_), m_path(path_), m_format(format_),
0073       m_job(nullptr), m_MARCXMLHandler(nullptr), m_MODSHandler(nullptr), m_SRWHandler(nullptr), m_started(false) {
0074   m_name = name_; // m_name is protected in super class
0075   if(!m_path.startsWith(QLatin1Char('/'))) {
0076     m_path.prepend(QLatin1Char('/'));
0077   }
0078 }
0079 
0080 SRUFetcher::~SRUFetcher() {
0081   delete m_MARCXMLHandler;
0082   m_MARCXMLHandler = nullptr;
0083   delete m_MODSHandler;
0084   m_MODSHandler = nullptr;
0085   delete m_SRWHandler;
0086   m_SRWHandler = nullptr;
0087 }
0088 
0089 QString SRUFetcher::source() const {
0090   return m_name.isEmpty() ? defaultName() : m_name;
0091 }
0092 
0093 // No Raw for now.
0094 bool SRUFetcher::canSearch(Fetch::FetchKey k) const {
0095   return k == Title || k == Person || k == ISBN || k == Keyword || k == LCCN;
0096 }
0097 
0098 bool SRUFetcher::canFetch(int type) const {
0099   return type == Data::Collection::Book || type == Data::Collection::Bibtex;
0100 }
0101 
0102 void SRUFetcher::readConfigHook(const KConfigGroup& config_) {
0103   m_scheme = config_.readEntry("Scheme", "http");
0104   m_host = config_.readEntry("Host");
0105   int p = config_.readEntry("Port", SRU_DEFAULT_PORT);
0106   if(p > 0) {
0107     m_port = p;
0108   }
0109   m_path = config_.readEntry("Path");
0110   // used to be called Database
0111   if(m_path.isEmpty()) {
0112     m_path = config_.readEntry("Database");
0113   }
0114   if(!m_path.startsWith(QLatin1Char('/'))) {
0115     m_path.prepend(QLatin1Char('/'));
0116   }
0117   m_format = config_.readEntry("Format", "mods");
0118   const QStringList queryFields = config_.readEntry("QueryFields", QStringList());
0119   const QStringList queryValues = config_.readEntry("QueryValues", QStringList());
0120   Q_ASSERT(queryFields.count() == queryValues.count());
0121   for(int i = 0; i < qMin(queryFields.count(), queryValues.count()); ++i) {
0122     m_queryMap.insert(queryFields.at(i), queryValues.at(i));
0123   }
0124 }
0125 
0126 void SRUFetcher::search() {
0127   m_started = true;
0128   if(m_host.isEmpty() || m_path.isEmpty() || m_format.isEmpty()) {
0129     myDebug() << source() << "- host and path are not set!";
0130     stop();
0131     return;
0132   }
0133 
0134   QUrl u;
0135   u.setScheme(m_scheme);
0136   u.setHost(m_host);
0137   u.setPort(m_port);
0138   u = QUrl::fromUserInput(u.url() + m_path);
0139 
0140   QUrlQuery query;
0141   for(StringMap::ConstIterator it = m_queryMap.constBegin(); it != m_queryMap.constEnd(); ++it) {
0142     query.addQueryItem(it.key(), it.value());
0143   }
0144   // allow user to override these so check for existing item first
0145   if(!query.hasQueryItem(QStringLiteral("operation"))) {
0146     query.addQueryItem(QStringLiteral("operation"), QStringLiteral("searchRetrieve"));
0147   }
0148   if(!query.hasQueryItem(QStringLiteral("version"))) {
0149     query.addQueryItem(QStringLiteral("version"), QStringLiteral("1.1"));
0150   }
0151   if(!query.hasQueryItem(QStringLiteral("maximumRecords"))) {
0152     query.addQueryItem(QStringLiteral("maximumRecords"), QString::number(SRU_MAX_RECORDS));
0153   }
0154   if(!m_format.isEmpty() && m_format != QLatin1String("none")
0155      && !query.hasQueryItem(QStringLiteral("recordSchema"))) {
0156     query.addQueryItem(QStringLiteral("recordSchema"), m_format);
0157   }
0158 
0159   const int type = collectionType();
0160   QString str = QLatin1Char('"') + request().value() + QLatin1Char('"');
0161   switch(request().key()) {
0162     case Title:
0163       query.addQueryItem(QStringLiteral("query"), QLatin1String("dc.title=") + str);
0164       break;
0165 
0166     case Person:
0167       {
0168         QString s;
0169         if(type == Data::Collection::Book || type == Data::Collection::Bibtex) {
0170           s = QLatin1String("author=") + str + QLatin1String(" or dc.author=") + str;
0171         } else {
0172           s = QLatin1String("dc.creator=") + str + QLatin1String(" or dc.editor=") + str;
0173         }
0174         query.addQueryItem(QStringLiteral("query"), s);
0175       }
0176       break;
0177 
0178     case ISBN:
0179       {
0180         QString s = request().value();
0181         s.remove(QLatin1Char('-'));
0182         QStringList isbnList = FieldFormat::splitValue(s);
0183         // also search for isbn10 values
0184         for(QStringList::Iterator it = isbnList.begin(); it != isbnList.end(); ++it) {
0185           if((*it).startsWith(QLatin1String("978"))) {
0186             QString isbn10 = ISBNValidator::isbn10(*it);
0187             isbn10.remove(QLatin1Char('-'));
0188             it = isbnList.insert(it, isbn10);
0189             ++it;
0190           }
0191         }
0192         QString q;
0193         for(int i = 0; i < isbnList.count(); ++i) {
0194           // make an assumption that DC output uses the dc profile and everything else uses Bath for ISBN
0195           // no idea if this holds true universally, but matches LOC, COPAC, and KB
0196           if(m_format == QLatin1String("dc")) {
0197             q += QLatin1String("dc.identifier=") + isbnList.at(i);
0198           } else {
0199             q += QLatin1String("bath.isbn=") + isbnList.at(i);
0200           }
0201           if(i < isbnList.count()-1) {
0202             q += QLatin1String(" or ");
0203           }
0204         }
0205         query.addQueryItem(QStringLiteral("query"), q);
0206       }
0207       break;
0208 
0209     case LCCN:
0210       {
0211         QString s = request().value();
0212         QStringList lccnList = FieldFormat::splitValue(s);
0213         QString q;
0214         for(int i = 0; i < lccnList.count(); ++i) {
0215           q += QLatin1String("bath.lccn=") + lccnList.at(i);
0216           q += QLatin1String(" or bath.lccn=") + LCCNValidator::formalize(lccnList.at(i));
0217           if(i < lccnList.count()-1) {
0218             q += QLatin1String(" or ");
0219           }
0220         }
0221         query.addQueryItem(QStringLiteral("query"), q);
0222       }
0223       break;
0224 
0225     case Keyword:
0226       query.addQueryItem(QStringLiteral("query"), str);
0227       break;
0228 
0229     case Raw:
0230       {
0231         QString key = request().value().section(QLatin1Char('='), 0, 0).trimmed();
0232         QString str = request().value().section(QLatin1Char('='), 1).trimmed();
0233         query.addQueryItem(key, str);
0234       }
0235       break;
0236 
0237     default:
0238       myWarning() << source() << "- key not recognized:" << request().key();
0239       stop();
0240       break;
0241   }
0242   u.setQuery(query);
0243 //  myDebug() << u.url();
0244 
0245   m_job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
0246   KJobWidgets::setWindow(m_job, GUI::Proxy::widget());
0247   connect(m_job.data(), &KJob::result,
0248           this, &SRUFetcher::slotComplete);
0249 }
0250 
0251 void SRUFetcher::stop() {
0252   if(!m_started) {
0253     return;
0254   }
0255   if(m_job) {
0256     m_job->kill();
0257     m_job = nullptr;
0258   }
0259 
0260   m_started = false;
0261   emit signalDone(this);
0262 }
0263 
0264 void SRUFetcher::slotComplete(KJob*) {
0265   if(m_job->error()) {
0266     m_job->uiDelegate()->showErrorMessage();
0267     stop();
0268     return;
0269   }
0270 
0271   QByteArray data = m_job->data();
0272   if(data.isEmpty()) {
0273     stop();
0274     return;
0275   }
0276   // see bug 319662. If fetcher is cancelled, job is killed
0277   // if the pointer is retained, it gets double-deleted
0278   m_job = nullptr;
0279 
0280 #if 0
0281   myWarning() << "Remove debug from srufetcher.cpp";
0282   QFile f(QString::fromLatin1("/tmp/test.xml"));
0283   if(f.open(QIODevice::WriteOnly)) {
0284     QTextStream t(&f);
0285     t.setCodec("UTF-8");
0286     t << data;
0287   }
0288   f.close();
0289 #endif
0290 
0291   Data::CollPtr coll;
0292   QString msg;
0293 
0294   const QString result = QString::fromUtf8(data.constData(), data.size());
0295 
0296   // first check for SRU errors
0297   QDomDocument dom;
0298   if(!dom.setContent(result, true /*namespace*/)) {
0299     myWarning() << "server did not return valid XML.";
0300     stop();
0301     return;
0302   }
0303 
0304   const QString& diag = XML::nsZingDiag;
0305   QDomNodeList diagList = dom.elementsByTagNameNS(diag, QStringLiteral("diagnostic"));
0306   for(int i = 0; i < diagList.count(); ++i) {
0307     QDomElement elem = diagList.item(i).toElement();
0308     QDomNodeList nodeList1 = elem.elementsByTagNameNS(diag, QStringLiteral("message"));
0309     QDomNodeList nodeList2 = elem.elementsByTagNameNS(diag, QStringLiteral("details"));
0310     for(int j = 0; j < nodeList1.count(); ++j) {
0311       QString d = nodeList1.item(j).toElement().text();
0312       if(!d.isEmpty()) {
0313         QString d2 = nodeList2.item(j).toElement().text();
0314         if(!d2.isEmpty()) {
0315           d += QLatin1String(" (") + d2 + QLatin1Char(')');
0316         }
0317         myDebug() << "[" << m_host << "/" << m_path << "]" << d;
0318         if(!msg.isEmpty()) {
0319           msg += QLatin1Char('\n');
0320         }
0321         msg += d;
0322       }
0323     }
0324   }
0325 
0326   QString modsResult;
0327   if(m_format == QLatin1String("mods")) {
0328     modsResult = result;
0329 //  } else if(m_format == QLatin1String("marcxml") && initMARCXMLHandler()) {
0330 // some SRU data sources call it MARC21-xml or something other than marcxml
0331   } else if(m_format.startsWith(QLatin1String("marc"), Qt::CaseInsensitive) && initMARCXMLHandler()) {
0332     // brute force marcxchange conversion. This is probably wrong at some level
0333     QString newResult = result;
0334     if(m_format.startsWith(QLatin1String("marcxchange"), Qt::CaseInsensitive)) {
0335       static const QRegularExpression marcRx(QLatin1String("xmlns:marc=\"info:lc/xmlns/marcxchange-v[12]\""));
0336       newResult.replace(marcRx,
0337                         QStringLiteral("xmlns:marc=\"http://www.loc.gov/MARC21/slim\""));
0338     }
0339     modsResult = m_MARCXMLHandler->applyStylesheet(newResult);
0340   }
0341   if(!modsResult.isEmpty() && initMODSHandler()) {
0342     Import::TellicoImporter imp(m_MODSHandler->applyStylesheet(modsResult));
0343     coll = imp.collection();
0344     if(!msg.isEmpty()) {
0345       msg += QLatin1Char('\n');
0346     }
0347     msg += imp.statusMessage();
0348   } else if((m_format == QLatin1String("pam") ||
0349              m_format == QLatin1String("dc") ||
0350              m_format == QLatin1String("none")) &&
0351             initSRWHandler()) {
0352     Import::TellicoImporter imp(m_SRWHandler->applyStylesheet(result));
0353     coll = imp.collection();
0354     if(!msg.isEmpty()) {
0355       msg += QLatin1Char('\n');
0356     }
0357     msg += imp.statusMessage();
0358   } else {
0359     myDebug() << "unrecognized format:" << m_format;
0360     stop();
0361     return;
0362   }
0363 
0364   if(coll && !msg.isEmpty()) {
0365     message(msg, coll->entryCount() == 0 ? MessageHandler::Warning : MessageHandler::Status);
0366   }
0367 
0368   if(!coll) {
0369     myDebug() << "no collection pointer";
0370     if(!msg.isEmpty()) {
0371       message(msg, MessageHandler::Error);
0372     }
0373     stop();
0374     return;
0375   }
0376 
0377   // since the Dewey and LoC field titles have a context in their i18n call here
0378   // but not in the stylesheet where the field is actually created
0379   // update the field titles here
0380   QHashIterator<QString, QString> i(allOptionalFields());
0381   while(i.hasNext()) {
0382     i.next();
0383     Data::FieldPtr field = coll->fieldByName(i.key());
0384     if(field) {
0385       field->setTitle(i.value());
0386       coll->modifyField(field);
0387     }
0388   }
0389 
0390   foreach(Data::EntryPtr entry, coll->entries()) {
0391     FetchResult* r = new FetchResult(this, entry);
0392     m_entries.insert(r->uid, entry);
0393     emit signalResultFound(r);
0394   }
0395   stop();
0396 }
0397 
0398 Tellico::Data::EntryPtr SRUFetcher::fetchEntryHook(uint uid_) {
0399   return m_entries[uid_];
0400 }
0401 
0402 Tellico::Fetch::FetchRequest SRUFetcher::updateRequest(Data::EntryPtr entry_) {
0403 //  myDebug() << source() << ": " << entry_->title();
0404   QString isbn = entry_->field(QStringLiteral("isbn"));
0405   if(!isbn.isEmpty()) {
0406     return FetchRequest(Fetch::ISBN, isbn);
0407   }
0408 
0409   QString lccn = entry_->field(QStringLiteral("lccn"));
0410   if(!lccn.isEmpty()) {
0411     return FetchRequest(Fetch::LCCN, lccn);
0412   }
0413 
0414   // optimistically try searching for title and rely on Collection::sameEntry() to figure things out
0415   QString t = entry_->field(QStringLiteral("title"));
0416   if(!t.isEmpty()) {
0417     return FetchRequest(Fetch::Title, t);
0418   }
0419   return FetchRequest();
0420 }
0421 
0422 bool SRUFetcher::initMARCXMLHandler() {
0423   if(m_MARCXMLHandler) {
0424     return true;
0425   }
0426 
0427   QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("MARC21slim2MODS3.xsl"));
0428   if(xsltfile.isEmpty()) {
0429     myWarning() << "can not locate MARC21slim2MODS3.xsl.";
0430     return false;
0431   }
0432 
0433   QUrl u = QUrl::fromLocalFile(xsltfile);
0434 
0435   m_MARCXMLHandler = new XSLTHandler(u);
0436   if(!m_MARCXMLHandler->isValid()) {
0437     myWarning() << "error in MARC21slim2MODS3.xsl.";
0438     delete m_MARCXMLHandler;
0439     m_MARCXMLHandler = nullptr;
0440     return false;
0441   }
0442   return true;
0443 }
0444 
0445 bool SRUFetcher::initMODSHandler() {
0446   if(m_MODSHandler) {
0447     return true;
0448   }
0449 
0450   QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("mods2tellico.xsl"));
0451   if(xsltfile.isEmpty()) {
0452     myWarning() << "can not locate mods2tellico.xsl.";
0453     return false;
0454   }
0455 
0456   QUrl u = QUrl::fromLocalFile(xsltfile);
0457 
0458   m_MODSHandler = new XSLTHandler(u);
0459   if(!m_MODSHandler->isValid()) {
0460     myWarning() << "error in mods2tellico.xsl.";
0461     delete m_MODSHandler;
0462     m_MODSHandler = nullptr;
0463     return false;
0464   }
0465   return true;
0466 }
0467 
0468 bool SRUFetcher::initSRWHandler() {
0469   if(m_SRWHandler) {
0470     return true;
0471   }
0472 
0473   QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("srw2tellico.xsl"));
0474   if(xsltfile.isEmpty()) {
0475     myWarning() << "can not locate srw2tellico.xsl.";
0476     return false;
0477   }
0478 
0479   QUrl u = QUrl::fromLocalFile(xsltfile);
0480 
0481   m_SRWHandler = new XSLTHandler(u);
0482   if(!m_SRWHandler->isValid()) {
0483     myWarning() << "error in srw2tellico.xsl.";
0484     delete m_SRWHandler;
0485     m_SRWHandler = nullptr;
0486     return false;
0487   }
0488   return true;
0489 }
0490 
0491 Tellico::Fetch::Fetcher::Ptr SRUFetcher::libraryOfCongress(QObject* parent_) {
0492   return Fetcher::Ptr(new SRUFetcher(i18n("Library of Congress (US)"), QStringLiteral("lx2.loc.gov"), 210,
0493                                      QStringLiteral("LCDB"), QStringLiteral("mods"), parent_));
0494 }
0495 
0496 QString SRUFetcher::defaultName() {
0497   return i18n("SRU Server");
0498 }
0499 
0500 QString SRUFetcher::defaultIcon() {
0501   return QStringLiteral(":/icons/sru");
0502 }
0503 
0504 // static
0505 Tellico::StringHash SRUFetcher::allOptionalFields() {
0506   StringHash hash;
0507   hash[QStringLiteral("address")]  = i18n("Address");
0508   hash[QStringLiteral("abstract")] = i18n("Abstract");
0509   hash[QStringLiteral("dewey")]    = i18nc("Dewey Decimal classification system", "Dewey Decimal");
0510   hash[QStringLiteral("lcc")]      = i18nc("Library of Congress classification system", "LoC Classification");
0511   return hash;
0512 }
0513 
0514 Tellico::Fetch::ConfigWidget* SRUFetcher::configWidget(QWidget* parent_) const {
0515   return new ConfigWidget(parent_, this);
0516 }
0517 
0518 SRUFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const SRUFetcher* fetcher_ /*=0*/)
0519     : Fetch::ConfigWidget(parent_) {
0520   QGridLayout* l = new QGridLayout(optionsWidget());
0521   l->setSpacing(4);
0522   l->setColumnStretch(1, 10);
0523 
0524   int row = -1;
0525   QLabel* label = new QLabel(i18n("Scheme: "), optionsWidget());
0526   l->addWidget(label, ++row, 0);
0527   m_schemeCombo = new GUI::ComboBox(optionsWidget());
0528   m_schemeCombo->addItem(QStringLiteral("http"));
0529   m_schemeCombo->addItem(QStringLiteral("https"));
0530   void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated;
0531   connect(m_schemeCombo, activatedInt, this, &ConfigWidget::slotSetModified);
0532   connect(m_schemeCombo, &QComboBox::editTextChanged, this, &ConfigWidget::slotSetModified);
0533   l->addWidget(m_schemeCombo, row, 1);
0534   QString w = i18n("Enter the path to the database used by the server.");
0535   label->setWhatsThis(w);
0536   m_schemeCombo->setWhatsThis(w);
0537   label->setBuddy(m_schemeCombo);
0538 
0539   label = new QLabel(i18n("Hos&t: "), optionsWidget());
0540   l->addWidget(label, ++row, 0);
0541   m_hostEdit = new GUI::LineEdit(optionsWidget());
0542   connect(m_hostEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified);
0543   connect(m_hostEdit, &QLineEdit::textChanged, this, &ConfigWidget::signalName);
0544   connect(m_hostEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotCheckHost);
0545   l->addWidget(m_hostEdit, row, 1);
0546   w = i18n("Enter the host name of the server.");
0547   label->setWhatsThis(w);
0548   m_hostEdit->setWhatsThis(w);
0549   label->setBuddy(m_hostEdit);
0550 
0551   label = new QLabel(i18n("&Port: "), optionsWidget());
0552   l->addWidget(label, ++row, 0);
0553   m_portSpinBox = new QSpinBox(optionsWidget());
0554   m_portSpinBox->setMaximum(999999);
0555   m_portSpinBox->setMinimum(0);
0556   m_portSpinBox->setValue(SRU_DEFAULT_PORT);
0557   void (QSpinBox::* valueChanged)(int) = &QSpinBox::valueChanged;
0558   connect(m_portSpinBox, valueChanged, this, &ConfigWidget::slotSetModified);
0559   l->addWidget(m_portSpinBox, row, 1);
0560   w = i18n("Enter the port number of the server. The default is %1.", SRU_DEFAULT_PORT);
0561   label->setWhatsThis(w);
0562   m_portSpinBox->setWhatsThis(w);
0563   label->setBuddy(m_portSpinBox);
0564 
0565   label = new QLabel(i18n("Path: "), optionsWidget());
0566   l->addWidget(label, ++row, 0);
0567   m_pathEdit = new GUI::LineEdit(optionsWidget());
0568   connect(m_pathEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified);
0569   l->addWidget(m_pathEdit, row, 1);
0570   w = i18n("Enter the path to the database used by the server.");
0571   label->setWhatsThis(w);
0572   m_pathEdit->setWhatsThis(w);
0573   label->setBuddy(m_pathEdit);
0574 
0575   label = new QLabel(i18n("Format: "), optionsWidget());
0576   l->addWidget(label, ++row, 0);
0577   m_formatCombo = new GUI::ComboBox(optionsWidget());
0578   m_formatCombo->addItem(QStringLiteral("MODS"), QLatin1String("mods"));
0579   m_formatCombo->addItem(QStringLiteral("MARCXML"), QLatin1String("marcxml"));
0580   m_formatCombo->addItem(QStringLiteral("PAM"), QLatin1String("pam"));
0581   m_formatCombo->addItem(QStringLiteral("Dublin Core"), QLatin1String("dc"));
0582   m_formatCombo->setEditable(true);
0583   connect(m_formatCombo, activatedInt, this, &ConfigWidget::slotSetModified);
0584   connect(m_formatCombo, &QComboBox::editTextChanged, this, &ConfigWidget::slotSetModified);
0585   l->addWidget(m_formatCombo, row, 1);
0586   w = i18n("Enter the result format used by the server.");
0587   label->setWhatsThis(w);
0588   m_formatCombo->setWhatsThis(w);
0589   label->setBuddy(m_formatCombo);
0590 
0591   l->setRowStretch(++row, 1);
0592 
0593   m_queryTree = new GUI::StringMapWidget(StringMap(), optionsWidget());
0594   l->addWidget(m_queryTree, row, 0, 1, 2);
0595   m_queryTree->setLabels(i18n("Field"), i18n("Value"));
0596 
0597   // now add additional fields widget
0598   addFieldsWidget(SRUFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList());
0599 
0600   if(fetcher_) {
0601     m_schemeCombo->setCurrentText(fetcher_->m_scheme);
0602     m_hostEdit->setText(fetcher_->m_host);
0603     m_portSpinBox->setValue(fetcher_->m_port);
0604     m_pathEdit->setText(fetcher_->m_path);
0605     if(m_formatCombo->findData(fetcher_->m_format) == -1) {
0606       m_formatCombo->addItem(fetcher_->m_format, fetcher_->m_format);
0607     }
0608     m_formatCombo->setCurrentData(fetcher_->m_format);
0609     m_queryTree->setStringMap(fetcher_->m_queryMap);
0610   }
0611   KAcceleratorManager::manage(optionsWidget());
0612 }
0613 
0614 void SRUFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
0615   QString s = m_hostEdit->text().trimmed();
0616   if(!s.isEmpty()) {
0617     config_.writeEntry("Host", s);
0618   }
0619   int port = m_portSpinBox->value();
0620   if(port > 0) {
0621     config_.writeEntry("Port", port);
0622   }
0623   s = m_pathEdit->text().trimmed();
0624   if(!s.isEmpty()) {
0625     config_.writeEntry("Path", s);
0626   }
0627   s = m_formatCombo->currentData().toString().trimmed();
0628   if(s.isEmpty()) {
0629     // user-entered format will not have data set for the item. Just use the text itself
0630     s = m_formatCombo->currentText().trimmed();
0631   }
0632   if(!s.isEmpty()) {
0633     config_.writeEntry("Format", s);
0634   }
0635   s = m_schemeCombo->currentText();
0636   if(!s.isEmpty()) {
0637     config_.writeEntry("Scheme", s);
0638   }
0639   StringMap queryMap = m_queryTree->stringMap();
0640   if(!queryMap.isEmpty()) {
0641     config_.writeEntry("QueryFields", queryMap.keys());
0642     config_.writeEntry("QueryValues", queryMap.values());
0643   }
0644 }
0645 
0646 QString SRUFetcher::ConfigWidget::preferredName() const {
0647   QString s = m_hostEdit->text();
0648   return s.isEmpty() ? SRUFetcher::defaultName() : s;
0649 }
0650 
0651 void SRUFetcher::ConfigWidget::slotCheckHost() {
0652   QString s = m_hostEdit->text();
0653   // someone might be pasting a full URL, check that
0654   if(s.indexOf(QLatin1Char(':')) > -1 || s.indexOf(QLatin1Char('/')) > -1) {
0655     QUrl u(s);
0656     if(u.isValid()) {
0657       m_hostEdit->setText(u.host());
0658       if(u.port() > 0) {
0659         m_portSpinBox->setValue(u.port());
0660       }
0661       if(!u.path().isEmpty()) {
0662         m_pathEdit->setText(u.path().trimmed());
0663       }
0664     }
0665   }
0666 }