diff --git a/kmymoney/main.cpp b/kmymoney/main.cpp index 722a5b02c..a3d56a054 100644 --- a/kmymoney/main.cpp +++ b/kmymoney/main.cpp @@ -1,366 +1,378 @@ /*************************************************************************** main.cpp ------------------- copyright : (C) 2001 by Michael Edwardes email : mte@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. * * * ***************************************************************************/ #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include +#include +#include #ifdef KMM_DBUS #include #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoney/mymoneyfile.h" #include "mymoneyexception.h" #include "kmymoney.h" #include "kstartuplogo.h" #include "kcreditswindow.h" #include "kmymoneyutils.h" #include "kmymoneysettings.h" #include "misc/webconnect.h" #include "platformtools.h" #ifdef KMM_DEBUG #include "mymoneyutils.h" #include "mymoneytracer.h" #endif bool timersOn = false; KMyMoneyApp* kmymoney; static int runKMyMoney(QApplication& a, std::unique_ptr splash, const QUrl & file, bool noFile); static void migrateConfigFiles(); int main(int argc, char *argv[]) { /** * Create application first */ QApplication app(argc, argv); KLocalizedString::setApplicationDomain("kmymoney"); migrateConfigFiles(); /** * construct and register about data */ KAboutData aboutData(QStringLiteral("kmymoney"), i18n("KMyMoney"), QStringLiteral(VERSION)); aboutData.setOrganizationDomain("kde.org"); KAboutData::setApplicationData(aboutData); QStringList fileUrls; bool isNoCatchOption = false; bool isNoFileOption = false; #ifdef KMM_DEBUG bool isDumpActionsOption = false; #endif if (argc != 0) { /** * Create command line parser and feed it with known options */ QCommandLineParser parser; aboutData.setupCommandLine(&parser); // language // const QCommandLineOption langOption(QStringLiteral("lang"), i18n("language to be used")); // parser.addOption(langOption); // no file const QCommandLineOption noFileOption(QStringLiteral("n"), i18n("do not open last used file")); parser.addOption(noFileOption); // timers const QCommandLineOption timersOption(QStringLiteral("timers"), i18n("enable performance timers")); parser.addOption(timersOption); // no catch const QCommandLineOption noCatchOption(QStringLiteral("nocatch"), i18n("do not globally catch uncaught exceptions")); parser.addOption(noCatchOption); #ifdef KMM_DEBUG // The following options are only available when compiled in debug mode // trace const QCommandLineOption traceOption(QStringLiteral("trace"), i18n("turn on program traces")); parser.addOption(traceOption); // dump actions const QCommandLineOption dumpActionsOption(QStringLiteral("dump-actions"), i18n("dump the names of all defined QAction objects to stdout and quit")); parser.addOption(dumpActionsOption); #endif // INSERT YOUR COMMANDLINE OPTIONS HERE // url to open parser.addPositionalArgument(QStringLiteral("url"), i18n("file to open")); /** * do the command line parsing */ parser.parse(QApplication::arguments()); bool ishelpSet = parser.isSet(QStringLiteral("help")); if (ishelpSet || parser.isSet(QStringLiteral("author")) || parser.isSet(QStringLiteral("license"))) { aboutData = initializeCreditsData(); if (ishelpSet) parser.showHelp(); } if (parser.isSet(QStringLiteral("version"))) parser.showVersion(); /** * handle standard options */ aboutData.processCommandLine(&parser); #ifdef KMM_DEBUG if (parser.isSet(traceOption)) MyMoneyTracer::on(); timersOn = parser.isSet(timersOption); isDumpActionsOption = parser.isSet(dumpActionsOption); #endif isNoCatchOption = parser.isSet(noCatchOption); isNoFileOption = parser.isSet(noFileOption); fileUrls = parser.positionalArguments(); } // create the singletons before we start memory checking // to avoid false error reports auto file = MyMoneyFile::instance(); Q_UNUSED(file) KMyMoneyUtils::checkConstants(); // show startup logo std::unique_ptr splash(KMyMoneySettings::showSplash() ? createStartupLogo() : nullptr); app.processEvents(); // setup the MyMoneyMoney locale settings according to the KDE settings MyMoneyMoney::setThousandSeparator(QLocale().groupSeparator()); MyMoneyMoney::setDecimalSeparator(QLocale().decimalPoint()); // TODO: port to kf5 (negative numbers in parens) //MyMoneyMoney::setNegativeMonetarySignPosition(static_cast(KLocale::global()->negativeMonetarySignPosition())); //MyMoneyMoney::setPositiveMonetarySignPosition(static_cast(KLocale::global()->positiveMonetarySignPosition())); MyMoneyMoney::setNegativePrefixCurrencySymbol(platformTools::currencySymbolPosition(true) < platformTools::AfterQuantityMoney); MyMoneyMoney::setPositivePrefixCurrencySymbol(platformTools::currencySymbolPosition(false) < platformTools::AfterQuantityMoney); // QString language = parser.value(langOption); // if (!language.isEmpty()) { //if (!KLocale::global()->setLanguage(QStringList() << language)) { // qWarning("Unable to select language '%s'. This has one of two reasons:\n\ta) the standard KDE message catalog is not installed\n\tb) the KMyMoney message catalog is not installed", qPrintable(language)); //} // } kmymoney = new KMyMoneyApp(); #ifdef KMM_DEBUG if (isDumpActionsOption) { kmymoney->dumpActions(); // Before we delete the application, we make sure that we destroy all // widgets by running the event loop for some time to catch all those // widgets that are requested to be destroyed using the deleteLater() method. //QApplication::eventLoop()->processEvents(QEventLoop::ExcludeUserInput, 10); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 10); delete kmymoney; exit(0); } #endif QString fname; // in case a filename is provided we need to check if it is a local // file. In case the name does not start with "file://" or "./" or "/" // we need to prepend "./" to fake a relative filename. Otherwise, QUrl prepends // "http://" and uses the full path which will not work. + // On MS-Windows we also need to check if the filename starts with a + // drive letter or the backslash variants. // // The handling might be different on other OSes if (!fileUrls.isEmpty()) { fname = fileUrls.front(); QFileInfo fi(fname); - if (fi.isFile() && !fname.startsWith(QLatin1String("file://")) - && !fname.startsWith(QLatin1String("./")) - && !fname.startsWith(QLatin1String("/"))) { + auto needLeadIn = fi.isFile(); +#ifdef Q_OS_WIN + QRegularExpression exp("^[a-z]:", QRegularExpression::CaseInsensitiveOption); + needLeadIn &= !exp.match(fname).hasMatch() + && !fname.startsWith(QLatin1String(".\\")) + && !fname.startsWith(QLatin1String("\\")); +#endif + needLeadIn &= !fname.startsWith(QLatin1String("file://")) + && !fname.startsWith(QLatin1String("./")) + && !fname.startsWith(QLatin1String("/")); + if (needLeadIn) { fname.prepend(QLatin1String("./")); } } const QUrl url = QUrl::fromUserInput(fname, QLatin1String("."), QUrl::AssumeLocalFile); int rc = 0; if (isNoCatchOption) { qDebug("Running w/o global try/catch block"); rc = runKMyMoney(app, std::move(splash), url, isNoFileOption); } else { try { rc = runKMyMoney(app, std::move(splash), url, isNoFileOption); } catch (const MyMoneyException &e) { KMessageBox::detailedError(0, i18n("Uncaught error. Please report the details to the developers"), QString::fromLatin1(e.what())); throw; } } return rc; } int runKMyMoney(QApplication& a, std::unique_ptr splash, const QUrl & file, bool noFile) { bool instantQuit = false; /** * enable high dpi icons */ a.setAttribute(Qt::AA_UseHighDpiPixmaps); if (kmymoney->webConnect()->isClient()) { // If the user launches a second copy of the app and includes a file to // open, they are probably attempting a "WebConnect" session. In this case, // we'll check to make sure it's an importable file that's passed in, and if so, we'll // notify the primary instance of the file and kill ourselves. if (file.isValid()) { if (kmymoney->isImportableFile(file)) { instantQuit = true; kmymoney->webConnect()->loadFile(file); } } } kmymoney->centralWidget()->setEnabled(false); // force complete paint of widgets qApp->processEvents(); if (!instantQuit) { QString importfile; QUrl url; // make sure, we take the file provided on the command // line before we go and open the last one used if (file.isValid()) { // Check to see if this is an importable file, as opposed to a loadable // file. If it is importable, what we really want to do is load the // last used file anyway and then immediately import this file. This // implements a "web connect" session where there is not already an // instance of the program running. if (kmymoney->isImportableFile(file)) { importfile = file.path(); url = QUrl::fromUserInput(kmymoney->readLastUsedFile()); } else { url = file; } } else { url = QUrl::fromUserInput(kmymoney->readLastUsedFile()); } KTipDialog::showTip(kmymoney, QString(), false); if (url.isValid() && !noFile) { kmymoney->slotFileOpenRecent(url); } else if (KMyMoneySettings::firstTimeRun()) { // resetting the splash here is needed for windows to have access // to the new file wizard splash.reset(); kmymoney->slotFileNew(); } KMyMoneySettings::setFirstTimeRun(false); if (!importfile.isEmpty()) kmymoney->webConnect(importfile, QByteArray()); } else { // the instantQuit flag is set, so we force the app to quit right away kmymoney->slotFileQuit(); } kmymoney->centralWidget()->setEnabled(true); kmymoney->show(); splash.reset(); const int rc = a.exec(); //krazy:exclude=crashy return rc; } static void migrateConfigFiles() { const QString sMainConfigName(QStringLiteral("kmymoneyrc")); const QString sMainConfigSubdirectory(QStringLiteral("kmymoney/")); // all KMM config files should be in ~/.config/kmymoney/ const QString sMainConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + sMainConfigSubdirectory; if (!QFile::exists(sMainConfigPath + sMainConfigName)) { // if main config file doesn't exist, then it's first run // it could be migration from KDE4 to KF5 so prepare list of configuration files to migrate QStringList sConfigNames { sMainConfigName, QStringLiteral("csvimporterrc"), QStringLiteral("printcheckpluginrc"), QStringLiteral("icalendarexportpluginrc"), QStringLiteral("kbankingrc"), }; // Copy KDE 4 config files to the KF5 location Kdelibs4ConfigMigrator migrator(QStringLiteral("kmymoney")); migrator.setConfigFiles(sConfigNames); migrator.setUiFiles(QStringList{QStringLiteral("kmymoneyui.rc")}); migrator.migrate(); QFileInfo fileInfo(sMainConfigPath + sMainConfigName); QDir().mkpath(fileInfo.absolutePath()); const QString sOldMainConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/'); // some files have changed their names during switch to KF5, so prepare map for name replacements QMap configNamesChange { {QStringLiteral("printcheckpluginrc"), QStringLiteral("checkprintingrc")}, {QStringLiteral("icalendarexportpluginrc"), QStringLiteral("icalendarexporterrc")} }; for (const auto& sConfigName : sConfigNames) { const auto sOldConfigFilename = sOldMainConfigPath + sConfigName; const auto sNewConfigFilename = sMainConfigPath + configNamesChange.value(sConfigName, sConfigName); if (QFile::exists(sOldConfigFilename)) { if (QFile::copy(sOldConfigFilename, sNewConfigFilename)) QFile::remove(sOldConfigFilename); } } } KConfig::setMainConfigName(sMainConfigSubdirectory + sMainConfigName); // otherwise it would be ~/.config/kmymoneyrc and not ~/.config/kmymoney/kmymoneyrc } diff --git a/kmymoney/mymoney/CMakeLists.txt b/kmymoney/mymoney/CMakeLists.txt index 0042e4c3e..21ced0845 100644 --- a/kmymoney/mymoney/CMakeLists.txt +++ b/kmymoney/mymoney/CMakeLists.txt @@ -1,117 +1,118 @@ add_subdirectory( storage ) add_subdirectory( payeeidentifier ) ########### next target ############### set(kmm_mymoney_LIB_SRCS mymoneymoney.cpp mymoneyfinancialcalculator.cpp mymoneytransactionfilter.cpp mymoneyfile.cpp mymoneykeyvaluecontainer.cpp mymoneyobject.cpp mymoneypayeeidentifiercontainer.cpp mymoneysplit.cpp mymoneyinstitution.cpp mymoneyinvesttransaction.cpp mymoneyutils.cpp mymoneysecurity.cpp mymoneytransaction.cpp mymoneyschedule.cpp mymoneypayee.cpp mymoneytracer.cpp mymoneytag.cpp mymoneycategory.cpp mymoneycostcenter.cpp mymoneyaccount.cpp mymoneyaccountloan.cpp mymoneyreport.cpp mymoneystatement.cpp mymoneyprice.cpp mymoneybudget.cpp mymoneyforecast.cpp mymoneybalancecache.cpp onlinejob.cpp onlinejobadministration.cpp onlinejobmessage.cpp onlinejobfolder.cpp mymoneycontact.cpp payeeidentifiermodel.cpp ) # storage_a_SOURCES cannot be set in storage directory on MS Windows # because, while building kmm_storage, linker reports many undefined symbols # which are in fact available in kmm_mymoney set(storage_a_SOURCES ./storage/imymoneystorageformat.cpp ./storage/mymoneystoragemgr.cpp ) set(onlineTask_a_SOURCES ./onlinetasks/sepa/sepaonlinetransferimpl.cpp ./onlinetasks/sepa/sepaonlinetransfer.cpp ./onlinetasks/unavailabletask/tasks/unavailabletask.cpp ) # NOTE: this payeeIdentifier and its cmake file cannot be used as a template # This payeeIdentifier must be linked with payeeIdentifierLoader because it # is a fallback if something failed during loading of plugins (for xml files only) set(payeeidentifier_a_SOURCES ./payeeidentifier/ibanbic/ibanbic.cpp ./payeeidentifier/nationalaccount/nationalaccount.cpp ./payeeidentifier/unavailableplugin/unavailableplugin.cpp ) list(APPEND storage_a_SOURCES $<$,$,$>: ./storage/mymoneystoragedump.cpp>) list(APPEND kmm_mymoney_LIB_SRCS ${storage_a_SOURCES}) list(APPEND kmm_mymoney_LIB_SRCS ${onlineTask_a_SOURCES}) list(APPEND kmm_mymoney_LIB_SRCS ${payeeidentifier_a_SOURCES}) set(mymoney_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/kmm_mymoney_export.h mymoneyobject.h mymoneyaccount.h mymoneycategory.h mymoneyexception.h mymoneyfile.h mymoneyfinancialcalculator.h mymoneyinstitution.h mymoneyinvesttransaction.h mymoneykeyvaluecontainer.h mymoneymoney.h mymoneypayee.h mymoneytag.h mymoneyprice.h mymoneyreport.h mymoneyschedule.h mymoneysecurity.h mymoneysplit.h mymoneystatement.h mymoneytransactionfilter.h mymoneytransaction.h mymoneyutils.h mymoneybudget.h mymoneyforecast.h imymoneyprocessingcalendar.h mymoneycostcenter.h + mymoneyenums.h mymoneyunittestable.h mymoneypayeeidentifiercontainer.h onlinejob.h onlinejobtyped.h onlinejobmessage.h onlinejobfolder.h ) add_library(kmm_mymoney SHARED ${kmm_mymoney_LIB_SRCS}) generate_export_header(kmm_mymoney BASE_NAME kmm_mymoney) target_link_libraries(kmm_mymoney PUBLIC kmm_icons Qt5::Xml Qt5::Core Qt5::Gui KF5::Service KF5::I18n Alkimia::alkimia kmm_payeeidentifier kmm_plugin # TODO: fix this KF5::XmlGui PRIVATE onlinetask_interfaces ) if(ENABLE_ADDRESSBOOK) target_link_libraries(kmm_mymoney PUBLIC KF5::IdentityManagement KF5::AkonadiCore KF5::Contacts) endif() set_target_properties(kmm_mymoney PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) ########### install files ############### install(TARGETS kmm_mymoney ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES ${mymoney_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel) ############## tests #################### if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/kmymoney/plugins/ofx/import/ofximporter.cpp b/kmymoney/plugins/ofx/import/ofximporter.cpp index 58d26e754..deba34573 100644 --- a/kmymoney/plugins/ofx/import/ofximporter.cpp +++ b/kmymoney/plugins/ofx/import/ofximporter.cpp @@ -1,911 +1,909 @@ /* * Copyright 2005 Ace Jones acejones@users.sourceforge.net * Copyright 2010-2018 Thomas Baumgart tbaumgart@kde.org * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "ofximporter.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include "konlinebankingstatus.h" #include "konlinebankingsetupwizard.h" #include "kofxdirectconnectdlg.h" #include "mymoneyaccount.h" #include "mymoneyexception.h" #include "mymoneystatement.h" #include "mymoneystatementreader.h" #include "statementinterface.h" #include "importinterface.h" #include "viewinterface.h" #include "ui_importoption.h" #include "kmymoneyutils.h" //#define DEBUG_LIBOFX #ifdef IS_APPIMAGE #include #include #endif using KWallet::Wallet; class OFXImporter::Private { public: Private() : m_valid(false), m_preferName(PreferId), m_walletIsOpen(false), m_statusDlg(0), m_wallet(0), m_updateStartDate(QDate(1900,1,1)), m_timestampOffset(0) {} bool m_valid; enum NamePreference { PreferId = 0, PreferName, PreferMemo } m_preferName; bool m_walletIsOpen; QList m_statementlist; QList m_securitylist; QString m_fatalerror; QStringList m_infos; QStringList m_warnings; QStringList m_errors; KOnlineBankingStatus* m_statusDlg; Wallet *m_wallet; QDate m_updateStartDate; int m_timestampOffset; }; OFXImporter::OFXImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "ofximporter"), /* * the string in the line above must be the same as * X-KDE-PluginInfo-Name and the provider name assigned in * OfxImporterPlugin::onlineBankingSettings() */ KMyMoneyPlugin::ImporterPlugin(), d(new Private) { Q_UNUSED(args) const auto componentName = QLatin1String("ofximporter"); const auto rcFileName = QLatin1String("ofximporter.rc"); setComponentName(componentName, i18n("OFX Importer")); #ifdef IS_APPIMAGE const QString rcFilePath = QCoreApplication::applicationDirPath() + QLatin1String("/../share/kxmlgui5/") + componentName + QLatin1Char('/') + rcFileName; setXMLFile(rcFilePath); const QString localRcFilePath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).first() + QLatin1Char('/') + componentName + QLatin1Char('/') + rcFileName; setLocalXMLFile(localRcFilePath); #else setXMLFile(rcFileName); #endif createActions(); // For ease announce that we have been loaded. qDebug("Plugins: ofximporter loaded"); } OFXImporter::~OFXImporter() { delete d; qDebug("Plugins: ofximporter unloaded"); } void OFXImporter::createActions() { const auto &kpartgui = QStringLiteral("file_import_ofx"); auto a = actionCollection()->addAction(kpartgui); a->setText(i18n("OFX...")); connect(a, &QAction::triggered, this, static_cast(&OFXImporter::slotImportFile)); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void OFXImporter::slotImportFile() { QWidget * widget = new QWidget; Ui_ImportOption* option = new Ui_ImportOption; option->setupUi(widget); QUrl url = importInterface()->selectFile(i18n("OFX import file selection"), QString(), QStringLiteral("*.ofx *.qfx *.ofc|OFX files (*.ofx *.qfx *.ofc);;*|All files (*)"), QFileDialog::ExistingFile, widget); d->m_preferName = static_cast(option->m_preferName->currentIndex()); if (url.isValid()) { const QString filename(url.toLocalFile()); if (isMyFormat(filename)) { statementInterface()->resetMessages(); slotImportFile(filename); statementInterface()->showMessages(d->m_statementlist.count()); } else { KMessageBox::error(0, i18n("Unable to import %1 using the OFX importer plugin. This file is not the correct format.", url.toDisplayString()), i18n("Incorrect format")); } } delete option; delete widget; } QString OFXImporter::formatName() const { return QStringLiteral("OFX"); } QString OFXImporter::formatFilenameFilter() const { return QStringLiteral("*.ofx *.qfx *.ofc"); } bool OFXImporter::isMyFormat(const QString& filename) const { // filename is considered an Ofx file if it contains // the tag "" or "" in the first 20 lines. // which contain some data bool result = false; QFile f(filename); if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { - qDebug() << "OFX: file opened"; QTextStream ts(&f); int lineCount = 20; while (!ts.atEnd() && !result && lineCount != 0) { // get a line of data and remove all unnecessary whitepace chars QString line = ts.readLine().simplified(); - qDebug() << "OFX:" << line; if (line.contains(QStringLiteral(""), Qt::CaseInsensitive) || line.contains(QStringLiteral(""), Qt::CaseInsensitive)) result = true; // count only lines that contain some non white space chars if (!line.isEmpty()) lineCount--; } f.close(); } else { qDebug() << "OFXImporter::isMyFormat: unable to open" << filename << "with" << f.errorString(); } return result; } bool OFXImporter::import(const QString& filename) { d->m_fatalerror = i18n("Unable to parse file"); d->m_valid = false; d->m_errors.clear(); d->m_warnings.clear(); d->m_infos.clear(); d->m_statementlist.clear(); d->m_securitylist.clear(); QByteArray filename_deep = QFile::encodeName(filename); ofx_STATUS_msg = true; ofx_INFO_msg = true; ofx_WARNING_msg = true; ofx_ERROR_msg = true; #ifdef DEBUG_LIBOFX ofx_PARSER_msg = true; ofx_DEBUG_msg = true; ofx_DEBUG1_msg = true; ofx_DEBUG2_msg = true; ofx_DEBUG3_msg = true; ofx_DEBUG4_msg = true; ofx_DEBUG5_msg = true; #endif LibofxContextPtr ctx = libofx_get_new_context(); Q_CHECK_PTR(ctx); // Don't show the position that caused a message to be shown // This has no setter (see libofx.h) ofx_show_position = false; qDebug("setup callback routines"); ofx_set_transaction_cb(ctx, ofxTransactionCallback, this); ofx_set_statement_cb(ctx, ofxStatementCallback, this); ofx_set_account_cb(ctx, ofxAccountCallback, this); ofx_set_security_cb(ctx, ofxSecurityCallback, this); ofx_set_status_cb(ctx, ofxStatusCallback, this); qDebug("process data"); libofx_proc_file(ctx, filename_deep, AUTODETECT); qDebug("process data done"); libofx_free_context(ctx); if (d->m_valid) { d->m_fatalerror.clear(); d->m_valid = storeStatements(d->m_statementlist); } return d->m_valid; } QString OFXImporter::lastError() const { if (d->m_errors.count() == 0) return d->m_fatalerror; return d->m_errors.join(QStringLiteral("

