File indexing completed on 2024-04-28 16:13:25
0001 /* 0002 SPDX-FileCopyrightText: 2004 Ace Jones acejones @users.sourceforge.net 0003 SPDX-FileCopyrightText: 2019 Thomas Baumgart tbaumgart @kde.org 0004 0005 This file is part of libalkimia. 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "alkonlinequote.h" 0011 0012 #include "alkdateformat.h" 0013 #include "alkexception.h" 0014 #ifdef ENABLE_FINANCEQUOTE 0015 #include "alkfinancequoteprocess.h" 0016 #endif 0017 #include "alkonlinequoteprocess.h" 0018 #include "alkonlinequotesprofile.h" 0019 #include "alkonlinequotesprofilemanager.h" 0020 #include "alkonlinequotesource.h" 0021 #include "alkimia/alkversion.h" 0022 #include "alkwebpage.h" 0023 0024 #include <QApplication> 0025 #include <QByteArray> 0026 #include <QFile> 0027 #include <QRegExp> 0028 #include <QTextStream> 0029 #include <QTextCodec> 0030 #include <QTimer> 0031 0032 #ifdef BUILD_WITH_QTNETWORK 0033 #include <QNetworkAccessManager> 0034 #include <QNetworkRequest> 0035 #include <QNetworkReply> 0036 #include <QNetworkProxyFactory> 0037 #else 0038 #include <KIO/Scheduler> 0039 #endif 0040 0041 #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) 0042 #include <KLocalizedString> 0043 #ifndef BUILD_WITH_QTNETWORK 0044 #include <KIO/Job> 0045 #endif 0046 #include <QDebug> 0047 #include <QTemporaryFile> 0048 #define kDebug(a) qDebug() 0049 #define KIcon QIcon 0050 #define KUrl QUrl 0051 #define prettyUrl() toDisplayString() 0052 #else 0053 #include <KDebug> 0054 #include <KGlobal> 0055 #include <KLocale> 0056 #include <KUrl> 0057 #ifndef BUILD_WITH_QTNETWORK 0058 #include <kio/netaccess.h> 0059 #endif 0060 #endif 0061 0062 #include <KConfigGroup> 0063 #include <KEncodingProber> 0064 #include <KProcess> 0065 #include <KShell> 0066 0067 AlkOnlineQuote::Errors::Errors() 0068 { 0069 } 0070 0071 AlkOnlineQuote::Errors::Errors(Type type) 0072 { 0073 m_type.append(type); 0074 } 0075 0076 AlkOnlineQuote::Errors::Errors(const Errors &e) 0077 { 0078 m_type = e.m_type; 0079 } 0080 0081 AlkOnlineQuote::Errors &AlkOnlineQuote::Errors::operator |=(Type t) 0082 { 0083 if (!m_type.contains(t)) { 0084 m_type.append(t); 0085 } 0086 return *this; 0087 } 0088 0089 bool AlkOnlineQuote::Errors::operator &(Type t) const 0090 { 0091 return m_type.contains(t); 0092 } 0093 0094 0095 class AlkOnlineQuote::Private : public QObject 0096 { 0097 Q_OBJECT 0098 public: 0099 AlkOnlineQuote *m_p; 0100 AlkOnlineQuoteProcess m_filter; 0101 QString m_quoteData; 0102 QString m_symbol; 0103 QString m_id; 0104 QDate m_date; 0105 double m_price; 0106 AlkOnlineQuoteSource m_source; 0107 AlkOnlineQuote::Errors m_errors; 0108 KUrl m_url; 0109 QEventLoop *m_eventLoop; 0110 QString m_acceptLanguage; 0111 AlkOnlineQuotesProfile *m_profile; 0112 bool m_ownProfile; 0113 int m_timeout; 0114 0115 #if QT_VERSION < QT_VERSION_CHECK(5,0,0) 0116 static int dbgArea() 0117 { 0118 static int s_area = KDebug::registerArea("Alkimia (AlkOnlineQuote)"); 0119 return s_area; 0120 } 0121 #endif 0122 0123 Private(AlkOnlineQuote *parent) 0124 : m_p(parent) 0125 , m_eventLoop(nullptr) 0126 , m_ownProfile(false) 0127 , m_timeout(-1) 0128 { 0129 connect(&m_filter, SIGNAL(processExited(QString)), this, SLOT(slotParseQuote(QString))); 0130 } 0131 0132 ~Private() 0133 { 0134 if (m_ownProfile) 0135 delete m_profile; 0136 } 0137 0138 bool initLaunch(const QString &_symbol, const QString &_id, const QString &_source); 0139 bool launchWebKitCssSelector(const QString &_symbol, const QString &_id, 0140 const QString &_source); 0141 bool launchWebKitHtmlParser(const QString &_symbol, const QString &_id, const QString &_source); 0142 bool launchNative(const QString &_symbol, const QString &_id, const QString &_source); 0143 bool launchFinanceQuote(const QString &_symbol, const QString &_id, const QString &_source); 0144 void enter_loop(); 0145 bool parsePrice(const QString &pricestr); 0146 bool parseDate(const QString &datestr); 0147 bool downloadUrl(const KUrl& url); 0148 bool processDownloadedFile(const KUrl& url, const QString& tmpFile); 0149 bool processDownloadedPage(const KUrl &url, const QByteArray &page); 0150 0151 public slots: 0152 void slotLoadStarted(); 0153 void slotLoadFinishedHtmlParser(bool ok = false); 0154 void slotLoadFinishedCssSelector(bool ok); 0155 bool slotParseQuote(const QString &_quotedata); 0156 0157 private slots: 0158 void slotLoadTimeout(); 0159 #ifndef BUILD_WITH_QTNETWORK 0160 void downloadUrlDone(KJob* job); 0161 #else 0162 void downloadUrlDone(QNetworkReply *reply); 0163 #endif 0164 }; 0165 0166 bool AlkOnlineQuote::Private::initLaunch(const QString &_symbol, const QString &_id, const QString &_source) 0167 { 0168 m_symbol = _symbol; 0169 m_id = _id; 0170 m_errors = Errors::None; 0171 0172 emit m_p->status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol, _id)); 0173 0174 // Get sources from the config file 0175 QString source = _source; 0176 if (source.isEmpty()) { 0177 source = "KMyMoney Currency"; 0178 } 0179 0180 if (!m_profile->quoteSources().contains(source)) { 0181 emit m_p->error(i18n("Source <placeholder>%1</placeholder> does not exist.", source)); 0182 m_errors |= Errors::Source; 0183 return false; 0184 } 0185 0186 //m_profile->createSource(source); 0187 m_source = AlkOnlineQuoteSource(source, m_profile); 0188 0189 KUrl url; 0190 0191 // if the source has room for TWO symbols.. 0192 if (m_source.url().contains("%2")) { 0193 // this is a two-symbol quote. split the symbol into two. valid symbol 0194 // characters are: 0-9, A-Z and the dot. anything else is a separator 0195 QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive); 0196 // if we've truly found 2 symbols delimited this way... 0197 if (splitrx.indexIn(m_symbol) != -1) { 0198 url = KUrl(m_source.url().arg(splitrx.cap(1), splitrx.cap(2))); 0199 } else { 0200 kDebug(Private::dbgArea()) << "WebPriceQuote::launch() did not find 2 symbols"; 0201 } 0202 } else { 0203 // a regular one-symbol quote 0204 url = KUrl(m_source.url().arg(m_symbol)); 0205 } 0206 0207 m_url = url; 0208 0209 return true; 0210 } 0211 0212 void AlkOnlineQuote::Private::slotLoadFinishedHtmlParser(bool ok) 0213 { 0214 if (!ok) { 0215 emit m_p->error(i18n("Unable to fetch url for %1", m_symbol)); 0216 m_errors |= Errors::URL; 0217 emit m_p->failed(m_id, m_symbol); 0218 } else { 0219 // parse symbol 0220 slotParseQuote(AlkOnlineQuotesProfileManager::instance().webPage()->toHtml()); 0221 } 0222 if (m_eventLoop) 0223 m_eventLoop->exit(); 0224 } 0225 0226 void AlkOnlineQuote::Private::slotLoadFinishedCssSelector(bool ok) 0227 { 0228 if (!ok) { 0229 emit m_p->error(i18n("Unable to fetch url for %1", m_symbol)); 0230 m_errors |= Errors::URL; 0231 emit m_p->failed(m_id, m_symbol); 0232 } else { 0233 AlkWebPage *webPage = AlkOnlineQuotesProfileManager::instance().webPage(); 0234 // parse symbol 0235 QString symbol = webPage->getFirstElement(m_source.sym()); 0236 if (!symbol.isEmpty()) { 0237 emit m_p->status(i18n("Symbol found: '%1'", symbol)); 0238 } else { 0239 m_errors |= Errors::Symbol; 0240 emit m_p->error(i18n("Unable to parse symbol for %1", m_symbol)); 0241 } 0242 0243 // parse price 0244 QString price = webPage->getFirstElement(m_source.price()); 0245 bool gotprice = parsePrice(price); 0246 0247 // parse date 0248 QString date = webPage->getFirstElement(m_source.date()); 0249 bool gotdate = parseDate(date); 0250 0251 if (gotprice && gotdate) { 0252 emit m_p->quote(m_id, m_symbol, m_date, m_price); 0253 } else { 0254 emit m_p->failed(m_id, m_symbol); 0255 } 0256 } 0257 if (m_eventLoop) 0258 m_eventLoop->exit(); 0259 } 0260 0261 void AlkOnlineQuote::Private::slotLoadStarted() 0262 { 0263 emit m_p->status(i18n("Fetching URL %1...", m_url.prettyUrl())); 0264 } 0265 0266 bool AlkOnlineQuote::Private::launchWebKitCssSelector(const QString &_symbol, const QString &_id, 0267 const QString &_source) 0268 { 0269 if (!initLaunch(_symbol, _id, _source)) { 0270 return false; 0271 } 0272 AlkWebPage *webPage = AlkOnlineQuotesProfileManager::instance().webPage(); 0273 connect(webPage, SIGNAL(loadStarted()), this, SLOT(slotLoadStarted())); 0274 connect(webPage, SIGNAL(loadFinished(bool)), this, 0275 SLOT(slotLoadFinishedCssSelector(bool))); 0276 if (m_timeout != -1) 0277 QTimer::singleShot(m_timeout, this, SLOT(slotLoadTimeout())); 0278 webPage->setUrl(m_url); 0279 m_eventLoop = new QEventLoop; 0280 m_eventLoop->exec(); 0281 delete m_eventLoop; 0282 m_eventLoop = nullptr; 0283 disconnect(webPage, SIGNAL(loadStarted()), this, SLOT(slotLoadStarted())); 0284 disconnect(webPage, SIGNAL(loadFinished(bool)), this, 0285 SLOT(slotLoadFinishedCssSelector(bool))); 0286 0287 return !(m_errors & Errors::URL || m_errors & Errors::Price 0288 || m_errors & Errors::Date || m_errors & Errors::Data); 0289 } 0290 0291 bool AlkOnlineQuote::Private::launchWebKitHtmlParser(const QString &_symbol, const QString &_id, 0292 const QString &_source) 0293 { 0294 if (!initLaunch(_symbol, _id, _source)) { 0295 return false; 0296 } 0297 AlkWebPage *webPage = AlkOnlineQuotesProfileManager::instance().webPage(); 0298 connect(webPage, SIGNAL(loadStarted()), this, SLOT(slotLoadStarted())); 0299 connect(webPage, SIGNAL(loadFinished(bool)), this, SLOT(slotLoadFinishedHtmlParser(bool))); 0300 if (m_timeout != -1) 0301 QTimer::singleShot(m_timeout, this, SLOT(slotLoadTimeout())); 0302 webPage->load(m_url, m_acceptLanguage); 0303 m_eventLoop = new QEventLoop; 0304 m_eventLoop->exec(); 0305 delete m_eventLoop; 0306 m_eventLoop = nullptr; 0307 disconnect(webPage, SIGNAL(loadStarted()), this, SLOT(slotLoadStarted())); 0308 disconnect(webPage, SIGNAL(loadFinished(bool)), this, SLOT(slotLoadFinishedHtmlParser(bool))); 0309 0310 return !(m_errors & Errors::URL || m_errors & Errors::Price 0311 || m_errors & Errors::Date || m_errors & Errors::Data); 0312 } 0313 0314 bool AlkOnlineQuote::Private::launchNative(const QString &_symbol, const QString &_id, 0315 const QString &_source) 0316 { 0317 bool result = true; 0318 if (!initLaunch(_symbol, _id, _source)) { 0319 return false; 0320 } 0321 0322 KUrl url = m_url; 0323 if (url.isLocalFile()) { 0324 emit m_p->status(i18nc("The process x is executing", "Executing %1...", url.toLocalFile())); 0325 0326 m_filter.clearProgram(); 0327 m_filter << url.toLocalFile().split(' ', QString::SkipEmptyParts); 0328 m_filter.setSymbol(m_symbol); 0329 0330 m_filter.setOutputChannelMode(KProcess::MergedChannels); 0331 m_filter.start(); 0332 0333 if (m_filter.waitForStarted()) { 0334 result = true; 0335 } else { 0336 emit m_p->error(i18n("Unable to launch: %1", url.toLocalFile())); 0337 m_errors |= Errors::Script; 0338 result = slotParseQuote(QString()); 0339 } 0340 } else { 0341 slotLoadStarted(); 0342 result = downloadUrl(url); 0343 } 0344 return result; 0345 } 0346 0347 bool AlkOnlineQuote::Private::processDownloadedFile(const KUrl& url, const QString& tmpFile) 0348 { 0349 bool result = false; 0350 0351 QFile f(tmpFile); 0352 if (f.open(QIODevice::ReadOnly)) { 0353 // Find out the page encoding and convert it to unicode 0354 QByteArray page = f.readAll(); 0355 result = processDownloadedPage(url, page); 0356 f.close(); 0357 } else { 0358 emit m_p->error(i18n("Failed to open downloaded file")); 0359 m_errors |= Errors::URL; 0360 result = slotParseQuote(QString()); 0361 } 0362 return result; 0363 } 0364 0365 bool AlkOnlineQuote::Private::processDownloadedPage(const KUrl& url, const QByteArray& page) 0366 { 0367 bool result = false; 0368 KEncodingProber prober(KEncodingProber::Universal); 0369 prober.feed(page); 0370 QTextCodec *codec = QTextCodec::codecForName(prober.encoding()); 0371 if (!codec) { 0372 codec = QTextCodec::codecForLocale(); 0373 } 0374 QString quote = codec->toUnicode(page); 0375 emit m_p->status(i18n("URL found: %1...", url.prettyUrl())); 0376 if (AlkOnlineQuotesProfileManager::instance().webPageEnabled()) 0377 AlkOnlineQuotesProfileManager::instance().webPage()->setContent(quote.toLocal8Bit()); 0378 result = slotParseQuote(quote); 0379 return result; 0380 } 0381 0382 #ifndef BUILD_WITH_QTNETWORK 0383 #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) 0384 0385 bool AlkOnlineQuote::Private::downloadUrl(const QUrl& url) 0386 { 0387 // Create a temporary filename (w/o leaving the file on the filesystem) 0388 // In case the file is still present, the KIO::file_copy operation cannot 0389 // be performed on some operating systems (Windows). 0390 auto tmpFile = new QTemporaryFile; 0391 tmpFile->open(); 0392 auto tmpFileName = QUrl::fromLocalFile(tmpFile->fileName()); 0393 delete tmpFile; 0394 0395 m_eventLoop = new QEventLoop; 0396 KJob *job = KIO::file_copy(url, tmpFileName, -1, KIO::HideProgressInfo); 0397 connect(job, SIGNAL(result(KJob*)), this, SLOT(downloadUrlDone(KJob*))); 0398 0399 if (m_timeout != -1) 0400 QTimer::singleShot(m_timeout, this, SLOT(slotLoadTimeout())); 0401 auto result = m_eventLoop->exec(QEventLoop::ExcludeUserInputEvents); 0402 delete m_eventLoop; 0403 m_eventLoop = nullptr; 0404 0405 return result; 0406 } 0407 0408 void AlkOnlineQuote::Private::downloadUrlDone(KJob* job) 0409 { 0410 QString tmpFileName = dynamic_cast<KIO::FileCopyJob*>(job)->destUrl().toLocalFile(); 0411 QUrl url = dynamic_cast<KIO::FileCopyJob*>(job)->srcUrl(); 0412 0413 bool result; 0414 if (!job->error()) { 0415 qDebug() << "Downloaded" << tmpFileName << "from" << url; 0416 result = processDownloadedFile(url, tmpFileName); 0417 } else { 0418 emit m_p->error(job->errorString()); 0419 m_errors |= Errors::URL; 0420 result = slotParseQuote(QString()); 0421 } 0422 m_eventLoop->exit(result); 0423 } 0424 0425 #else // QT_VERSION 0426 0427 // This is simply a placeholder. It is unused but needs to be present 0428 // to make the linker happy (since the declaration of the slot cannot 0429 // be made dependendant on QT_VERSION with the Qt4 moc compiler. 0430 void AlkOnlineQuote::Private::downloadUrlDone(KJob* job) 0431 { 0432 Q_UNUSED(job); 0433 } 0434 0435 bool AlkOnlineQuote::Private::downloadUrl(const KUrl& url) 0436 { 0437 bool result = false; 0438 0439 QString tmpFile; 0440 if (KIO::NetAccess::download(url, tmpFile, nullptr)) { 0441 // kDebug(Private::dbgArea()) << "Downloaded " << tmpFile; 0442 kDebug(Private::dbgArea()) << "Downloaded" << tmpFile << "from" << url; 0443 result = processDownloadedFile(url, tmpFile); 0444 KIO::NetAccess::removeTempFile(tmpFile); 0445 } else { 0446 emit m_p->error(KIO::NetAccess::lastErrorString()); 0447 m_errors |= Errors::URL; 0448 result = slotParseQuote(QString()); 0449 } 0450 return result; 0451 } 0452 0453 #endif // QT_VERSION 0454 #else // BUILD_WITH_QTNETWORK 0455 0456 void AlkOnlineQuote::Private::downloadUrlDone(QNetworkReply *reply) 0457 { 0458 int result = 0; 0459 if (reply->error() == QNetworkReply::NoError) { 0460 QUrl newUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 0461 if (!newUrl.isEmpty() && newUrl != reply->url()) { 0462 m_url = reply->url().resolved(newUrl); 0463 // TODO migrate to i18n() 0464 emit m_p->status(QString("<font color=\"orange\">%1</font>") 0465 .arg(I18N_NOOP("The URL has been redirected; check an update of the online quote URL"))); 0466 result = 2; 0467 } else { 0468 kDebug(Private::dbgArea()) << "Downloaded data from" << reply->url(); 0469 result = processDownloadedPage(KUrl(reply->url()), reply->readAll()) ? 0 : 1; 0470 } 0471 } else { 0472 emit m_p->error(reply->errorString()); 0473 m_errors |= Errors::URL; 0474 result = slotParseQuote(QString()) ? 0 : 1; 0475 } 0476 m_eventLoop->exit(result); 0477 } 0478 0479 bool AlkOnlineQuote::Private::downloadUrl(const KUrl &url) 0480 { 0481 QNetworkAccessManager manager(this); 0482 connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadUrlDone(QNetworkReply*))); 0483 0484 QNetworkRequest request; 0485 request.setUrl(url); 0486 request.setRawHeader("User-Agent", "alkimia " ALK_VERSION_STRING); 0487 manager.get(request); 0488 0489 if (m_timeout != -1) 0490 QTimer::singleShot(m_timeout, this, SLOT(slotLoadTimeout())); 0491 m_eventLoop = new QEventLoop; 0492 int result = m_eventLoop->exec(QEventLoop::ExcludeUserInputEvents); 0493 delete m_eventLoop; 0494 m_eventLoop = nullptr; 0495 if (result == 2) { 0496 QNetworkRequest request; 0497 request.setUrl(m_url); 0498 request.setRawHeader("User-Agent", "alkimia " ALK_VERSION_STRING); 0499 manager.get(request); 0500 0501 if (m_timeout != -1) 0502 QTimer::singleShot(m_timeout, this, SLOT(slotLoadTimeout())); 0503 m_eventLoop = new QEventLoop; 0504 result = m_eventLoop->exec(QEventLoop::ExcludeUserInputEvents); 0505 delete m_eventLoop; 0506 m_eventLoop = nullptr; 0507 } 0508 return result == 0; 0509 } 0510 #endif // BUILD_WITH_QTNETWORK 0511 0512 #ifdef ENABLE_FINANCEQUOTE 0513 bool AlkOnlineQuote::Private::launchFinanceQuote(const QString &_symbol, const QString &_id, 0514 const QString &_sourcename) 0515 { 0516 bool result = true; 0517 m_symbol = _symbol; 0518 m_id = _id; 0519 m_errors = Errors::None; 0520 m_source = AlkOnlineQuoteSource(_sourcename, m_profile->scriptPath(), 0521 "\"([^,\"]*)\",.*", // symbol regexp 0522 "[^,]*,[^,]*,\"([^\"]*)\"", // price regexp 0523 "[^,]*,([^,]*),.*", // date regexp 0524 "%y-%m-%d"); // date format 0525 0526 //emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id)); 0527 AlkFinanceQuoteProcess tmp; 0528 QString fQSource = m_profile->type() == AlkOnlineQuotesProfile::Type::Script ? 0529 tmp.crypticName(_sourcename) : _sourcename.section(' ', 1); 0530 0531 QStringList args; 0532 args << "perl" << m_profile->scriptPath() << fQSource << m_symbol; 0533 m_filter.clearProgram(); 0534 m_filter << args; 0535 emit m_p->status(i18nc("Executing 'script' 'online source' 'investment symbol' ", 0536 "Executing %1 %2 %3...", args.join(" "), QString(), QString())); 0537 0538 m_filter.setOutputChannelMode(KProcess::MergedChannels); 0539 m_filter.start(); 0540 0541 // This seems to work best if we just block until done. 0542 if (m_filter.waitForFinished()) { 0543 } else { 0544 emit m_p->error(i18n("Unable to launch: %1", m_profile->scriptPath())); 0545 m_errors |= Errors::Script; 0546 result = slotParseQuote(QString()); 0547 } 0548 return result; 0549 } 0550 #endif 0551 0552 bool AlkOnlineQuote::Private::parsePrice(const QString &_pricestr) 0553 { 0554 bool result = true; 0555 // Deal with european quotes that come back as X.XXX,XX or XX,XXX 0556 // 0557 // We will make the assumption that ALL prices have a decimal separator. 0558 // So "1,000" always means 1.0, not 1000.0. 0559 // 0560 // Remove all non-digits from the price string except the last one, and 0561 // set the last one to a period. 0562 QString pricestr(_pricestr); 0563 if (!pricestr.isEmpty()) { 0564 int pos = pricestr.lastIndexOf(QRegExp("\\D")); 0565 if (pos > 0) { 0566 pricestr[pos] = '.'; 0567 pos = pricestr.lastIndexOf(QRegExp("\\D"), pos - 1); 0568 } 0569 while (pos > 0) { 0570 pricestr.remove(pos, 1); 0571 pos = pricestr.lastIndexOf(QRegExp("\\D"), pos); 0572 } 0573 0574 m_price = pricestr.toDouble(); 0575 kDebug(Private::dbgArea()) << "Price" << pricestr; 0576 emit m_p->status(i18n("Price found: '%1' (%2)", pricestr, m_price)); 0577 } else { 0578 m_errors |= Errors::Price; 0579 emit m_p->error(i18n("Unable to parse price for '%1'", m_symbol)); 0580 result = false; 0581 } 0582 return result; 0583 } 0584 0585 bool AlkOnlineQuote::Private::parseDate(const QString &datestr) 0586 { 0587 if (!datestr.isEmpty()) { 0588 emit m_p->status(i18n("Date found: '%1'", datestr)); 0589 0590 AlkDateFormat dateparse(m_source.dateformat()); 0591 try { 0592 m_date = dateparse.convertString(datestr, false /*strict*/); 0593 kDebug(Private::dbgArea()) << "Date" << datestr; 0594 emit m_p->status(i18n("Date format found: '%1' -> '%2'", datestr, m_date.toString())); 0595 } catch (const AlkException &e) { 0596 m_errors |= Errors::DateFormat; 0597 emit m_p->error(i18n("Unable to parse date '%1' using format '%2': %3", datestr, 0598 dateparse.format(), 0599 e.what())); 0600 m_date = QDate::currentDate(); 0601 emit m_p->status(i18n("Using current date for '%1'", m_symbol)); 0602 } 0603 } else { 0604 if (m_source.date().isEmpty()) { 0605 emit m_p->status(i18n("Parsing date is disabled for '%1'", m_symbol)); 0606 } else { 0607 m_errors |= Errors::Date; 0608 emit m_p->error(i18n("Unable to parse date for '%1'", m_symbol)); 0609 } 0610 m_date = QDate::currentDate(); 0611 emit m_p->status(i18n("Using current date for '%1'", m_symbol)); 0612 } 0613 return true; 0614 } 0615 0616 /** 0617 * Parse quote data according to currently selected web price quote source 0618 * 0619 * @param _quotedata quote data to parse 0620 * @return true parsing successful 0621 * @return false parsing unsuccessful 0622 */ 0623 bool AlkOnlineQuote::Private::slotParseQuote(const QString &_quotedata) 0624 { 0625 QString quotedata = _quotedata; 0626 m_quoteData = quotedata; 0627 bool gotprice = false; 0628 bool gotdate = false; 0629 bool result = true; 0630 0631 kDebug(Private::dbgArea()) << "quotedata" << _quotedata; 0632 0633 if (!quotedata.isEmpty()) { 0634 if (!m_source.skipStripping()) { 0635 // 0636 // First, remove extraneous non-data elements 0637 // 0638 0639 // HTML tags 0640 quotedata.remove(QRegExp("<[^>]*>")); 0641 0642 // &...;'s 0643 quotedata.replace(QRegExp("&\\w+;"), " "); 0644 0645 // Extra white space 0646 quotedata = quotedata.simplified(); 0647 kDebug(Private::dbgArea()) << "stripped text" << quotedata; 0648 } 0649 0650 QRegExp symbolRegExp(m_source.sym()); 0651 QRegExp dateRegExp(m_source.date()); 0652 QRegExp priceRegExp(m_source.price()); 0653 0654 if (symbolRegExp.indexIn(quotedata) > -1) { 0655 kDebug(Private::dbgArea()) << "Symbol" << symbolRegExp.cap(1); 0656 emit m_p->status(i18n("Symbol found: '%1'", symbolRegExp.cap(1))); 0657 } else { 0658 m_errors |= Errors::Symbol; 0659 emit m_p->error(i18n("Unable to parse symbol for %1", m_symbol)); 0660 } 0661 0662 if (priceRegExp.indexIn(quotedata) > -1) { 0663 gotprice = true; 0664 QString pricestr = priceRegExp.cap(1); 0665 parsePrice(pricestr); 0666 } else { 0667 parsePrice(QString()); 0668 } 0669 0670 if (dateRegExp.indexIn(quotedata) > -1) { 0671 QString datestr = dateRegExp.cap(1); 0672 gotdate = parseDate(datestr); 0673 } else { 0674 gotdate = parseDate(QString()); 0675 } 0676 0677 if (gotprice && gotdate) { 0678 emit m_p->quote(m_id, m_symbol, m_date, m_price); 0679 } else { 0680 emit m_p->failed(m_id, m_symbol); 0681 result = false; 0682 } 0683 } else { 0684 m_errors |= Errors::Data; 0685 emit m_p->error(i18n("Unable to update price for %1 (empty quote data)", m_symbol)); 0686 emit m_p->failed(m_id, m_symbol); 0687 result = false; 0688 } 0689 return result; 0690 } 0691 0692 void AlkOnlineQuote::Private::slotLoadTimeout() 0693 { 0694 emit m_p->error(i18n("Timeout exceeded on fetching url for %1", m_symbol)); 0695 m_errors |= Errors::Timeout; 0696 emit m_p->failed(m_id, m_symbol); 0697 m_eventLoop->exit(Errors::Timeout); 0698 } 0699 0700 AlkOnlineQuote::AlkOnlineQuote(AlkOnlineQuotesProfile *profile, QObject *_parent) 0701 : QObject(_parent) 0702 , d(new Private(this)) 0703 { 0704 if (profile) 0705 d->m_profile = profile; 0706 else { 0707 d->m_profile = new AlkOnlineQuotesProfile; 0708 d->m_ownProfile = true; 0709 } 0710 } 0711 0712 AlkOnlineQuote::~AlkOnlineQuote() 0713 { 0714 delete d; 0715 } 0716 0717 AlkOnlineQuotesProfile *AlkOnlineQuote::profile() 0718 { 0719 return d->m_profile; 0720 } 0721 0722 void AlkOnlineQuote::setProfile(AlkOnlineQuotesProfile *profile) 0723 { 0724 if (profile && d->m_ownProfile) { 0725 // switching from own profile to external 0726 delete d->m_profile; 0727 d->m_ownProfile = false; 0728 d->m_profile = profile; 0729 0730 } else if (!profile && !d->m_ownProfile) { 0731 // switching from external to own profile 0732 d->m_profile = new AlkOnlineQuotesProfile; 0733 d->m_ownProfile = true; 0734 0735 } else if (profile) { 0736 // exchange external profile 0737 d->m_profile = profile; 0738 } 0739 } 0740 0741 void AlkOnlineQuote::setAcceptLanguage(const QString &language) 0742 { 0743 d->m_acceptLanguage = language; 0744 } 0745 0746 int AlkOnlineQuote::timeout() const 0747 { 0748 return d->m_timeout; 0749 } 0750 0751 void AlkOnlineQuote::setTimeout(int newTimeout) 0752 { 0753 d->m_timeout = newTimeout; 0754 } 0755 0756 bool AlkOnlineQuote::launch(const QString &_symbol, const QString &_id, const QString &_source) 0757 { 0758 #ifdef ENABLE_FINANCEQUOTE 0759 if (AlkOnlineQuoteSource::isFinanceQuote(_source) || 0760 d->m_profile->type() == AlkOnlineQuotesProfile::Type::Script) { 0761 return d->launchFinanceQuote(_symbol, _id, _source); 0762 } else 0763 #endif 0764 if (_source.endsWith(QLatin1String(".css"))) { 0765 return d->launchWebKitCssSelector(_symbol, _id, _source); 0766 } else if (_source.endsWith(QLatin1String(".webkit"))) { 0767 return d->launchWebKitHtmlParser(_symbol, _id, _source); 0768 } else { 0769 return d->launchNative(_symbol, _id, _source); 0770 } 0771 } 0772 0773 const AlkOnlineQuote::Errors &AlkOnlineQuote::errors() 0774 { 0775 return d->m_errors; 0776 } 0777 0778 #include "alkonlinequote.moc"