diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2011-07-04 22:38:03 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2011-07-04 22:38:03 +0000 |
commit | dadc34655c3ab961b0b0b94a10eaaba710f0b5e8 (patch) | |
tree | 99e72842fe687baea16376a147619b6048d7e441 /kmymoney2/converter/webpricequote.cpp | |
download | kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.tar.gz kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.zip |
Added kmymoney
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kmymoney@1239792 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kmymoney2/converter/webpricequote.cpp')
-rw-r--r-- | kmymoney2/converter/webpricequote.cpp | 1050 |
1 files changed, 1050 insertions, 0 deletions
diff --git a/kmymoney2/converter/webpricequote.cpp b/kmymoney2/converter/webpricequote.cpp new file mode 100644 index 0000000..de30963 --- /dev/null +++ b/kmymoney2/converter/webpricequote.cpp @@ -0,0 +1,1050 @@ +/*************************************************************************** + webpricequote.cpp + ------------------- + begin : Thu Dec 30 2004 + copyright : (C) 2004 by Ace Jones + email : Ace Jones <acejones@users.sourceforge.net> + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +// ---------------------------------------------------------------------------- +// QT Headers + +#include <qfile.h> +#include <qregexp.h> +#include <qtextstream.h> +#include <qprocess.h> + +// ---------------------------------------------------------------------------- +// KDE Headers + +#include <kio/netaccess.h> +#include <kio/scheduler.h> +#include <kurl.h> +#include <klocale.h> +#include <kdebug.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kcalendarsystem.h> +#include <ktempfile.h> + +// ---------------------------------------------------------------------------- +// Project Headers + +#include "../mymoney/mymoneyexception.h" +#include "mymoneyqifprofile.h" +#include "webpricequote.h" + +// define static members +QString WebPriceQuote::m_financeQuoteScriptPath; +QStringList WebPriceQuote::m_financeQuoteSources; + +QString * WebPriceQuote::lastErrorMsg; +int WebPriceQuote::lastErrorCode = 0; + +WebPriceQuote::WebPriceQuote( QObject* _parent, const char* _name ): + QObject( _parent, _name ) +{ + m_financeQuoteScriptPath = + KGlobal::dirs()->findResource("appdata", QString("misc/financequote.pl")); + connect(&m_filter,SIGNAL(processExited(const QString&)),this,SLOT(slotParseQuote(const QString&))); +} + +WebPriceQuote::~WebPriceQuote() +{ +} + +bool WebPriceQuote::launch( const QString& _symbol, const QString& _id, const QString& _sourcename ) +{ + if (_sourcename.contains("Finance::Quote")) + return (launchFinanceQuote (_symbol, _id, _sourcename)); + else + return (launchNative (_symbol, _id, _sourcename)); +} + +bool WebPriceQuote::launchNative( const QString& _symbol, const QString& _id, const QString& _sourcename ) { + bool result = true; + m_symbol = _symbol; + m_id = _id; + +// emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id)); + + // if we're running normally, with a UI, we can just get these the normal way, + // from the config file + if ( kapp ) + { + QString sourcename = _sourcename; + if ( sourcename.isEmpty() ) + sourcename = "Yahoo"; + + if ( quoteSources().contains(sourcename) ) + m_source = WebPriceQuoteSource(sourcename); + else + emit error(QString("Source <%1> does not exist.").arg(sourcename)); + } + // otherwise, if we have no kapp, we have no config. so we just get them from + // the defaults + else + { + if ( _sourcename.isEmpty() ) + m_source = defaultQuoteSources()["Yahoo"]; + else + m_source = defaultQuoteSources()[_sourcename]; + } + + KURL url; + + // if the source has room for TWO symbols.. + if ( m_source.m_url.contains("%2") ) + { + // this is a two-symbol quote. split the symbol into two. valid symbol + // characters are: 0-9, A-Z and the dot. anything else is a separator + QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)",false /*case sensitive*/); + + // if we've truly found 2 symbols delimited this way... + if ( splitrx.search(m_symbol) != -1 ) + url = KURL::fromPathOrURL(m_source.m_url.arg(splitrx.cap(1),splitrx.cap(2))); + else + kdDebug(2) << "WebPriceQuote::launch() did not find 2 symbols" << endl; + } + else + // a regular one-symbol quote + url = KURL::fromPathOrURL(m_source.m_url.arg(m_symbol)); + + // If we're running a non-interactive session (with no UI), we can't + // use KIO::NetAccess, so we have to get our web data the old-fashioned + // way... with 'wget'. + // + // Note that a 'non-interactive' session right now means only the test + // cases. Although in the future if KMM gains a non-UI mode, this would + // still be useful + if ( ! kapp && ! url.isLocalFile() ) + url = KURL::fromPathOrURL("/usr/bin/wget -O - " + url.prettyURL()); + + if ( url.isLocalFile() ) + { + emit status(QString("Executing %1...").arg(url.path())); + + m_filter.clearArguments(); + m_filter << QStringList::split(" ",url.path()); + m_filter.setSymbol(m_symbol); + + // if we're running non-interactive, we'll need to block. + // otherwise, just let us know when it's done. + KProcess::RunMode mode = KProcess::NotifyOnExit; + if ( ! kapp ) + mode = KProcess::Block; + + if(m_filter.start(mode, KProcess::All)) + { + result = true; + m_filter.resume(); + } + else + { + emit error(QString("Unable to launch: %1").arg(url.path())); + slotParseQuote(QString()); + } + } + else + { + emit status(QString("Fetching URL %1...").arg(url.prettyURL())); + + QString tmpFile; + if( download( url, tmpFile, NULL ) ) + { + kdDebug(2) << "Downloaded " << tmpFile << endl; + QFile f(tmpFile); + if ( f.open( IO_ReadOnly ) ) + { + result = true; + QString quote = QTextStream(&f).read(); + f.close(); + slotParseQuote(quote); + } + else + { + slotParseQuote(QString()); + } + removeTempFile( tmpFile ); + } + else + { + emit error(KIO::NetAccess::lastErrorString()); + slotParseQuote(QString()); + } + } + return result; +} + +void WebPriceQuote::removeTempFile(const QString& tmpFile) +{ + if(tmpFile == m_tmpFile) { + unlink(tmpFile); + m_tmpFile = QString(); + } +} + +bool WebPriceQuote::download(const KURL& u, QString & target, QWidget* window) +{ + m_tmpFile = QString(); + + // the following code taken and adapted from KIO::NetAccess::download() + if (target.isEmpty()) + { + KTempFile tmpFile; + target = tmpFile.name(); + m_tmpFile = target; + } + + KURL dest; + dest.setPath( target ); + + + // the following code taken and adapted from KIO::NetAccess::filecopyInternal() + bJobOK = true; // success unless further error occurs + + KIO::Scheduler::checkSlaveOnHold(true); + KIO::Job * job = KIO::file_copy( u, dest, -1, true, false, false ); + job->setWindow (window); + job->addMetaData("cache", "reload"); // bypass cache + connect( job, SIGNAL( result (KIO::Job *) ), + this, SLOT( slotResult (KIO::Job *) ) ); + + enter_loop(); + return bJobOK; + +} + +// The following parts are copied and adjusted from KIO::NetAccess + +// If a troll sees this, he kills me +void qt_enter_modal( QWidget *widget ); +void qt_leave_modal( QWidget *widget ); + +void WebPriceQuote::enter_loop(void) +{ + QWidget dummy(0,0,WType_Dialog | WShowModal); + dummy.setFocusPolicy( QWidget::NoFocus ); + qt_enter_modal(&dummy); + qApp->enter_loop(); + qt_leave_modal(&dummy); +} + +void WebPriceQuote::slotResult( KIO::Job * job ) +{ + lastErrorCode = job->error(); + bJobOK = !job->error(); + if ( !bJobOK ) + { + if ( !lastErrorMsg ) + lastErrorMsg = new QString; + *lastErrorMsg = job->errorString(); + } + + qApp->exit_loop(); +} +// The above parts are copied and adjusted from KIO::NetAccess + +bool WebPriceQuote::launchFinanceQuote ( const QString& _symbol, const QString& _id, + const QString& _sourcename ) { + bool result = true; + m_symbol = _symbol; + m_id = _id; + QString FQSource = _sourcename.section (" ", 1); + m_source = WebPriceQuoteSource (_sourcename, m_financeQuoteScriptPath, + "\"([^,\"]*)\",.*", // symbol regexp + "[^,]*,[^,]*,\"([^\"]*)\"", // price regexp + "[^,]*,([^,]*),.*", // date regexp + "%y-%m-%d"); // date format + + //emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id)); + + + m_filter.clearArguments(); + m_filter << "perl" << m_financeQuoteScriptPath << FQSource << KProcess::quote(_symbol); + m_filter.setUseShell(true); + m_filter.setSymbol(m_symbol); + emit status(QString("Executing %1 %2 %3...").arg(m_financeQuoteScriptPath).arg(FQSource).arg(_symbol)); + + // if we're running non-interactive, we'll need to block. + // otherwise, just let us know when it's done. + KProcess::RunMode mode = KProcess::NotifyOnExit; + if ( ! kapp ) + mode = KProcess::Block; + + if(m_filter.start(mode, KProcess::All)) + { + result = true; + m_filter.resume(); + } + else + { + emit error(QString("Unable to launch: %1").arg(m_financeQuoteScriptPath)); + slotParseQuote(QString()); + } + + return result; +} + +void WebPriceQuote::slotParseQuote(const QString& _quotedata) +{ + QString quotedata = _quotedata; + bool gotprice = false; + bool gotdate = false; + +// kdDebug(2) << "WebPriceQuote::slotParseQuote( " << _quotedata << " ) " << endl; + + if ( ! quotedata.isEmpty() ) + { + if(!m_source.m_skipStripping) { + // + // First, remove extranous non-data elements + // + + // HTML tags + quotedata.remove(QRegExp("<[^>]*>")); + + // &...;'s + quotedata.replace(QRegExp("&\\w+;")," "); + + // Extra white space + quotedata = quotedata.simplifyWhiteSpace(); + } + +#if KMM_DEBUG + // Enable to get a look at the data coming back from the source after it's stripped + QFile file("stripped.txt"); + if ( file.open( IO_WriteOnly ) ) + { + QTextStream( &file ) << quotedata; + file.close(); + } +#endif + + QRegExp symbolRegExp(m_source.m_sym); + QRegExp dateRegExp(m_source.m_date); + QRegExp priceRegExp(m_source.m_price); + + if( symbolRegExp.search(quotedata) > -1) + emit status(i18n("Symbol found: %1").arg(symbolRegExp.cap(1))); + + if(priceRegExp.search(quotedata)> -1) + { + gotprice = true; + + // Deal with european quotes that come back as X.XXX,XX or XX,XXX + // + // We will make the assumption that ALL prices have a decimal separator. + // So "1,000" always means 1.0, not 1000.0. + // + // Remove all non-digits from the price string except the last one, and + // set the last one to a period. + QString pricestr = priceRegExp.cap(1); + + int pos = pricestr.findRev(QRegExp("\\D")); + if ( pos > 0 ) + { + pricestr[pos] = '.'; + pos = pricestr.findRev(QRegExp("\\D"),pos-1); + } + while ( pos > 0 ) + { + pricestr.remove(pos,1); + pos = pricestr.findRev(QRegExp("\\D"),pos); + } + + m_price = pricestr.toDouble(); + emit status(i18n("Price found: %1 (%2)").arg(pricestr).arg(m_price)); + } + + if(dateRegExp.search(quotedata) > -1) + { + QString datestr = dateRegExp.cap(1); + + MyMoneyDateFormat dateparse(m_source.m_dateformat); + try + { + m_date = dateparse.convertString( datestr,false /*strict*/ ); + gotdate = true; + emit status(i18n("Date found: %1").arg(m_date.toString()));; + } + catch (MyMoneyException* e) + { + // emit error(i18n("Unable to parse date %1 using format %2: %3").arg(datestr,dateparse.format(),e->what())); + m_date = QDate::currentDate(); + gotdate = true; + delete e; + } + } + + if ( gotprice && gotdate ) + { + emit quote( m_id, m_symbol, m_date, m_price ); + } + else + { + emit error(i18n("Unable to update price for %1").arg(m_symbol)); + emit failed( m_id, m_symbol ); + } + } + else + { + emit error(i18n("Unable to update price for %1").arg(m_symbol)); + emit failed( m_id, m_symbol ); + } +} + +QMap<QString,WebPriceQuoteSource> WebPriceQuote::defaultQuoteSources(void) +{ + QMap<QString,WebPriceQuoteSource> result; + + result["Yahoo"] = WebPriceQuoteSource("Yahoo", + "http://finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d1", + "\"([^,\"]*)\",.*", // symbolregexp + "[^,]*,([^,]*),.*", // priceregexp + "[^,]*,[^,]*,\"([^\"]*)\"", // dateregexp + "%m %d %y" // dateformat + ); + + result["Yahoo Currency"] = WebPriceQuoteSource("Yahoo Currency", + "http://finance.yahoo.com/d/quotes.csv?s=%1%2=X&f=sl1d1", + "\"([^,\"]*)\",.*", // symbolregexp + "[^,]*,([^,]*),.*", // priceregexp + "[^,]*,[^,]*,\"([^\"]*)\"", // dateregexp + "%m %d %y" // dateformat + ); + + // 2009-08-20 Yahoo UK has no quotes and has comma separators + // sl1d1 format for Yahoo UK doesn't seem to give a date ever + // sl1d3 gives US locale time (9:99pm) and date (mm/dd/yyyy) + result["Yahoo UK"] = WebPriceQuoteSource("Yahoo UK", + "http://uk.finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d3", + "^([^,]*),.*", // symbolregexp + "^[^,]*,([^,]*),.*", // priceregexp + "^[^,]*,[^,]*,(.*)", // dateregexp + "%m/%d/%y" // dateformat + ); + + // sl1d1 format for Yahoo France doesn't seem to give a date ever + // sl1d3 gives us time (99h99) and date + result["Yahoo France"] = WebPriceQuoteSource("Yahoo France", + "http://fr.finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d3", + "([^;]*).*", // symbolregexp + "[^;]*.([^;]*),*", // priceregexp + "[^;]*.[^;]*...h...([^;]*)", // dateregexp + "%d/%m/%y" // dateformat + ); + + result["Globe & Mail"] = WebPriceQuoteSource("Globe & Mail", + "http://globefunddb.theglobeandmail.com/gishome/plsql/gis.price_history?pi_fund_id=%1", + QString(), // symbolregexp + "Reinvestment Price \\w+ \\d+, \\d+ (\\d+\\.\\d+)", // priceregexp + "Reinvestment Price (\\w+ \\d+, \\d+)", // dateregexp + "%m %d %y" // dateformat + ); + + result["MSN.CA"] = WebPriceQuoteSource("MSN.CA", + "http://ca.moneycentral.msn.com/investor/quotes/quotes.asp?symbol=%1", + QString(), // symbolregexp + "Net Asset Value (\\d+\\.\\d+)", // priceregexp + "NAV update (\\d+\\D+\\d+\\D+\\d+)", // dateregexp + "%d %m %y" // dateformat + ); + // Finanztreff (replaces VWD.DE) and boerseonline supplied by Micahel Zimmerman + result["Finanztreff"] = WebPriceQuoteSource("Finanztreff", + "http://finanztreff.de/kurse_einzelkurs_detail.htn?u=100&i=%1", + QString(), // symbolregexp + "([0-9]+,\\d+).+Gattung:Fonds", // priceregexp + "\\).(\\d+\\D+\\d+\\D+\\d+)", // dateregexp (doesn't work; date in chart + "%d.%m.%y" // dateformat + ); + + result["boerseonline"] = WebPriceQuoteSource("boerseonline", + "http://www.boerse-online.de/tools/boerse/einzelkurs_kurse.htm?&s=%1", + QString(), // symbolregexp + "Akt\\. Kurs.(\\d+,\\d\\d)", // priceregexp + "Datum.(\\d+\\.\\d+\\.\\d+)", // dateregexp (doesn't work; date in chart + "%d.%m.%y" // dateformat + ); + + // The following two price sources were contributed by + // Marc Zahnlecker <tf2k@users.sourceforge.net> + + result["Wallstreet-Online.DE (Default)"] = WebPriceQuoteSource("Wallstreet-Online.DE (Default)", + "http://www.wallstreet-online.de/si/?k=%1&spid=ws", + "Symbol:(\\w+)", // symbolregexp + "Letzter Kurs: ([0-9.]+,\\d+)", // priceregexp + ", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp + "%d %m %y" // dateformat + ); + + // This quote source provided by Peter Lord + // The trading symbol will normally be the SEDOL (see wikipedia) but + // the flexibility presently (1/2008) in the code will allow use of + // the ISIN or MEXID (FT specific) codes + result["Financial Times UK Funds"] = WebPriceQuoteSource("Financial Times UK Funds", + "http://funds.ft.com/funds/simpleSearch.do?searchArea=%&search=%1", + "SEDOL[\\ ]*(\\d+.\\d+)", // symbol regexp + "\\(GBX\\)[\\ ]*([0-9,]*.\\d+)[\\ ]*", // price regexp + "Valuation date:[\\ ]*(\\d+/\\d+/\\d+)", // date regexp + "%d/%m/%y" // date format + ); + + // This quote source provided by Danny Scott + result["Yahoo Canada"] = WebPriceQuoteSource("Yahoo Canada", + "http://ca.finance.yahoo.com/q?s=%1", + "%1", // symbol regexp + "Last Trade: (\\d+\\.\\d+)", // price regexp + "day, (.\\D+\\d+\\D+\\d+)", // date regexp + "%m %d %y" // date format + ); + + // (tf2k) The "mpid" is I think the market place id. In this case five + // stands for Hamburg. + // + // Here the id for several market places: 2 Frankfurt, 3 Berlin, 4 + // Düsseldorf, 5 Hamburg, 6 München/Munich, 7 Hannover, 9 Stuttgart, 10 + // Xetra, 32 NASDAQ, 36 NYSE + + result["Wallstreet-Online.DE (Hamburg)"] = WebPriceQuoteSource("Wallstreet-Online.DE (Hamburg)", + "http://fonds.wallstreet-online.de/si/?k=%1&spid=ws&mpid=5", + "Symbol:(\\w+)", // symbolregexp + "Fonds \\(EUR\\) ([0-9.]+,\\d+)", // priceregexp + ", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp + "%d %m %y" // dateformat + ); + + // The following price quote was contributed by + // Piotr Adacha <piotr.adacha@googlemail.com> + + // I would like to post new Online Query Settings for KMyMoney. This set is + // suitable to query stooq.com service, providing quotes for stocks, futures, + // mutual funds and other financial instruments from Polish Gielda Papierow + // Wartosciowych (GPW). Unfortunately, none of well-known international + // services provide quotes for this market (biggest one in central and eastern + // Europe), thus, I think it could be helpful for Polish users of KMyMoney (and + // I am one of them for almost a year). + + result["Gielda Papierow Wartosciowych (GPW)"] = WebPriceQuoteSource("Gielda Papierow Wartosciowych (GPW)", + "http://stooq.com/q/?s=%1", + QString(), // symbol regexp + "Kurs.*(\\d+\\.\\d+).*Data", // price regexp + "(\\d{4,4}-\\d{2,2}-\\d{2,2})", // date regexp + "%y %m %d" // date format + ); + + // The following price quote is for getting prices of different funds + // at OMX Baltic market. + result["OMX Baltic funds"] = WebPriceQuoteSource("OMX Baltic funds", + "http://www.baltic.omxgroup.com/market/?pg=nontradeddetails¤cy=0&instrument=%1", + QString(), // symbolregexp + "NAV (\\d+,\\d+)", // priceregexp + "Kpv (\\d+.\\d+.\\d+)", // dateregexp + "%d.%m.%y" // dateformat + ); + + // The following price quote was contributed by + // Peter Hargreaves <pete.h@pdh-online.info> + // The original posting can be found here: + // http://sourceforge.net/mailarchive/message.php?msg_name=200806060854.11682.pete.h%40pdh-online.info + + // I have PEP and ISA accounts which I invest in Funds with Barclays + // Stockbrokers. They give me Fund data via Financial Express: + // + // https://webfund6.financialexpress.net/Clients/Barclays/default.aspx + // + // A typical Fund Factsheet is: + // + // https://webfund6.financialexpress.net/Clients/Barclays/search_factsheet_summary.aspx?code=0585239 + // + // On the Factsheet to identify the fund you can see ISIN Code GB0005852396. + // In the url, this code is shortened by loosing the first four and last + // characters. + // + // Update: + // + // Nick Elliot has contributed a modified regular expression to cope with values presented + // in pounds as well as those presented in pence. The source can be found here: + // http://forum.kde.org/update-stock-and-currency-prices-t-32049.html + + result["Financial Express"] = WebPriceQuoteSource("Financial Express", + "https://webfund6.financialexpress.net/Clients/Barclays/search_factsheet_summary.aspx?code=%1", + "ISIN Code[^G]*(GB..........).*", // symbolregexp + "Current Market Information[^0-9]*([0-9,\\.]+).*", // priceregexp + "Price Date[^0-9]*(../../....).*", // dateregexp + "%d/%m/%y" // dateformat + ); + + return result; +} + +QStringList WebPriceQuote::quoteSources (const _quoteSystemE _system) { + if (_system == Native) + return (quoteSourcesNative()); + else + return (quoteSourcesFinanceQuote()); +} + +QStringList WebPriceQuote::quoteSourcesNative() +{ + KConfig *kconfig = KGlobal::config(); + QStringList groups = kconfig->groupList(); + + QStringList::Iterator it; + QRegExp onlineQuoteSource(QString("^Online-Quote-Source-(.*)$")); + + // get rid of all 'non online quote source' entries + for(it = groups.begin(); it != groups.end(); it = groups.remove(it)) { + if(onlineQuoteSource.search(*it) >= 0) { + // Insert the name part + groups.insert(it, onlineQuoteSource.cap(1)); + } + } + + // if the user has the OLD quote source defined, now is the + // time to remove that entry and convert it to the new system. + if ( ! groups.count() && kconfig->hasGroup("Online Quotes Options") ) + { + kconfig->setGroup("Online Quotes Options"); + QString url(kconfig->readEntry("URL","http://finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d1")); + QString symbolRegExp(kconfig->readEntry("SymbolRegex","\"([^,\"]*)\",.*")); + QString priceRegExp(kconfig->readEntry("PriceRegex","[^,]*,([^,]*),.*")); + QString dateRegExp(kconfig->readEntry("DateRegex","[^,]*,[^,]*,\"([^\"]*)\"")); + kconfig->deleteGroup("Online Quotes Options"); + + groups += "Old Source"; + kconfig->setGroup(QString("Online-Quote-Source-%1").arg("Old Source")); + kconfig->writeEntry("URL", url); + kconfig->writeEntry("SymbolRegex", symbolRegExp); + kconfig->writeEntry("PriceRegex",priceRegExp); + kconfig->writeEntry("DateRegex", dateRegExp); + kconfig->writeEntry("DateFormatRegex", "%m %d %y"); + kconfig->sync(); + } + + // Set up each of the default sources. These are done piecemeal so that + // when we add a new source, it's automatically picked up. + QMap<QString,WebPriceQuoteSource> defaults = defaultQuoteSources(); + QMap<QString,WebPriceQuoteSource>::const_iterator it_source = defaults.begin(); + while ( it_source != defaults.end() ) + { + if ( ! groups.contains( (*it_source).m_name ) ) + { + groups += (*it_source).m_name; + (*it_source).write(); + kconfig->sync(); + } + ++it_source; + } + + return groups; +} + +QStringList WebPriceQuote::quoteSourcesFinanceQuote() +{ + if (m_financeQuoteSources.empty()) { // run the process one time only + FinanceQuoteProcess getList; + m_financeQuoteScriptPath = + KGlobal::dirs()->findResource("appdata", QString("misc/financequote.pl")); + getList.launch( m_financeQuoteScriptPath ); + while (!getList.isFinished()) { + qApp->processEvents(); + } + m_financeQuoteSources = getList.getSourceList(); + } + return (m_financeQuoteSources); +} + +// +// Helper class to load/save an individual source +// + +WebPriceQuoteSource::WebPriceQuoteSource(const QString& name, const QString& url, const QString& sym, const QString& price, const QString& date, const QString& dateformat): + m_name(name), + m_url(url), + m_sym(sym), + m_price(price), + m_date(date), + m_dateformat(dateformat) +{ +} + +WebPriceQuoteSource::WebPriceQuoteSource(const QString& name) +{ + m_name = name; + KConfig *kconfig = KGlobal::config(); + kconfig->setGroup(QString("Online-Quote-Source-%1").arg(m_name)); + m_sym = kconfig->readEntry("SymbolRegex"); + m_date = kconfig->readEntry("DateRegex"); + m_dateformat = kconfig->readEntry("DateFormatRegex","%m %d %y"); + m_price = kconfig->readEntry("PriceRegex"); + m_url = kconfig->readEntry("URL"); + m_skipStripping = kconfig->readBoolEntry("SkipStripping", false); +} + +void WebPriceQuoteSource::write(void) const +{ + KConfig *kconfig = KGlobal::config(); + kconfig->setGroup(QString("Online-Quote-Source-%1").arg(m_name)); + kconfig->writeEntry("URL", m_url); + kconfig->writeEntry("PriceRegex", m_price); + kconfig->writeEntry("DateRegex", m_date); + kconfig->writeEntry("DateFormatRegex", m_dateformat); + kconfig->writeEntry("SymbolRegex", m_sym); + if(m_skipStripping) + kconfig->writeEntry("SkipStripping", m_skipStripping); + else + kconfig->deleteEntry("SkipStripping"); +} + +void WebPriceQuoteSource::rename(const QString& name) +{ + remove(); + m_name = name; + write(); +} + +void WebPriceQuoteSource::remove(void) const +{ + KConfig *kconfig = KGlobal::config(); + kconfig->deleteGroup(QString("Online-Quote-Source-%1").arg(m_name)); +} + +// +// Helper class to babysit the KProcess used for running the local script in that case +// + +WebPriceQuoteProcess::WebPriceQuoteProcess(void) +{ + connect(this, SIGNAL(receivedStdout(KProcess*, char*, int)), this, SLOT(slotReceivedDataFromFilter(KProcess*, char*, int))); + connect(this, SIGNAL(processExited(KProcess*)), this, SLOT(slotProcessExited(KProcess*))); +} + +void WebPriceQuoteProcess::slotReceivedDataFromFilter(KProcess* /*_process*/, char* _pcbuffer, int _nbufferlen) +{ + QByteArray data; + data.duplicate(_pcbuffer, _nbufferlen); + +// kdDebug(2) << "WebPriceQuoteProcess::slotReceivedDataFromFilter(): " << QString(data) << endl; + m_string += QString(data); +} + +void WebPriceQuoteProcess::slotProcessExited(KProcess*) +{ +// kdDebug(2) << "WebPriceQuoteProcess::slotProcessExited()" << endl; + emit processExited(m_string); + m_string.truncate(0); +} + +// +// Helper class to babysit the KProcess used for running the Finance Quote sources script +// + +FinanceQuoteProcess::FinanceQuoteProcess(void) +{ + m_isDone = false; + m_string = ""; + m_fqNames["aex"] = "AEX"; + m_fqNames["aex_futures"] = "AEX Futures"; + m_fqNames["aex_options"] = "AEX Options"; + m_fqNames["amfiindia"] = "AMFI India"; + m_fqNames["asegr"] = "ASE"; + m_fqNames["asia"] = "Asia (Yahoo, ...)"; + m_fqNames["asx"] = "ASX"; + m_fqNames["australia"] = "Australia (ASX, Yahoo, ...)"; + m_fqNames["bmonesbittburns"] = "BMO NesbittBurns"; + m_fqNames["brasil"] = "Brasil (Yahoo, ...)"; + m_fqNames["canada"] = "Canada (Yahoo, ...)"; + m_fqNames["canadamutual"] = "Canada Mutual (Fund Library, ...)"; + m_fqNames["deka"] = "Deka Investments"; + m_fqNames["dutch"] = "Dutch (AEX, ...)"; + m_fqNames["dwsfunds"] = "DWS"; + m_fqNames["europe"] = "Europe (Yahoo, ...)"; + m_fqNames["fidelity"] = "Fidelity (Fidelity, ...)"; + m_fqNames["fidelity_direct"] = "Fidelity Direct"; + m_fqNames["financecanada"] = "Finance Canada"; + m_fqNames["ftportfolios"] = "First Trust (First Trust, ...)"; + m_fqNames["ftportfolios_direct"] = "First Trust Portfolios"; + m_fqNames["fundlibrary"] = "Fund Library"; + m_fqNames["greece"] = "Greece (ASE, ...)"; + m_fqNames["indiamutual"] = "India Mutual (AMFI, ...)"; + m_fqNames["maninv"] = "Man Investments"; + m_fqNames["fool"] = "Motley Fool"; + m_fqNames["nasdaq"] = "Nasdaq (Yahoo, ...)"; + m_fqNames["nz"] = "New Zealand (Yahoo, ...)"; + m_fqNames["nyse"] = "NYSE (Yahoo, ...)"; + m_fqNames["nzx"] = "NZX"; + m_fqNames["platinum"] = "Platinum Asset Management"; + m_fqNames["seb_funds"] = "SEB"; + m_fqNames["sharenet"] = "Sharenet"; + m_fqNames["za"] = "South Africa (Sharenet, ...)"; + m_fqNames["troweprice_direct"] = "T. Rowe Price"; + m_fqNames["troweprice"] = "T. Rowe Price"; + m_fqNames["tdefunds"] = "TD Efunds"; + m_fqNames["tdwaterhouse"] = "TD Waterhouse Canada"; + m_fqNames["tiaacref"] = "TIAA-CREF"; + m_fqNames["trustnet"] = "Trustnet"; + m_fqNames["uk_unit_trusts"] = "U.K. Unit Trusts"; + m_fqNames["unionfunds"] = "Union Investments"; + m_fqNames["tsp"] = "US Govt. Thrift Savings Plan"; + m_fqNames["usfedbonds"] = "US Treasury Bonds"; + m_fqNames["usa"] = "USA (Yahoo, Fool ...)"; + m_fqNames["vanguard"] = "Vanguard"; + m_fqNames["vwd"] = "VWD"; + m_fqNames["yahoo"] = "Yahoo"; + m_fqNames["yahoo_asia"] = "Yahoo Asia"; + m_fqNames["yahoo_australia"] = "Yahoo Australia"; + m_fqNames["yahoo_brasil"] = "Yahoo Brasil"; + m_fqNames["yahoo_europe"] = "Yahoo Europe"; + m_fqNames["yahoo_nz"] = "Yahoo New Zealand"; + m_fqNames["zifunds"] = "Zuerich Investments"; + connect(this, SIGNAL(receivedStdout(KProcess*, char*, int)), this, SLOT(slotReceivedDataFromFilter(KProcess*, char*, int))); + connect(this, SIGNAL(processExited(KProcess*)), this, SLOT(slotProcessExited(KProcess*))); +} + +void FinanceQuoteProcess::slotReceivedDataFromFilter(KProcess* /*_process*/, char* _pcbuffer, int _nbufferlen) +{ + QByteArray data; + data.duplicate(_pcbuffer, _nbufferlen); + +// kdDebug(2) << "WebPriceQuoteProcess::slotReceivedDataFromFilter(): " << QString(data) << endl; + m_string += QString(data); +} + +void FinanceQuoteProcess::slotProcessExited(KProcess*) +{ +// kdDebug(2) << "WebPriceQuoteProcess::slotProcessExited()" << endl; + m_isDone = true; +} + +void FinanceQuoteProcess::launch (const QString& scriptPath) { + clearArguments(); + arguments.append(QCString("perl")); + arguments.append (QCString(scriptPath)); + arguments.append (QCString("-l")); + if (!start(KProcess::NotifyOnExit, KProcess::Stdout)) qFatal ("Unable to start FQ script"); + return; +} + +QStringList FinanceQuoteProcess::getSourceList() { + QStringList raw = QStringList::split(0x0A, m_string); + QStringList sources; + QStringList::iterator it; + for (it = raw.begin(); it != raw.end(); ++it) { + if (m_fqNames[*it].isEmpty()) sources.append(*it); + else sources.append(m_fqNames[*it]); + } + sources.sort(); + return (sources); +} + +const QString FinanceQuoteProcess::crypticName(const QString& niceName) { + QString ret (niceName); + fqNameMap::iterator it; + for (it = m_fqNames.begin(); it != m_fqNames.end(); ++it) { + if (niceName == it.data()) { + ret = it.key(); + break; + } + } + return (ret); +} + +const QString FinanceQuoteProcess::niceName(const QString& crypticName) { + QString ret (m_fqNames[crypticName]); + if (ret.isEmpty()) ret = crypticName; + return (ret); +} +// +// Universal date converter +// + +// In 'strict' mode, this is designed to be compatable with the QIF profile date +// converter. However, that converter deals with the concept of an apostrophe +// format in a way I don't understand. So for the moment, they are 99% +// compatable, waiting on that issue. (acejones) + +QDate MyMoneyDateFormat::convertString(const QString& _in, bool _strict, unsigned _centurymidpoint) const +{ + // + // Break date format string into component parts + // + + QRegExp formatrex("%([mdy]+)(\\W+)%([mdy]+)(\\W+)%([mdy]+)",false /* case sensitive */); + if ( formatrex.search(m_format) == -1 ) + { + throw new MYMONEYEXCEPTION("Invalid format string"); + } + + QStringList formatParts; + formatParts += formatrex.cap(1); + formatParts += formatrex.cap(3); + formatParts += formatrex.cap(5); + + QStringList formatDelimiters; + formatDelimiters += formatrex.cap(2); + formatDelimiters += formatrex.cap(4); + + // + // Break input string up into component parts, + // using the delimiters found in the format string + // + + QRegExp inputrex; + inputrex.setCaseSensitive(false); + + // strict mode means we must enforce the delimiters as specified in the + // format. non-strict allows any delimiters + if ( _strict ) + inputrex.setPattern(QString("(\\w+)%1(\\w+)%2(\\w+)").arg(formatDelimiters[0],formatDelimiters[1])); + else + inputrex.setPattern("(\\w+)\\W+(\\w+)\\W+(\\w+)"); + + if ( inputrex.search(_in) == -1 ) + { + throw new MYMONEYEXCEPTION("Invalid input string"); + } + + QStringList scannedParts; + scannedParts += inputrex.cap(1).lower(); + scannedParts += inputrex.cap(2).lower(); + scannedParts += inputrex.cap(3).lower(); + + // + // Convert the scanned parts into actual date components + // + + unsigned day = 0, month = 0, year = 0; + bool ok; + QRegExp digitrex("(\\d+)"); + QStringList::const_iterator it_scanned = scannedParts.begin(); + QStringList::const_iterator it_format = formatParts.begin(); + while ( it_scanned != scannedParts.end() ) + { + switch ( (*it_format)[0] ) + { + case 'd': + // remove any extraneous non-digits (e.g. read "3rd" as 3) + ok = false; + if ( digitrex.search(*it_scanned) != -1 ) + day = digitrex.cap(1).toUInt(&ok); + if ( !ok || day > 31 ) + throw new MYMONEYEXCEPTION(QString("Invalid day entry: %1").arg(*it_scanned)); + break; + case 'm': + month = (*it_scanned).toUInt(&ok); + if ( !ok ) + { + // maybe it's a textual date + unsigned i = 1; + while ( i <= 12 ) + { + if(KGlobal::locale()->calendar()->monthName(i, 2000, true).lower() == *it_scanned + || KGlobal::locale()->calendar()->monthName(i, 2000, false).lower() == *it_scanned) + month = i; + ++i; + } + } + + if ( month < 1 || month > 12 ) + throw new MYMONEYEXCEPTION(QString("Invalid month entry: %1").arg(*it_scanned)); + + break; + case 'y': + if ( _strict && (*it_scanned).length() != (*it_format).length()) + throw new MYMONEYEXCEPTION(QString("Length of year (%1) does not match expected length (%2).") + .arg(*it_scanned,*it_format)); + + year = (*it_scanned).toUInt(&ok); + + if (!ok) + throw new MYMONEYEXCEPTION(QString("Invalid year entry: %1").arg(*it_scanned)); + + // + // 2-digit year case + // + // this algorithm will pick a year within +/- 50 years of the + // centurymidpoint parameter. i.e. if the midpoint is 2000, + // then 0-49 will become 2000-2049, and 50-99 will become 1950-1999 + if ( year < 100 ) + { + unsigned centuryend = _centurymidpoint + 50; + unsigned centurybegin = _centurymidpoint - 50; + + if ( year < centuryend % 100 ) + year += 100; + year += centurybegin - centurybegin % 100; + } + + if ( year < 1900 ) + throw new MYMONEYEXCEPTION(QString("Invalid year (%1)").arg(year)); + + break; + default: + throw new MYMONEYEXCEPTION("Invalid format character"); + } + + ++it_scanned; + ++it_format; + } + + QDate result(year,month,day); + if ( ! result.isValid() ) + throw new MYMONEYEXCEPTION(QString("Invalid date (yr%1 mo%2 dy%3)").arg(year).arg(month).arg(day)); + + return result; +} + +// +// Unit test helpers +// + +convertertest::QuoteReceiver::QuoteReceiver(WebPriceQuote* q, QObject* parent, const char *name) : + QObject(parent,name) +{ + connect(q,SIGNAL(quote(const QString&,const QDate&, const double&)), + this,SLOT(slotGetQuote(const QString&,const QDate&, const double&))); + connect(q,SIGNAL(status(const QString&)), + this,SLOT(slotStatus(const QString&))); + connect(q,SIGNAL(error(const QString&)), + this,SLOT(slotError(const QString&))); +} + +convertertest::QuoteReceiver::~QuoteReceiver() +{ +} + +void convertertest::QuoteReceiver::slotGetQuote(const QString&,const QDate& d, const double& m) +{ +// kdDebug(2) << "test::QuoteReceiver::slotGetQuote( , " << d << " , " << m.toString() << " )" << endl; + + m_price = MyMoneyMoney(m); + m_date = d; +} +void convertertest::QuoteReceiver::slotStatus(const QString& msg) +{ +// kdDebug(2) << "test::QuoteReceiver::slotStatus( " << msg << " )" << endl; + + m_statuses += msg; +} +void convertertest::QuoteReceiver::slotError(const QString& msg) +{ +// kdDebug(2) << "test::QuoteReceiver::slotError( " << msg << " )" << endl; + + m_errors += msg; +} + +// vim:cin:si:ai:et:ts=2:sw=2: + +#include "webpricequote.moc" |