")); } /* __________________________________________________________________________ * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA * * Static callbacks for LibOFX * * YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY */ int OFXImporter::ofxTransactionCallback(struct OfxTransactionData data, void * pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); MyMoneyStatement& s = pofx->back(); MyMoneyStatement::Transaction t; if (data.date_posted_valid) { QDateTime dt; dt.setTime_t(data.date_posted - pofx->d->m_timestampOffset * 60); t.m_datePosted = dt.date(); } else if (data.date_initiated_valid) { QDateTime dt; dt.setTime_t(data.date_initiated - pofx->d->m_timestampOffset * 60); t.m_datePosted = dt.date(); } if (t.m_datePosted.isValid()) { // verify the transaction date is one we want if (t.m_datePosted < pofx->d->m_updateStartDate) { //kDebug(0) << "discarding transaction dated" << qPrintable(t.m_datePosted.toString(Qt::ISODate)); return 0; } } if (data.amount_valid) { t.m_amount = MyMoneyMoney(data.amount, 1000); } if (data.check_number_valid) { t.m_strNumber = QString::fromUtf8(data.check_number); } if (data.fi_id_valid) { t.m_strBankID = QStringLiteral("ID ") + QString::fromUtf8(data.fi_id); } else if (data.reference_number_valid) { t.m_strBankID = QStringLiteral("REF ") + QString::fromUtf8(data.reference_number); } // Decide whether to use NAME, PAYEEID or MEMO to construct the payee bool validity[3] = {false, false, false}; QStringList values; switch (pofx->d->m_preferName) { case OFXImporter::Private::PreferId: // PAYEEID default: validity[0] = data.payee_id_valid; validity[1] = data.name_valid; validity[2] = data.memo_valid; values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.name); values += QString::fromUtf8(data.memo); break; case OFXImporter::Private::PreferName: // NAME validity[0] = data.name_valid; validity[1] = data.payee_id_valid; validity[2] = data.memo_valid; values += QString::fromUtf8(data.name); values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.memo); break; case OFXImporter::Private::PreferMemo: // MEMO validity[0] = data.memo_valid; validity[1] = data.payee_id_valid; validity[2] = data.name_valid; values += QString::fromUtf8(data.memo); values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.name); break; } // for investment transactions we don't use the meme as payee if (data.invtransactiontype_valid) { values.clear(); validity[0] = data.payee_id_valid; validity[1] = data.name_valid; validity[2] = false; values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.name); } for (int idx = 0; idx < 3; ++idx) { if (validity[idx]) { t.m_strPayee = values[idx]; break; } } // extract memo field if we haven't used it as payee if ((data.memo_valid) && (pofx->d->m_preferName != OFXImporter::Private::PreferMemo)) { t.m_strMemo = QString::fromUtf8(data.memo); } // If the payee or memo fields are blank, set them to // the other one which is NOT blank. (acejones) if (t.m_strPayee.isEmpty()) { // But we only create a payee for non-investment transactions (ipwizard) if (! t.m_strMemo.isEmpty() && data.invtransactiontype_valid == false) t.m_strPayee = t.m_strMemo; } else { if (t.m_strMemo.isEmpty()) t.m_strMemo = t.m_strPayee; } if (data.security_data_valid) { struct OfxSecurityData* secdata = data.security_data_ptr; if (secdata->ticker_valid) { t.m_strSymbol = QString::fromUtf8(secdata->ticker); } if (secdata->secname_valid) { t.m_strSecurity = QString::fromUtf8(secdata->secname); } } t.m_shares = MyMoneyMoney(); if (data.units_valid) { t.m_shares = MyMoneyMoney(data.units, 100000).reduce(); } t.m_price = MyMoneyMoney(); if (data.unitprice_valid) { t.m_price = MyMoneyMoney(data.unitprice, 100000).reduce(); } t.m_fees = MyMoneyMoney(); if (data.fees_valid) { t.m_fees += MyMoneyMoney(data.fees, 1000).reduce(); } if (data.commission_valid) { t.m_fees += MyMoneyMoney(data.commission, 1000).reduce(); } bool unhandledtype = false; QString type; if (data.invtransactiontype_valid) { switch (data.invtransactiontype) { case OFX_BUYDEBT: case OFX_BUYMF: case OFX_BUYOPT: case OFX_BUYOTHER: case OFX_BUYSTOCK: t.m_eAction = eMyMoney::Transaction::Action::Buy; break; case OFX_REINVEST: t.m_eAction = eMyMoney::Transaction::Action::ReinvestDividend; break; case OFX_SELLDEBT: case OFX_SELLMF: case OFX_SELLOPT: case OFX_SELLOTHER: case OFX_SELLSTOCK: t.m_eAction = eMyMoney::Transaction::Action::Sell; break; case OFX_INCOME: t.m_eAction = eMyMoney::Transaction::Action::CashDividend; // NOTE: With CashDividend, the amount of the dividend should // be in data.amount. Since I've never seen an OFX file with // cash dividends, this is an assumption on my part. (acejones) break; // // These types are all not handled. We will generate a warning for them. // case OFX_CLOSUREOPT: unhandledtype = true; type = QStringLiteral("CLOSUREOPT (Close a position for an option)"); break; case OFX_INVEXPENSE: unhandledtype = true; type = QStringLiteral("INVEXPENSE (Misc investment expense that is associated with a specific security)"); break; case OFX_JRNLFUND: unhandledtype = true; type = QStringLiteral("JRNLFUND (Journaling cash holdings between subaccounts within the same investment account)"); break; case OFX_MARGININTEREST: unhandledtype = true; type = QStringLiteral("MARGININTEREST (Margin interest expense)"); break; case OFX_RETOFCAP: unhandledtype = true; type = QStringLiteral("RETOFCAP (Return of capital)"); break; case OFX_SPLIT: unhandledtype = true; type = QStringLiteral("SPLIT (Stock or mutial fund split)"); break; case OFX_TRANSFER: unhandledtype = true; type = QStringLiteral("TRANSFER (Transfer holdings in and out of the investment account)"); break; default: unhandledtype = true; type = QString("UNKNOWN %1").arg(data.invtransactiontype); break; } } else t.m_eAction = eMyMoney::Transaction::Action::None; // In the case of investment transactions, the 'total' is supposed to the total amount // of the transaction. units * unitprice +/- commission. Easy, right? Sadly, it seems // some ofx creators do not follow this in all circumstances. Therefore, we have to double- // check the total here and adjust it if it's wrong. #if 0 // Even more sadly, this logic is BROKEN. It consistently results in bogus total // values, because of rounding errors in the price. A more through solution would // be to test if the commission alone is causing a discrepancy, and adjust in that case. if (data.invtransactiontype_valid && data.unitprice_valid) { double proper_total = t.m_dShares * data.unitprice + t.m_moneyFees; if (proper_total != t.m_moneyAmount) { pofx->addWarning(QString("Transaction %1 has an incorrect total of %2. Using calculated total of %3 instead.").arg(t.m_strBankID).arg(t.m_moneyAmount).arg(proper_total)); t.m_moneyAmount = proper_total; } } #endif if (unhandledtype) pofx->addWarning(QString("Transaction %1 has an unsupported type (%2).").arg(t.m_strBankID, type)); else s.m_listTransactions += t; // kDebug(2) << Q_FUNC_INFO << "return 0 "; return 0; } int OFXImporter::ofxStatementCallback(struct OfxStatementData data, void* pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); MyMoneyStatement& s = pofx->back(); pofx->setValid(); if (data.currency_valid) { s.m_strCurrency = QString::fromUtf8(data.currency); } if (data.account_id_valid) { s.m_strAccountNumber = QString::fromUtf8(data.account_id); } if (data.date_start_valid) { QDateTime dt; dt.setTime_t(data.date_start - pofx->d->m_timestampOffset * 60); s.m_dateBegin = dt.date(); } if (data.date_end_valid) { QDateTime dt; dt.setTime_t(data.date_end - pofx->d->m_timestampOffset * 60); s.m_dateEnd = dt.date(); } if (data.ledger_balance_valid && data.ledger_balance_date_valid) { s.m_closingBalance = MyMoneyMoney(data.ledger_balance); QDateTime dt; dt.setTime_t(data.ledger_balance_date); s.m_dateEnd = dt.date(); } // kDebug(2) << Q_FUNC_INFO << " return 0"; return 0; } int OFXImporter::ofxAccountCallback(struct OfxAccountData data, void * pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); pofx->addnew(); MyMoneyStatement& s = pofx->back(); // Having any account at all makes an ofx statement valid pofx->d->m_valid = true; if (data.account_id_valid) { s.m_strAccountName = QString::fromUtf8(data.account_name); s.m_strAccountNumber = QString::fromUtf8(data.account_id); } if (data.bank_id_valid) { s.m_strRoutingNumber = QString::fromUtf8(data.bank_id); } if (data.broker_id_valid) { s.m_strRoutingNumber = QString::fromUtf8(data.broker_id); } if (data.currency_valid) { s.m_strCurrency = QString::fromUtf8(data.currency); } if (data.account_type_valid) { switch (data.account_type) { case OfxAccountData::OFX_CHECKING : s.m_eType = eMyMoney::Statement::Type::Checkings; break; case OfxAccountData::OFX_SAVINGS : s.m_eType = eMyMoney::Statement::Type::Savings; break; case OfxAccountData::OFX_MONEYMRKT : s.m_eType = eMyMoney::Statement::Type::Investment; break; case OfxAccountData::OFX_CREDITLINE : s.m_eType = eMyMoney::Statement::Type::CreditCard; break; case OfxAccountData::OFX_CMA : s.m_eType = eMyMoney::Statement::Type::CreditCard; break; case OfxAccountData::OFX_CREDITCARD : s.m_eType = eMyMoney::Statement::Type::CreditCard; break; case OfxAccountData::OFX_INVESTMENT : s.m_eType = eMyMoney::Statement::Type::Investment; break; } } // ask KMyMoney for an account id s.m_accountId = pofx->account(QStringLiteral("kmmofx-acc-ref"), QString("%1-%2").arg(s.m_strRoutingNumber, s.m_strAccountNumber)).id(); // copy over the securities s.m_listSecurities = pofx->d->m_securitylist; // kDebug(2) << Q_FUNC_INFO << " return 0"; return 0; } int OFXImporter::ofxSecurityCallback(struct OfxSecurityData data, void* pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); MyMoneyStatement::Security sec; if (data.unique_id_valid) { sec.m_strId = QString::fromUtf8(data.unique_id); } if (data.secname_valid) { sec.m_strName = QString::fromUtf8(data.secname); } if (data.ticker_valid) { sec.m_strSymbol = QString::fromUtf8(data.ticker); } pofx->d->m_securitylist += sec; return 0; } int OFXImporter::ofxStatusCallback(struct OfxStatusData data, void * pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); QString message; // if we got this far, we know we were able to parse the file. // so if it fails after here it can only because there were no actual // accounts in the file! pofx->d->m_fatalerror = i18n("No accounts found."); if (data.ofx_element_name_valid) message.prepend(QString("%1: ").arg(QString::fromUtf8(data.ofx_element_name))); if (data.code_valid) message += QString("%1 (Code %2): %3").arg(QString::fromUtf8(data.name)).arg(data.code).arg(QString::fromUtf8(data.description)); if (data.server_message_valid) message += QString(" (%1)").arg(QString::fromUtf8(data.server_message)); if (data.severity_valid) { switch (data.severity) { case OfxStatusData::INFO: pofx->addInfo(message); break; case OfxStatusData::ERROR: pofx->addError(message); break; case OfxStatusData::WARN: pofx->addWarning(message); break; default: pofx->addWarning(message); pofx->addWarning(QStringLiteral("Previous message was an unknown type. 'WARNING' was assumed.")); break; } } // kDebug(2) << Q_FUNC_INFO << " return 0 "; return 0; } QStringList OFXImporter::importStatement(const MyMoneyStatement &s) { qDebug("OfxImporterPlugin::importStatement start"); return statementInterface()->import(s, false); } MyMoneyAccount OFXImporter::account(const QString& key, const QString& value) const { return statementInterface()->account(key, value); } void OFXImporter::protocols(QStringList& protocolList) const { protocolList.clear(); protocolList << QStringLiteral("OFX"); } QWidget* OFXImporter::accountConfigTab(const MyMoneyAccount& acc, QString& name) { name = i18n("Online settings"); d->m_statusDlg = new KOnlineBankingStatus(acc, 0); return d->m_statusDlg; } MyMoneyKeyValueContainer OFXImporter::onlineBankingSettings(const MyMoneyKeyValueContainer& current) { MyMoneyKeyValueContainer kvp(current); // keep the provider name in sync with the one found in kmm_ofximport.desktop kvp[QStringLiteral("provider")] = objectName().toLower(); if (d->m_statusDlg) { kvp.deletePair(QStringLiteral("appId")); kvp.deletePair(QStringLiteral("kmmofx-headerVersion")); kvp.deletePair(QStringLiteral("password")); d->m_wallet = openSynchronousWallet(); if (d->m_wallet && (d->m_wallet->hasFolder(KWallet::Wallet::PasswordFolder()) || d->m_wallet->createFolder(KWallet::Wallet::PasswordFolder())) && d->m_wallet->setFolder(KWallet::Wallet::PasswordFolder())) { QString key = OFX_PASSWORD_KEY(kvp.value(QStringLiteral("url")), kvp.value(QStringLiteral("uniqueId"))); if (d->m_statusDlg->m_storePassword->isChecked()) { d->m_wallet->writePassword(key, d->m_statusDlg->m_password->text()); } else { if (d->m_wallet->hasEntry(key)) { d->m_wallet->removeEntry(key); } } } else { if (d->m_statusDlg->m_storePassword->isChecked()) { kvp.setValue(QStringLiteral("password"), d->m_statusDlg->m_password->text()); } } if (!d->m_statusDlg->appId().isEmpty()) kvp.setValue(QStringLiteral("appId"), d->m_statusDlg->appId()); kvp.setValue(QStringLiteral("kmmofx-headerVersion"), d->m_statusDlg->headerVersion()); kvp.setValue(QStringLiteral("kmmofx-numRequestDays"), QString::number(d->m_statusDlg->m_numdaysSpin->value())); kvp.setValue(QStringLiteral("kmmofx-todayMinus"), QString::number(d->m_statusDlg->m_todayRB->isChecked())); kvp.setValue(QStringLiteral("kmmofx-lastUpdate"), QString::number(d->m_statusDlg->m_lastUpdateRB->isChecked())); kvp.setValue(QStringLiteral("kmmofx-pickDate"), QString::number(d->m_statusDlg->m_pickDateRB->isChecked())); kvp.setValue(QStringLiteral("kmmofx-specificDate"), d->m_statusDlg->m_specificDate->date().toString()); kvp.setValue(QStringLiteral("kmmofx-preferName"), QString::number(d->m_statusDlg->m_preferredPayee->currentIndex())); if (!d->m_statusDlg->m_clientUidEdit->text().isEmpty()) kvp.setValue(QStringLiteral("clientUid"), d->m_statusDlg->m_clientUidEdit->text()); else kvp.deletePair(QStringLiteral("clientUid")); if (d->m_statusDlg->m_timestampOffset->time().msecsSinceStartOfDay() == 0) { kvp.deletePair(QStringLiteral("kmmofx-timestampOffset")); } else { // get offset in minutes int offset = d->m_statusDlg->m_timestampOffset->time().msecsSinceStartOfDay() / 1000 / 60; if (d->m_statusDlg->m_timestampOffsetSign->currentText() == QStringLiteral("-")) { offset = -offset; } kvp.setValue(QStringLiteral("kmmofx-timestampOffset"), QString::number(offset)); } // get rid of pre 4.6 values kvp.deletePair(QStringLiteral("kmmofx-preferPayeeid")); } return kvp; } bool OFXImporter::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& settings) { Q_UNUSED(acc); bool rc = false; QPointer wiz = new KOnlineBankingSetupWizard(0); if (wiz->isInit()) { if (wiz->exec() == QDialog::Accepted) { rc = wiz->chosenSettings(settings); } } delete wiz; return rc; } bool OFXImporter::updateAccount(const MyMoneyAccount& acc, bool moreAccounts) { Q_UNUSED(moreAccounts); qDebug("OfxImporterPlugin::updateAccount"); try { if (!acc.id().isEmpty()) { // Save the value of preferName to be used by ofxTransactionCallback d->m_preferName = static_cast(acc.onlineBankingSettings().value(QStringLiteral("kmmofx-preferName")).toInt()); QPointer dlg = new KOfxDirectConnectDlg(acc); connect(dlg.data(), &KOfxDirectConnectDlg::statementReady, this, static_cast(&OFXImporter::slotImportFile)); // get the date of the earliest transaction that we are interested in // from the settings for this account MyMoneyKeyValueContainer settings = acc.onlineBankingSettings(); if (!settings.value(QStringLiteral("provider")).isEmpty()) { if ((settings.value(QStringLiteral("kmmofx-todayMinus")).toInt() != 0) && !settings.value(QStringLiteral("kmmofx-numRequestDays")).isEmpty()) { //kDebug(0) << "start date = today minus"; d->m_updateStartDate = QDate::currentDate().addDays(-settings.value(QStringLiteral("kmmofx-numRequestDays")).toInt()); } else if ((settings.value(QStringLiteral("kmmofx-lastUpdate")).toInt() != 0) && !acc.value(QStringLiteral("lastImportedTransactionDate")).isEmpty()) { //kDebug(0) << "start date = last update"; d->m_updateStartDate = QDate::fromString(acc.value(QStringLiteral("lastImportedTransactionDate")), Qt::ISODate); } else if ((settings.value(QStringLiteral("kmmofx-pickDate")).toInt() != 0) && !settings.value(QStringLiteral("kmmofx-specificDate")).isEmpty()) { //kDebug(0) << "start date = pick date"; d->m_updateStartDate = QDate::fromString(settings.value(QStringLiteral("kmmofx-specificDate"))); } else { //kDebug(0) << "start date = today - 2 months"; d->m_updateStartDate = QDate::currentDate().addMonths(-2); } } d->m_timestampOffset = settings.value("kmmofx-timestampOffset").toInt(); //kDebug(0) << "ofx plugin: account" << acc.name() << "earliest transaction date to process =" << qPrintable(d->m_updateStartDate.toString(Qt::ISODate)); if (dlg->init()) dlg->exec(); delete dlg; // reset the earliest-interesting-transaction date to the non-specific account setting d->m_updateStartDate = QDate(1900,1,1); d->m_timestampOffset = 0; } } catch (const MyMoneyException &e) { KMessageBox::information(0 , i18n("Error connecting to bank: %1", QString::fromLatin1(e.what()))); } return false; } void OFXImporter::slotImportFile(const QString& url) { qDebug("OfxImporterPlugin::slotImportFile"); if (!import(url)) { KMessageBox::error(0, QString("%1").arg(i18n("

Unable to import '%1' using the OFX importer plugin. The plugin returned the following error:

%2

", url, lastError())), i18n("Importing error")); } } bool OFXImporter::storeStatements(const QList &statements) { if (statements.isEmpty()) return true; auto ok = true; auto abort = false; // FIXME Deal with warnings/errors coming back from plugins /*if ( ofx.errors().count() ) { if ( KMessageBox::warningContinueCancelList(this,i18n("The following errors were returned from your bank"),ofx.errors(),i18n("OFX Errors")) == KMessageBox::Cancel ) abort = true; } if ( ofx.warnings().count() ) { if ( KMessageBox::warningContinueCancelList(this,i18n("The following warnings were returned from your bank"),ofx.warnings(),i18n("OFX Warnings"),KStandardGuiItem::cont(),"ofxwarnings") == KMessageBox::Cancel ) abort = true; }*/ qDebug("OfxImporterPlugin::storeStatements() with %d statements called", statements.count()); for (const auto& statement : statements) { if (abort) break; if (importStatement(statement).isEmpty()) ok = false; } if (!ok) KMessageBox::error(nullptr, i18n("Importing process terminated unexpectedly."), i18n("Failed to import all statements.")); return ok; } void OFXImporter::addnew() { d->m_statementlist.push_back(MyMoneyStatement()); } MyMoneyStatement& OFXImporter::back() { return d->m_statementlist.back(); } bool OFXImporter::isValid() const { return d->m_valid; } void OFXImporter::setValid() { d->m_valid = true; } void OFXImporter::addInfo(const QString& _msg) { d->m_infos += _msg; } void OFXImporter::addWarning(const QString& _msg) { d->m_warnings += _msg; } void OFXImporter::addError(const QString& _msg) { d->m_errors += _msg; } const QStringList& OFXImporter::infos() const // krazy:exclude=spelling { return d->m_infos; } const QStringList& OFXImporter::warnings() const { return d->m_warnings; } const QStringList& OFXImporter::errors() const { return d->m_errors; } K_PLUGIN_FACTORY_WITH_JSON(OFXImporterFactory, "ofximporter.json", registerPlugin();) #include "ofximporter.moc" diff --git a/kmymoney/plugins/qif/config/mymoneyqifprofile.cpp b/kmymoney/plugins/qif/config/mymoneyqifprofile.cpp index f180ea770..c3b282f00 100644 --- a/kmymoney/plugins/qif/config/mymoneyqifprofile.cpp +++ b/kmymoney/plugins/qif/config/mymoneyqifprofile.cpp @@ -1,1003 +1,1003 @@ /*************************************************************************** mymoneyqifprofile.cpp - description ------------------- begin : Tue Dec 24 2002 copyright : (C) 2002 by Thomas Baumgart email : thb@net-bembel.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "mymoneyqifprofile.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyexception.h" #include "mymoneymoney.h" #include "mymoneyenums.h" /* * CENTURY_BREAK is used to identify the century for a two digit year * * if yr is < CENTURY_BREAK it is in 2000 * if yr is >= CENTURY_BREAK it is in 1900 * * so with CENTURY_BREAK being 70 the following will happen: * * 00..69 -> 2000..2069 * 70..99 -> 1970..1999 */ const int CENTURY_BREAK = 70; class MyMoneyQifProfile::Private { public: Private() : m_changeCount(3, 0), m_lastValue(3, 0), m_largestValue(3, 0) { } void getThirdPosition(); void dissectDate(QVector& parts, const QString& txt) const; QVector m_changeCount; QVector m_lastValue; QVector m_largestValue; QMap m_partPos; }; void MyMoneyQifProfile::Private::dissectDate(QVector& parts, const QString& txt) const { QRegExp nonDelimChars("[ 0-9a-zA-Z]"); int part = 0; // the current part we scan int pos; // the current scan position int maxPartSize = txt.length() > 6 ? 4 : 2; // the maximum size of a part // some fu... up MS-Money versions write two delimiter in a row // so we need to keep track of them. Example: D14/12/'08 bool lastWasDelim = false; // separate the parts of the date and keep the locations of the delimiters for (pos = 0; pos < txt.length() && part < 3; ++pos) { if (nonDelimChars.indexIn(txt[pos]) == -1) { if (!lastWasDelim) { ++part; maxPartSize = 0; // make sure to pick the right one depending if next char is numeric or not lastWasDelim = true; } } else { lastWasDelim = false; // check if the part is over and we did not see a delimiter if ((maxPartSize != 0) && (parts[part].length() == maxPartSize)) { ++part; maxPartSize = 0; } if (maxPartSize == 0) { maxPartSize = txt[pos].isDigit() ? 2 : 3; if (part == 2) maxPartSize = 4; } if (part < 3) parts[part] += txt[pos]; } } if (part == 3) { // invalid date for (int i = 0; i < 3; ++i) { parts[i] = '0'; } } } void MyMoneyQifProfile::Private::getThirdPosition() { // if we have detected two parts we can calculate the third and its position if (m_partPos.count() == 2) { QList partsPresent = m_partPos.keys(); QStringList partsAvail = QString("d,m,y").split(','); int missingIndex = -1; int value = 0; for (int i = 0; i < 3; ++i) { if (!partsPresent.contains(partsAvail[i][0])) { missingIndex = i; } else { value += m_partPos[partsAvail[i][0]]; } } m_partPos[partsAvail[missingIndex][0]] = 3 - value; } } MyMoneyQifProfile::MyMoneyQifProfile() : d(new Private), m_isDirty(false) { clear(); } MyMoneyQifProfile::MyMoneyQifProfile(const QString& name) : d(new Private), m_isDirty(false) { loadProfile(name); } MyMoneyQifProfile::~MyMoneyQifProfile() { delete d; } void MyMoneyQifProfile::clear() { m_dateFormat = "%d.%m.%yyyy"; m_apostropheFormat = "2000-2099"; m_valueMode = ""; m_filterScriptImport = ""; m_filterScriptExport = ""; - m_filterFileType = "*.qif"; + m_filterFileType = "*.qif *.QIF"; m_decimal.clear(); m_decimal['$'] = m_decimal['Q'] = m_decimal['T'] = m_decimal['O'] = m_decimal['I'] = QLocale().decimalPoint(); m_thousands.clear(); m_thousands['$'] = m_thousands['Q'] = m_thousands['T'] = m_thousands['O'] = m_thousands['I'] = QLocale().groupSeparator(); m_openingBalanceText = "Opening Balance"; m_voidMark = "VOID "; m_accountDelimiter = '['; m_profileName = ""; m_profileDescription = ""; m_profileType = "Bank"; m_attemptMatchDuplicates = true; } void MyMoneyQifProfile::loadProfile(const QString& name) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group(name); clear(); m_profileName = name; m_profileDescription = grp.readEntry("Description", m_profileDescription); m_profileType = grp.readEntry("Type", m_profileType); m_dateFormat = grp.readEntry("DateFormat", m_dateFormat); m_apostropheFormat = grp.readEntry("ApostropheFormat", m_apostropheFormat); m_accountDelimiter = grp.readEntry("AccountDelimiter", m_accountDelimiter); m_openingBalanceText = grp.readEntry("OpeningBalance", m_openingBalanceText); m_voidMark = grp.readEntry("VoidMark", m_voidMark); m_filterScriptImport = grp.readEntry("FilterScriptImport", m_filterScriptImport); m_filterScriptExport = grp.readEntry("FilterScriptExport", m_filterScriptExport); m_filterFileType = grp.readEntry("FilterFileType", m_filterFileType); m_attemptMatchDuplicates = grp.readEntry("AttemptMatchDuplicates", m_attemptMatchDuplicates); // make sure, we remove any old stuff for now grp.deleteEntry("FilterScript"); QString tmp = QString(m_decimal['Q']) + m_decimal['T'] + m_decimal['I'] + m_decimal['$'] + m_decimal['O']; tmp = grp.readEntry("Decimal", tmp); m_decimal['Q'] = tmp[0]; m_decimal['T'] = tmp[1]; m_decimal['I'] = tmp[2]; m_decimal['$'] = tmp[3]; m_decimal['O'] = tmp[4]; tmp = QString(m_thousands['Q']) + m_thousands['T'] + m_thousands['I'] + m_thousands['$'] + m_thousands['O']; tmp = grp.readEntry("Thousand", tmp); m_thousands['Q'] = tmp[0]; m_thousands['T'] = tmp[1]; m_thousands['I'] = tmp[2]; m_thousands['$'] = tmp[3]; m_thousands['O'] = tmp[4]; m_isDirty = false; } void MyMoneyQifProfile::saveProfile() { if (m_isDirty == true) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group(m_profileName); grp.writeEntry("Description", m_profileDescription); grp.writeEntry("Type", m_profileType); grp.writeEntry("DateFormat", m_dateFormat); grp.writeEntry("ApostropheFormat", m_apostropheFormat); grp.writeEntry("AccountDelimiter", m_accountDelimiter); grp.writeEntry("OpeningBalance", m_openingBalanceText); grp.writeEntry("VoidMark", m_voidMark); grp.writeEntry("FilterScriptImport", m_filterScriptImport); grp.writeEntry("FilterScriptExport", m_filterScriptExport); grp.writeEntry("FilterFileType", m_filterFileType); grp.writeEntry("AttemptMatchDuplicates", m_attemptMatchDuplicates); QString tmp; tmp = QString(m_decimal['Q']) + m_decimal['T'] + m_decimal['I'] + m_decimal['$'] + m_decimal['O']; grp.writeEntry("Decimal", tmp); tmp = QString(m_thousands['Q']) + m_thousands['T'] + m_thousands['I'] + m_thousands['$'] + m_thousands['O']; grp.writeEntry("Thousand", tmp); } m_isDirty = false; } void MyMoneyQifProfile::setProfileName(const QString& name) { if (m_profileName != name) m_isDirty = true; m_profileName = name; } void MyMoneyQifProfile::setProfileDescription(const QString& desc) { if (m_profileDescription != desc) m_isDirty = true; m_profileDescription = desc; } void MyMoneyQifProfile::setProfileType(const QString& type) { if (m_profileType != type) m_isDirty = true; m_profileType = type; } void MyMoneyQifProfile::setOutputDateFormat(const QString& dateFormat) { if (m_dateFormat != dateFormat) m_isDirty = true; m_dateFormat = dateFormat; } void MyMoneyQifProfile::setInputDateFormat(const QString& dateFormat) { int j = -1; if (dateFormat.length() > 0) { for (int i = 0; i < dateFormat.length() - 1; ++i) { if (dateFormat[i] == '%') { d->m_partPos[dateFormat[++i]] = ++j; } } } } void MyMoneyQifProfile::setApostropheFormat(const QString& apostropheFormat) { if (m_apostropheFormat != apostropheFormat) m_isDirty = true; m_apostropheFormat = apostropheFormat; } void MyMoneyQifProfile::setAmountDecimal(const QChar& def, const QChar& chr) { QChar ch(chr); if (ch == QChar()) ch = ' '; if (m_decimal[def] != ch) m_isDirty = true; m_decimal[def] = ch; } void MyMoneyQifProfile::setAmountThousands(const QChar& def, const QChar& chr) { QChar ch(chr); if (ch == QChar()) ch = ' '; if (m_thousands[def] != ch) m_isDirty = true; m_thousands[def] = ch; } const QChar MyMoneyQifProfile::amountDecimal(const QChar& def) const { QChar chr = m_decimal[def]; return chr; } const QChar MyMoneyQifProfile::amountThousands(const QChar& def) const { QChar chr = m_thousands[def]; return chr; } void MyMoneyQifProfile::setAccountDelimiter(const QString& delim) { QString txt(delim); if (txt.isEmpty()) txt = ' '; else if (txt[0] != '[') txt = '['; if (m_accountDelimiter[0] != txt[0]) m_isDirty = true; m_accountDelimiter = txt[0]; } void MyMoneyQifProfile::setOpeningBalanceText(const QString& txt) { if (m_openingBalanceText != txt) m_isDirty = true; m_openingBalanceText = txt; } void MyMoneyQifProfile::setVoidMark(const QString& txt) { if (m_voidMark != txt) m_isDirty = true; m_voidMark = txt; } const QString MyMoneyQifProfile::accountDelimiter() const { QString rc; if (m_accountDelimiter[0] == ' ') rc = ' '; else rc = "[]"; return rc; } const QString MyMoneyQifProfile::date(const QDate& datein) const { QString::const_iterator format = m_dateFormat.begin();; QString buffer; QChar delim; int maskLen; QChar maskChar; while (format != m_dateFormat.end()) { if (*format == '%') { maskLen = 0; maskChar = *(++format); while ((format != m_dateFormat.end()) && (*format == maskChar)) { ++maskLen; ++format; } if (maskChar == 'd') { if (! delim.isNull()) buffer += delim; buffer += QString::number(datein.day()).rightJustified(2, '0'); } else if (maskChar == 'm') { if (! delim.isNull()) buffer += delim; if (maskLen == 3) buffer += QLocale().monthName(datein.month(), QLocale::ShortFormat); else buffer += QString::number(datein.month()).rightJustified(2, '0'); } else if (maskChar == 'y') { if (maskLen == 2) { buffer += twoDigitYear(delim, datein.year()); } else { if (! delim.isNull()) buffer += delim; buffer += QString::number(datein.year()); } break; } else { throw MYMONEYEXCEPTION_CSTRING("Invalid char in QifProfile date field"); } delim = 0; } else { if (! delim.isNull()) buffer += delim; delim = *format++; } } return buffer; } const QDate MyMoneyQifProfile::date(const QString& datein) const { // in case we don't know the format, we return an invalid date if (d->m_partPos.count() != 3) return QDate(); QVector scannedParts(3); d->dissectDate(scannedParts, datein); int yr, mon, day; bool ok; yr = scannedParts[d->m_partPos['y']].toInt(); mon = scannedParts[d->m_partPos['m']].toInt(&ok); if (!ok) { QStringList monthNames = QString("jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec").split(','); int j; for (j = 1; j <= 12; ++j) { if ((QLocale().monthName(j, QLocale::ShortFormat).toLower() == scannedParts[d->m_partPos['m']].toLower()) || (monthNames[j-1] == scannedParts[d->m_partPos['m']].toLower())) { mon = j; break; } } if (j == 13) { qWarning("Unknown month '%s'", qPrintable(scannedParts[d->m_partPos['m']])); return QDate(); } } day = scannedParts[d->m_partPos['d']].toInt(); if (yr < 100) { // two digit year information? if (yr < CENTURY_BREAK) // less than the CENTURY_BREAK we assume this century yr += 2000; else yr += 1900; } return QDate(yr, mon, day); #if 0 QString scannedDelim[2]; QString formatParts[3]; QString formatDelim[2]; int part; int delim; unsigned int i, j; part = -1; delim = 0; for (i = 0; i < m_dateFormat.length(); ++i) { if (m_dateFormat[i] == '%') { ++part; if (part == 3) { qWarning("MyMoneyQifProfile::date(const QString& datein) Too many parts in date format"); return QDate(); } ++i; } switch (m_dateFormat[i].toLatin1()) { case 'm': case 'd': case 'y': formatParts[part] += m_dateFormat[i]; break; case '/': case '-': case '.': case '\'': if (delim == 2) { qWarning("MyMoneyQifProfile::date(const QString& datein) Too many delimiters in date format"); return QDate(); } formatDelim[delim] = m_dateFormat[i]; ++delim; break; default: qWarning("MyMoneyQifProfile::date(const QString& datein) Invalid char in date format"); return QDate(); } } part = 0; delim = 0; bool prevWasChar = false; for (i = 0; i < datein.length(); ++i) { switch (datein[i].toLatin1()) { case '/': case '.': case '-': case '\'': if (delim == 2) { qWarning("MyMoneyQifProfile::date(const QString& datein) Too many delimiters in date field"); return QDate(); } scannedDelim[delim] = datein[i]; ++delim; ++part; prevWasChar = false; break; default: if (prevWasChar && datein[i].isDigit()) { ++part; prevWasChar = false; } if (datein[i].isLetter()) prevWasChar = true; // replace blank with 0 scannedParts[part] += (datein[i] == ' ') ? QChar('0') : datein[i]; break; } } int day = 1, mon = 1, yr = 1900; bool ok = false; for (i = 0; i < 2; ++i) { if (scannedDelim[i] != formatDelim[i] && scannedDelim[i] != QChar('\'')) { qWarning("MyMoneyQifProfile::date(const QString& datein) Invalid delimiter '%s' when '%s' was expected", scannedDelim[i].toLatin1(), formatDelim[i].toLatin1()); return QDate(); } } QString msg; for (i = 0; i < 3; ++i) { switch (formatParts[i][0].toLatin1()) { case 'd': day = scannedParts[i].toUInt(&ok); if (!ok) msg = "Invalid numeric character in day string"; break; case 'm': if (formatParts[i].length() != 3) { mon = scannedParts[i].toUInt(&ok); if (!ok) msg = "Invalid numeric character in month string"; } else { for (j = 1; j <= 12; ++j) { if (QLocale().monthName(j, 2000, true).toLower() == formatParts[i].toLower()) { mon = j; ok = true; break; } } if (j == 13) { msg = "Unknown month '" + scannedParts[i] + "'"; } } break; case 'y': ok = false; if (scannedParts[i].length() == formatParts[i].length()) { yr = scannedParts[i].toUInt(&ok); if (!ok) msg = "Invalid numeric character in month string"; if (yr < 100) { // two digit year info if (i > 1) { ok = true; if (scannedDelim[i-1] == QChar('\'')) { if (m_apostropheFormat == "1900-1949") { if (yr < 50) yr += 1900; else yr += 2000; } else if (m_apostropheFormat == "1900-1999") { yr += 1900; } else if (m_apostropheFormat == "2000-2099") { yr += 2000; } else { msg = "Unsupported apostropheFormat!"; ok = false; } } else { if (m_apostropheFormat == "1900-1949") { if (yr < 50) yr += 2000; else yr += 1900; } else if (m_apostropheFormat == "1900-1999") { yr += 2000; } else if (m_apostropheFormat == "2000-2099") { yr += 1900; } else { msg = "Unsupported apostropheFormat!"; ok = false; } } } else { msg = "Year as first parameter is not supported!"; } } else if (yr < 1900) { msg = "Year not in range < 100 or >= 1900!"; } else { ok = true; } } else { msg = QString("Length of year (%1) does not match expected length (%2).") .arg(scannedParts[i].length()).arg(formatParts[i].length()); } break; } if (!msg.isEmpty()) { qWarning("MyMoneyQifProfile::date(const QString& datein) %s", msg.toLatin1()); return QDate(); } } return QDate(yr, mon, day); #endif } const QString MyMoneyQifProfile::twoDigitYear(const QChar& delim, int yr) const { QChar realDelim = delim; QString buffer; if (!delim.isNull()) { if ((m_apostropheFormat == "1900-1949" && yr <= 1949) || (m_apostropheFormat == "1900-1999" && yr <= 1999) || (m_apostropheFormat == "2000-2099" && yr >= 2000)) realDelim = '\''; buffer += realDelim; } yr -= 1900; if (yr > 100) yr -= 100; if (yr < 10) buffer += '0'; buffer += QString::number(yr); return buffer; } const QString MyMoneyQifProfile::value(const QChar& def, const MyMoneyMoney& valuein) const { QChar _decimalSeparator; QChar _thousandsSeparator; QString res; _decimalSeparator = MyMoneyMoney::decimalSeparator(); _thousandsSeparator = MyMoneyMoney::thousandSeparator(); eMyMoney::Money::signPosition _signPosition = MyMoneyMoney::negativeMonetarySignPosition(); MyMoneyMoney::setDecimalSeparator(amountDecimal(def).toLatin1()); MyMoneyMoney::setThousandSeparator(amountThousands(def).toLatin1()); MyMoneyMoney::setNegativeMonetarySignPosition(eMyMoney::Money::BeforeQuantityMoney); res = valuein.formatMoney("", 2); MyMoneyMoney::setDecimalSeparator(_decimalSeparator); MyMoneyMoney::setThousandSeparator(_thousandsSeparator); MyMoneyMoney::setNegativeMonetarySignPosition(_signPosition); return res; } const MyMoneyMoney MyMoneyQifProfile::value(const QChar& def, const QString& valuein) const { QChar _decimalSeparator; QChar _thousandsSeparator; MyMoneyMoney res; _decimalSeparator = MyMoneyMoney::decimalSeparator(); _thousandsSeparator = MyMoneyMoney::thousandSeparator(); eMyMoney::Money::signPosition _signPosition = MyMoneyMoney::negativeMonetarySignPosition(); MyMoneyMoney::setDecimalSeparator(amountDecimal(def).toLatin1()); MyMoneyMoney::setThousandSeparator(amountThousands(def).toLatin1()); MyMoneyMoney::setNegativeMonetarySignPosition(eMyMoney::Money::BeforeQuantityMoney); res = MyMoneyMoney(valuein); MyMoneyMoney::setDecimalSeparator(_decimalSeparator); MyMoneyMoney::setThousandSeparator(_thousandsSeparator); MyMoneyMoney::setNegativeMonetarySignPosition(_signPosition); return res; } void MyMoneyQifProfile::setFilterScriptImport(const QString& script) { if (m_filterScriptImport != script) m_isDirty = true; m_filterScriptImport = script; } void MyMoneyQifProfile::setFilterScriptExport(const QString& script) { if (m_filterScriptExport != script) m_isDirty = true; m_filterScriptExport = script; } void MyMoneyQifProfile::setFilterFileType(const QString& txt) { if (m_filterFileType != txt) m_isDirty = true; m_filterFileType = txt; } void MyMoneyQifProfile::setAttemptMatchDuplicates(bool f) { if (m_attemptMatchDuplicates != f) m_isDirty = true; m_attemptMatchDuplicates = f; } const QString MyMoneyQifProfile::inputDateFormat() const { QStringList list; possibleDateFormats(list); if (list.count() == 1) return list.first(); return QString(); } void MyMoneyQifProfile::possibleDateFormats(QStringList& list) const { QStringList defaultList = QString("y,m,d:y,d,m:m,d,y:m,y,d:d,m,y:d,y,m").split(':'); list.clear(); QStringList::const_iterator it_d; for (it_d = defaultList.constBegin(); it_d != defaultList.constEnd(); ++it_d) { const QStringList parts = (*it_d).split(',', QString::SkipEmptyParts); int i; for (i = 0; i < 3; ++i) { if (d->m_partPos.contains(parts[i][0])) { if (d->m_partPos[parts[i][0]] != i) break; } // months can't be larger than 12 if (parts[i] == "m" && d->m_largestValue[i] > 12) break; // days can't be larger than 31 if (parts[i] == "d" && d->m_largestValue[i] > 31) break; } // matches all tests if (i == 3) { QString format = *it_d; format.replace('y', "%y"); format.replace('m', "%m"); format.replace('d', "%d"); format.replace(',', " "); list << format; } } // if we haven't found any, then there's something wrong. // in this case, we present the full list and let the user decide if (list.count() == 0) { for (it_d = defaultList.constBegin(); it_d != defaultList.constEnd(); ++it_d) { QString format = *it_d; format.replace('y', "%y"); format.replace('m', "%m"); format.replace('d', "%d"); format.replace(',', " "); list << format; } } } void MyMoneyQifProfile::autoDetect(const QStringList& lines) { m_dateFormat.clear(); m_decimal.clear(); m_thousands.clear(); QString numericRecords = "BT$OIQ"; QStringList::const_iterator it; int datesScanned = 0; // section: used to switch between different QIF sections, // because the Record identifiers are ambiguous between sections // eg. in transaction records, T identifies a total amount, in // account sections it's the type. // // 0 - unknown // 1 - account // 2 - transactions // 3 - prices int section = 0; QRegExp price("\"(.*)\",(.*),\"(.*)\""); for (it = lines.begin(); it != lines.end(); ++it) { QChar c((*it)[0]); if (c == '!') { QString sname = (*it).toLower(); if (!sname.startsWith(QLatin1String("!option:"))) { section = 0; if (sname.startsWith(QLatin1String("!account"))) section = 1; else if (sname.startsWith(QLatin1String("!type"))) { if (sname.startsWith(QLatin1String("!type:cat")) || sname.startsWith(QLatin1String("!type:payee")) || sname.startsWith(QLatin1String("!type:security")) || sname.startsWith(QLatin1String("!type:class"))) { section = 0; } else if (sname.startsWith(QLatin1String("!type:price"))) { section = 3; } else section = 2; } } } switch (section) { case 1: if (c == 'B') { scanNumeric((*it).mid(1), m_decimal[c], m_thousands[c]); } break; case 2: if (numericRecords.contains(c)) { scanNumeric((*it).mid(1), m_decimal[c], m_thousands[c]); } else if ((c == 'D') && (m_dateFormat.isEmpty())) { if (d->m_partPos.count() != 3) { scanDate((*it).mid(1)); ++datesScanned; if (d->m_partPos.count() == 2) { // if we have detected two parts we can calculate the third and its position d->getThirdPosition(); } } } break; case 3: if (price.indexIn(*it) != -1) { scanNumeric(price.cap(2), m_decimal['P'], m_thousands['P']); scanDate(price.cap(3)); ++datesScanned; } break; } } // the following algorithm is only applied if we have more // than 20 dates found. Smaller numbers have shown that the // results are inaccurate which leads to a reduced number of // date formats presented to choose from. if (d->m_partPos.count() != 3 && datesScanned > 20) { QMap sortedPos; // make sure to reset the known parts for the following algorithm if (d->m_partPos.contains('y')) { d->m_changeCount[d->m_partPos['y']] = -1; for (int i = 0; i < 3; ++i) { if (d->m_partPos['y'] == i) continue; // can we say for sure that we hit the day field? if (d->m_largestValue[i] > 12) { d->m_partPos['d'] = i; } } } if (d->m_partPos.contains('d')) d->m_changeCount[d->m_partPos['d']] = -1; if (d->m_partPos.contains('m')) d->m_changeCount[d->m_partPos['m']] = -1; for (int i = 0; i < 3; ++i) { if (d->m_changeCount[i] != -1) { sortedPos[d->m_changeCount[i]] = i; } } QMap::const_iterator it_a; QMap::const_iterator it_b; switch (sortedPos.count()) { case 1: // all the same // let the user decide, we can't figure it out break; case 2: // two are the same, we treat the largest as the day // if it's 20% larger than the other one and let the // user pick the other two { it_b = sortedPos.constBegin(); it_a = it_b; ++it_b; double a = d->m_changeCount[*it_a]; double b = d->m_changeCount[*it_b]; if (b > (a * 1.2)) { d->m_partPos['d'] = *it_b; } } break; case 3: // three different, we check if they are 20% apart each it_b = sortedPos.constBegin(); for (int i = 0; i < 2; ++i) { it_a = it_b; ++it_b; double a = d->m_changeCount[*it_a]; double b = d->m_changeCount[*it_b]; if (b > (a * 1.2)) { switch (i) { case 0: d->m_partPos['y'] = *it_a; break; case 1: d->m_partPos['d'] = *it_b; break; } } } break; } // extract the last if necessary and possible date position d->getThirdPosition(); } } void MyMoneyQifProfile::scanNumeric(const QString& txt, QChar& decimal, QChar& thousands) const { QChar first, second; QRegExp numericChars("[0-9-()]"); for (int i = 0; i < txt.length(); ++i) { const QChar& c = txt[i]; if (numericChars.indexIn(c) == -1) { if (c == '.' || c == ',') { first = second; second = c; } } } if (!second.isNull()) decimal = second; if (!first.isNull()) thousands = first; } void MyMoneyQifProfile::scanDate(const QString& txt) const { // extract the parts from the txt QVector parts(3); // the various parts of the date d->dissectDate(parts, txt); // now analyze the parts for (int i = 0; i < 3; ++i) { bool ok; int value = parts[i].toInt(&ok); if (!ok) { // this should happen only if the part is non-numeric -> month d->m_partPos['m'] = i; } else if (value != 0) { if (value != d->m_lastValue[i]) { d->m_changeCount[i]++; d->m_lastValue[i] = value; if (value > d->m_largestValue[i]) d->m_largestValue[i] = value; } // if it's > 31 it can only be years if (value > 31) { d->m_partPos['y'] = i; } // and if it's in between 12 and 32 and we already identified the // position for the year it must be days if ((value > 12) && (value < 32) && d->m_partPos.contains('y')) { d->m_partPos['d'] = i; } } } } diff --git a/kmymoney/plugins/qif/import/kimportdlg.cpp b/kmymoney/plugins/qif/import/kimportdlg.cpp index 5d45ee476..5e7429190 100644 --- a/kmymoney/plugins/qif/import/kimportdlg.cpp +++ b/kmymoney/plugins/qif/import/kimportdlg.cpp @@ -1,206 +1,204 @@ /*************************************************************************** kimportdlg.cpp - description ------------------- begin : Wed May 16 2001 copyright : (C) 2001 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "kimportdlg.h" // ---------------------------------------------------------------------------- // QT Headers #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Headers #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Headers #include "kmymoneyutils.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "../config/mymoneyqifprofile.h" #include using namespace Icons; KImportDlg::KImportDlg(QWidget *parent) : KImportDlgDecl(parent) { // Set all the last used options readConfig(); loadProfiles(true); // load button icons KGuiItem okButtenItem(i18n("&Import"), Icons::get(Icon::DocumentImport), i18n("Start operation"), i18n("Use this to start the import operation")); KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Ok), okButtenItem); KGuiItem browseButtenItem(i18n("&Browse..."), Icons::get(Icon::DocumentOpen), i18n("Select filename"), i18n("Use this to select a filename to export to")); KGuiItem::assign(m_qbuttonBrowse, browseButtenItem); KGuiItem newButtenItem(i18nc("New profile", "&New..."), Icons::get(Icon::DocumentNew), i18n("Create a new profile"), i18n("Use this to open the profile editor")); // connect the buttons to their functionality - connect(m_qbuttonBrowse, SIGNAL(clicked()), this, SLOT(slotBrowse())); - connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(slotOkClicked())); - connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(m_qbuttonBrowse, &QPushButton::clicked, this, &KImportDlg::slotBrowse); + connect(m_buttonBox, &QDialogButtonBox::accepted, this, &KImportDlg::slotOkClicked); + connect(m_buttonBox, &QDialogButtonBox::rejected, this, &KImportDlg::reject); // connect the change signals to the check slot and perform initial check - connect(m_qlineeditFile, SIGNAL(textChanged(QString)), this, - SLOT(slotFileTextChanged(QString))); + connect(m_qlineeditFile, &KLineEdit::textChanged, this, &KImportDlg::slotFileTextChanged); // setup button enable status slotFileTextChanged(m_qlineeditFile->text()); } KImportDlg::~KImportDlg() { } void KImportDlg::slotBrowse() { // determine what the browse prefix should be from the current profile MyMoneyQifProfile tmpprofile; tmpprofile.loadProfile("Profile-" + profile()); - - QUrl file = QFileDialog::getOpenFileUrl(this, i18n("Import File..."), QUrl("kfiledialog:///kmymoney-import"), - i18n("Import files (%1);;All files (%2)", tmpprofile.filterFileType(), "*") - ); + QUrl file = QFileDialog::getOpenFileUrl(this, + i18n("Import File..."), + QUrl::fromLocalFile(m_qlineeditFile->text()), + i18n("Import files (%1);;All files (%2)", tmpprofile.filterFileType(), "*")); if (!file.isEmpty()) { m_qlineeditFile->setText(file.toDisplayString(QUrl::PreferLocalFile)); } } void KImportDlg::slotOkClicked() { // Save the used options. writeConfig(); // leave dialog directly accept(); } void KImportDlg::readConfig() { KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup kgrp = kconfig->group("Last Use Settings"); m_qlineeditFile->setText(kgrp.readEntry("KImportDlg_LastFile")); - } void KImportDlg::writeConfig() { KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp = kconfig->group("Last Use Settings"); grp.writeEntry("KImportDlg_LastFile", m_qlineeditFile->text()); grp.writeEntry("KImportDlg_LastProfile", m_profileComboBox->currentText()); kconfig->sync(); } /** Make sure the text input is ok */ void KImportDlg::slotFileTextChanged(const QString& text) { bool fileExists = false; if (file().isValid()) { Q_CONSTEXPR short int detailLevel = 0; // it's a file or a directory or a symlink, or it doesn't exist KIO::StatJob* statjob = KIO::stat(file(), KIO::StatJob::SourceSide, detailLevel); bool noerror = statjob->exec(); if (noerror) { // We want a file fileExists = !statjob->statResult().isDir(); } } if (!text.isEmpty() && fileExists) { // m_qcomboboxDateFormat->setEnabled(true); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); m_qlineeditFile->setText(text); } else { // m_qcomboboxDateFormat->setEnabled(false); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } } void KImportDlg::loadProfiles(const bool selectLast) { QString current = m_profileComboBox->currentText(); m_profileComboBox->clear(); QStringList list; KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Profiles"); list = grp.readEntry("profiles", QStringList()); list.sort(); m_profileComboBox->addItems(list); if (selectLast == true) { config->group("Last Use Settings"); current = grp.readEntry("KImportDlg_LastProfile"); } int index = m_profileComboBox->findText(current, Qt::MatchExactly); if (index > -1) { m_profileComboBox->setCurrentIndex(index); } else { m_profileComboBox->setCurrentIndex(0); } } void KImportDlg::addCategories(QStringList& strList, const QString& id, const QString& leadIn) const { MyMoneyFile *file = MyMoneyFile::instance(); QString name; MyMoneyAccount account = file->account(id); QStringList accList = account.accountList(); QStringList::ConstIterator it_a; for (it_a = accList.constBegin(); it_a != accList.constEnd(); ++it_a) { account = file->account(*it_a); strList << leadIn + account.name(); addCategories(strList, *it_a, leadIn + account.name() + MyMoneyFile::AccountSeparator); } } diff --git a/kmymoney/plugins/qif/import/qifimporter.cpp b/kmymoney/plugins/qif/import/qifimporter.cpp index fb0557bde..e5646b261 100644 --- a/kmymoney/plugins/qif/import/qifimporter.cpp +++ b/kmymoney/plugins/qif/import/qifimporter.cpp @@ -1,129 +1,127 @@ /*************************************************************************** qifimporter.cpp ------------------- copyright : (C) 2017 by Łukasz Wojniłowicz email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include #include "qifimporter.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kimportdlg.h" #include "mymoneyqifreader.h" #include "statementinterface.h" #include "viewinterface.h" #ifdef IS_APPIMAGE #include #include #endif class MyMoneyStatement; QIFImporter::QIFImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "qifimporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args); const auto componentName = QLatin1String("qifimporter"); const auto rcFileName = QLatin1String("qifimporter.rc"); setComponentName(componentName, i18n("QIF importer")); #ifdef IS_APPIMAGE const QString rcFilePath = QCoreApplication::applicationDirPath() + QLatin1String("/../share/kxmlgui5/csvexporter/") + rcFileName; setXMLFile(rcFilePath); const QString localRcFilePath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).first() + QLatin1Char('/') + rcFileName; setLocalXMLFile(localRcFilePath); #else setXMLFile(rcFileName); #endif createActions(); // For information, announce that we have been loaded. qDebug("Plugins: qifimporter loaded"); } QIFImporter::~QIFImporter() { qDebug("Plugins: qifimporter unloaded"); } void QIFImporter::createActions() { const auto &kpartgui = QStringLiteral("file_import_qif"); m_action = actionCollection()->addAction(kpartgui); m_action->setText(i18n("QIF...")); connect(m_action, &QAction::triggered, this, &QIFImporter::slotQifImport); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void QIFImporter::slotQifImport() { - m_action->setEnabled(false); QPointer dlg = new KImportDlg(nullptr); if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { + m_action->setEnabled(false); m_qifReader = new MyMoneyQifReader; statementInterface()->resetMessages(); connect(m_qifReader, &MyMoneyQifReader::statementsReady, this, &QIFImporter::slotGetStatements); m_qifReader->setURL(dlg->file()); m_qifReader->setProfile(dlg->profile()); m_qifReader->setCategoryMapping(dlg->m_typeComboBox->currentIndex() == 0); - const auto statementCount = m_qifReader->statementCount(); - if (!m_qifReader->startImport()) + if (!m_qifReader->startImport()) { delete m_qifReader; - statementInterface()->showMessages(statementCount); + statementInterface()->showMessages(0); + m_action->setEnabled(true); + } } delete dlg; - m_action->setEnabled(true); } bool QIFImporter::slotGetStatements(const QList &statements) { auto ret = true; - QStringList importSummary; for (const auto& statement : statements) { const auto singleImportSummary = statementInterface()->import(statement); if (singleImportSummary.isEmpty()) ret = false; - - importSummary.append(singleImportSummary); } delete m_qifReader; - if (!importSummary.isEmpty()) - KMessageBox::informationList(nullptr, - i18n("The statement has been processed with the following results:"), importSummary, i18n("Statement stats")); + // inform the user about the result of the operation + statementInterface()->showMessages(statements.count()); + // allow further QIF imports + m_action->setEnabled(true); return ret; } K_PLUGIN_FACTORY_WITH_JSON(QIFImporterFactory, "qifimporter.json", registerPlugin();) #include "qifimporter.moc"