|
|
|
/***************************************************************************
|
|
|
|
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. *
|
|
|
|
* *
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// QT Includes
|
|
|
|
|
|
|
|
#include <tqregexp.h>
|
|
|
|
#include <tqvaluevector.h>
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// KDE Includes
|
|
|
|
|
|
|
|
#include <kglobal.h>
|
|
|
|
#include <kconfig.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kcalendarsystem.h>
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Project Includes
|
|
|
|
|
|
|
|
#include "mymoneyqifprofile.h"
|
|
|
|
#include "../mymoney/mymoneyexception.h"
|
|
|
|
#include "../mymoney/mymoneymoney.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* CENTURY_BREAK is used to identfy 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
|
|
|
|
*/
|
|
|
|
#define CENTURY_BREAK 70
|
|
|
|
|
|
|
|
class MyMoneyQifProfile::Private {
|
|
|
|
public:
|
|
|
|
Private() {
|
|
|
|
m_changeCount.resize(3, 0);
|
|
|
|
m_lastValue.resize(3, 0);
|
|
|
|
m_largestValue.resize(3, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void getThirdPosition(void);
|
|
|
|
void dissectDate(TQValueVector<TQString>& parts, const TQString& txt) const;
|
|
|
|
|
|
|
|
TQValueVector<int> m_changeCount;
|
|
|
|
TQValueVector<int> m_lastValue;
|
|
|
|
TQValueVector<int> m_largestValue;
|
|
|
|
TQMap<TQChar, int> m_partPos;
|
|
|
|
};
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::Private::dissectDate(TQValueVector<TQString>& parts, const TQString& txt) const
|
|
|
|
{
|
|
|
|
TQRegExp nonDelimChars("[ 0-9a-zA-Z]");
|
|
|
|
int part = 0; // the current part we scan
|
|
|
|
unsigned int pos; // the current scan position
|
|
|
|
unsigned 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.search(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(void)
|
|
|
|
{
|
|
|
|
// if we have detected two parts we can calculate the third and its position
|
|
|
|
if(m_partPos.count() == 2) {
|
|
|
|
TQValueList<TQChar> partsPresent = m_partPos.keys();
|
|
|
|
TQStringList partsAvail = TQStringList::split(",", "d,m,y");
|
|
|
|
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 TQString& name) :
|
|
|
|
d(new Private),
|
|
|
|
m_isDirty(false)
|
|
|
|
{
|
|
|
|
loadProfile(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
MyMoneyQifProfile::~MyMoneyQifProfile()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::clear(void)
|
|
|
|
{
|
|
|
|
m_dateFormat = "%d.%m.%yyyy";
|
|
|
|
m_apostropheFormat = "2000-2099";
|
|
|
|
m_valueMode = "";
|
|
|
|
m_filterScriptImport = "";
|
|
|
|
m_filterScriptExport = "";
|
|
|
|
m_filterFileType = "*.qif";
|
|
|
|
|
|
|
|
m_decimal.clear();
|
|
|
|
m_decimal['$'] =
|
|
|
|
m_decimal['Q'] =
|
|
|
|
m_decimal['T'] =
|
|
|
|
m_decimal['O'] =
|
|
|
|
m_decimal['I'] = KGlobal::locale()->monetaryDecimalSymbol()[0];
|
|
|
|
|
|
|
|
m_thousands.clear();
|
|
|
|
m_thousands['$'] =
|
|
|
|
m_thousands['Q'] =
|
|
|
|
m_thousands['T'] =
|
|
|
|
m_thousands['O'] =
|
|
|
|
m_thousands['I'] = KGlobal::locale()->monetaryThousandsSeparator()[0];
|
|
|
|
|
|
|
|
m_openingBalanceText = "Opening Balance";
|
|
|
|
m_voidMark = "VOID ";
|
|
|
|
m_accountDelimiter = "[";
|
|
|
|
|
|
|
|
m_profileName = "";
|
|
|
|
m_profileDescription = "";
|
|
|
|
m_profileType = "Bank";
|
|
|
|
|
|
|
|
m_attemptMatchDuplicates = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::loadProfile(const TQString& name)
|
|
|
|
{
|
|
|
|
KConfig* config = KGlobal::config();
|
|
|
|
config->setGroup(name);
|
|
|
|
|
|
|
|
clear();
|
|
|
|
|
|
|
|
m_profileName = name;
|
|
|
|
m_profileDescription = config->readEntry("Description", m_profileDescription);
|
|
|
|
m_profileType = config->readEntry("Type", m_profileType);
|
|
|
|
m_dateFormat = config->readEntry("DateFormat", m_dateFormat);
|
|
|
|
m_apostropheFormat = config->readEntry("ApostropheFormat", m_apostropheFormat);
|
|
|
|
m_accountDelimiter = config->readEntry("AccountDelimiter", m_accountDelimiter);
|
|
|
|
m_openingBalanceText = config->readEntry("OpeningBalance", m_openingBalanceText);
|
|
|
|
m_voidMark = config->readEntry("VoidMark", m_voidMark);
|
|
|
|
m_filterScriptImport = config->readEntry("FilterScriptImport", m_filterScriptImport);
|
|
|
|
m_filterScriptExport = config->readEntry("FilterScriptExport", m_filterScriptExport);
|
|
|
|
m_filterFileType = config->readEntry("FilterFileType",m_filterFileType);
|
|
|
|
|
|
|
|
m_attemptMatchDuplicates = config->readBoolEntry("AttemptMatchDuplicates", m_attemptMatchDuplicates);
|
|
|
|
|
|
|
|
// make sure, we remove any old stuff for now
|
|
|
|
config->deleteEntry("FilterScript");
|
|
|
|
|
|
|
|
TQString tmp = TQString(m_decimal['Q']) + m_decimal['T'] + m_decimal['I'] +
|
|
|
|
m_decimal['$'] + m_decimal['O'];
|
|
|
|
tmp = config->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 = TQString(m_thousands['Q']) + m_thousands['T'] + m_thousands['I'] +
|
|
|
|
m_thousands['$'] + m_thousands['O'];
|
|
|
|
tmp = config->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(void)
|
|
|
|
{
|
|
|
|
if(m_isDirty == true) {
|
|
|
|
KConfig* config = KGlobal::config();
|
|
|
|
config->setGroup(m_profileName);
|
|
|
|
|
|
|
|
config->writeEntry("Description", m_profileDescription);
|
|
|
|
config->writeEntry("Type", m_profileType);
|
|
|
|
config->writeEntry("DateFormat", m_dateFormat);
|
|
|
|
config->writeEntry("ApostropheFormat", m_apostropheFormat);
|
|
|
|
config->writeEntry("AccountDelimiter", m_accountDelimiter);
|
|
|
|
config->writeEntry("OpeningBalance", m_openingBalanceText);
|
|
|
|
config->writeEntry("VoidMark", m_voidMark);
|
|
|
|
config->writeEntry("FilterScriptImport", m_filterScriptImport);
|
|
|
|
config->writeEntry("FilterScriptExport", m_filterScriptExport);
|
|
|
|
config->writeEntry("FilterFileType", m_filterFileType);
|
|
|
|
config->writeEntry("AttemptMatchDuplicates", m_attemptMatchDuplicates);
|
|
|
|
|
|
|
|
TQString tmp;
|
|
|
|
|
|
|
|
tmp = TQString(m_decimal['Q']) + m_decimal['T'] + m_decimal['I'] +
|
|
|
|
m_decimal['$'] + m_decimal['O'];
|
|
|
|
config->writeEntry("Decimal", tmp);
|
|
|
|
tmp = TQString(m_thousands['Q']) + m_thousands['T'] + m_thousands['I'] +
|
|
|
|
m_thousands['$'] + m_thousands['O'];
|
|
|
|
config->writeEntry("Thousand", tmp);
|
|
|
|
}
|
|
|
|
m_isDirty = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setProfileName(const TQString& name)
|
|
|
|
{
|
|
|
|
if(m_profileName != name)
|
|
|
|
m_isDirty = true;
|
|
|
|
|
|
|
|
m_profileName = name;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setProfileDescription(const TQString& desc)
|
|
|
|
{
|
|
|
|
if(m_profileDescription != desc)
|
|
|
|
m_isDirty = true;
|
|
|
|
|
|
|
|
m_profileDescription = desc;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setProfileType(const TQString& type)
|
|
|
|
{
|
|
|
|
if(m_profileType != type)
|
|
|
|
m_isDirty = true;
|
|
|
|
m_profileType = type;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setOutputDateFormat(const TQString& dateFormat)
|
|
|
|
{
|
|
|
|
if(m_dateFormat != dateFormat)
|
|
|
|
m_isDirty = true;
|
|
|
|
|
|
|
|
m_dateFormat = dateFormat;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setInputDateFormat(const TQString& dateFormat)
|
|
|
|
{
|
|
|
|
int j = -1;
|
|
|
|
if(dateFormat.length() > 0) {
|
|
|
|
for(unsigned int i = 0; i < dateFormat.length()-1; ++i) {
|
|
|
|
if(dateFormat[i] == '%') {
|
|
|
|
d->m_partPos[dateFormat[++i]] = ++j;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setApostropheFormat(const TQString& apostropheFormat)
|
|
|
|
{
|
|
|
|
if(m_apostropheFormat != apostropheFormat)
|
|
|
|
m_isDirty = true;
|
|
|
|
|
|
|
|
m_apostropheFormat = apostropheFormat;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setAmountDecimal(const TQChar& def, const TQChar& chr)
|
|
|
|
{
|
|
|
|
TQChar ch(chr);
|
|
|
|
if(ch == TQChar())
|
|
|
|
ch = ' ';
|
|
|
|
|
|
|
|
if(m_decimal[def] != ch)
|
|
|
|
m_isDirty = true;
|
|
|
|
|
|
|
|
m_decimal[def] = ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setAmountThousands(const TQChar& def, const TQChar& chr)
|
|
|
|
{
|
|
|
|
TQChar ch(chr);
|
|
|
|
if(ch == TQChar())
|
|
|
|
ch = ' ';
|
|
|
|
|
|
|
|
if(m_thousands[def] != ch)
|
|
|
|
m_isDirty = true;
|
|
|
|
|
|
|
|
m_thousands[def] = ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQChar MyMoneyQifProfile::amountDecimal(const TQChar& def) const
|
|
|
|
{
|
|
|
|
TQChar chr = m_decimal[def];
|
|
|
|
return chr;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQChar MyMoneyQifProfile::amountThousands(const TQChar& def) const
|
|
|
|
{
|
|
|
|
TQChar chr = m_thousands[def];
|
|
|
|
return chr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setAccountDelimiter(const TQString& delim)
|
|
|
|
{
|
|
|
|
TQString 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 TQString& txt)
|
|
|
|
{
|
|
|
|
if(m_openingBalanceText != txt)
|
|
|
|
m_isDirty = true;
|
|
|
|
m_openingBalanceText = txt;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setVoidMark(const TQString& txt)
|
|
|
|
{
|
|
|
|
if(m_voidMark != txt)
|
|
|
|
m_isDirty = true;
|
|
|
|
m_voidMark = txt;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString MyMoneyQifProfile::accountDelimiter(void) const
|
|
|
|
{
|
|
|
|
TQString rc;
|
|
|
|
|
|
|
|
switch(m_accountDelimiter[0]) {
|
|
|
|
case ' ':
|
|
|
|
rc = " ";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rc = "[]";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString MyMoneyQifProfile::date(const TQDate& datein) const
|
|
|
|
{
|
|
|
|
const char* format = m_dateFormat.latin1();
|
|
|
|
TQString buffer;
|
|
|
|
TQChar delim;
|
|
|
|
int maskLen;
|
|
|
|
char maskChar;
|
|
|
|
|
|
|
|
while(*format) {
|
|
|
|
switch(*format) {
|
|
|
|
case '%':
|
|
|
|
maskLen = 0;
|
|
|
|
maskChar = *++format;
|
|
|
|
while(*format && *format == maskChar) {
|
|
|
|
++maskLen;
|
|
|
|
++format;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(maskChar) {
|
|
|
|
case 'd':
|
|
|
|
if(delim)
|
|
|
|
buffer += delim;
|
|
|
|
buffer += TQString::number(datein.day()).rightJustify(2, '0');
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'm':
|
|
|
|
if(delim)
|
|
|
|
buffer += delim;
|
|
|
|
if(maskLen == 3)
|
|
|
|
buffer += KGlobal::locale()->calendar()->monthName(datein.month(), datein.year(), true);
|
|
|
|
else
|
|
|
|
buffer += TQString::number(datein.month()).rightJustify(2, '0');
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'y':
|
|
|
|
if(maskLen == 2) {
|
|
|
|
buffer += twoDigitYear(delim, datein.year());
|
|
|
|
} else {
|
|
|
|
if(delim)
|
|
|
|
buffer += delim;
|
|
|
|
buffer += TQString::number(datein.year());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new MYMONEYEXCEPTION("Invalid char in QifProfile date field");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
delim = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
if(delim)
|
|
|
|
buffer += delim;
|
|
|
|
delim = *format++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
const TQDate MyMoneyQifProfile::date(const TQString& datein) const
|
|
|
|
{
|
|
|
|
// in case we don't know the format, we return an invalid date
|
|
|
|
if(d->m_partPos.count() != 3)
|
|
|
|
return TQDate();
|
|
|
|
|
|
|
|
TQValueVector<TQString> 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) {
|
|
|
|
TQStringList monthNames = TQStringList::split(",", "jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec");
|
|
|
|
int j;
|
|
|
|
for(j = 1; j <= 12; ++j) {
|
|
|
|
if((KGlobal::locale()->calendar()->monthName(j, 2000, true).lower() == scannedParts[d->m_partPos['m']].lower())
|
|
|
|
|| (monthNames[j-1] == scannedParts[d->m_partPos['m']].lower())) {
|
|
|
|
mon = j;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(j == 13) {
|
|
|
|
qWarning("Unknown month '%s'", scannedParts[d->m_partPos['m']].data());
|
|
|
|
return TQDate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 TQDate(yr, mon, day);
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
TQString scannedDelim[2];
|
|
|
|
TQString formatParts[3];
|
|
|
|
TQString 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 TQString& datein) Too many parts in date format");
|
|
|
|
return TQDate();
|
|
|
|
}
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
switch(m_dateFormat[i].latin1()) {
|
|
|
|
case 'm':
|
|
|
|
case 'd':
|
|
|
|
case 'y':
|
|
|
|
formatParts[part] += m_dateFormat[i];
|
|
|
|
break;
|
|
|
|
case '/':
|
|
|
|
case '-':
|
|
|
|
case '.':
|
|
|
|
case '\'':
|
|
|
|
if(delim == 2) {
|
|
|
|
qWarning("MyMoneyQifProfile::date(const TQString& datein) Too many delimiters in date format");
|
|
|
|
return TQDate();
|
|
|
|
}
|
|
|
|
formatDelim[delim] = m_dateFormat[i];
|
|
|
|
++delim;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
qWarning("MyMoneyQifProfile::date(const TQString& datein) Invalid char in date format");
|
|
|
|
return TQDate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
part = 0;
|
|
|
|
delim = 0;
|
|
|
|
bool prevWasChar = false;
|
|
|
|
for(i = 0; i < datein.length(); ++i) {
|
|
|
|
switch(datein[i].latin1()) {
|
|
|
|
case '/':
|
|
|
|
case '.':
|
|
|
|
case '-':
|
|
|
|
case '\'':
|
|
|
|
if(delim == 2) {
|
|
|
|
qWarning("MyMoneyQifProfile::date(const TQString& datein) Too many delimiters in date field");
|
|
|
|
return TQDate();
|
|
|
|
}
|
|
|
|
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] == ' ') ? TQChar('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] != TQChar('\'')) {
|
|
|
|
qWarning("MyMoneyQifProfile::date(const TQString& datein) Invalid delimiter '%s' when '%s' was expected",
|
|
|
|
scannedDelim[i].latin1(), formatDelim[i].latin1());
|
|
|
|
return TQDate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString msg;
|
|
|
|
for(i = 0; i < 3; ++i) {
|
|
|
|
switch(formatParts[i][0].latin1()) {
|
|
|
|
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(KGlobal::locale()->calendar()->monthName(j, 2000, true).lower() == formatParts[i].lower()) {
|
|
|
|
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] == TQChar('\'')) {
|
|
|
|
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 = TQString("Length of year (%1) does not match expected length (%2).")
|
|
|
|
.tqarg(scannedParts[i].length()).tqarg(formatParts[i].length());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if(!msg.isEmpty()) {
|
|
|
|
qWarning("MyMoneyQifProfile::date(const TQString& datein) %s",msg.latin1());
|
|
|
|
return TQDate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return TQDate(yr, mon, day);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString MyMoneyQifProfile::twoDigitYear(const TQChar delim, int yr) const
|
|
|
|
{
|
|
|
|
TQChar realDelim = delim;
|
|
|
|
TQString buffer;
|
|
|
|
|
|
|
|
if(delim) {
|
|
|
|
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 += TQString::number(yr);
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString MyMoneyQifProfile::value(const TQChar& def, const MyMoneyMoney& valuein) const
|
|
|
|
{
|
|
|
|
unsigned char _decimalSeparator;
|
|
|
|
unsigned char _thousandsSeparator;
|
|
|
|
TQString res;
|
|
|
|
|
|
|
|
_decimalSeparator = MyMoneyMoney::decimalSeparator();
|
|
|
|
_thousandsSeparator = MyMoneyMoney::thousandSeparator();
|
|
|
|
MyMoneyMoney::signPosition _signPosition = MyMoneyMoney::negativeMonetarySignPosition();
|
|
|
|
|
|
|
|
MyMoneyMoney::setDecimalSeparator(amountDecimal(def));
|
|
|
|
MyMoneyMoney::setThousandSeparator(amountThousands(def));
|
|
|
|
MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney);
|
|
|
|
|
|
|
|
res = valuein.formatMoney("", 2);
|
|
|
|
|
|
|
|
MyMoneyMoney::setDecimalSeparator(_decimalSeparator);
|
|
|
|
MyMoneyMoney::setThousandSeparator(_thousandsSeparator);
|
|
|
|
MyMoneyMoney::setNegativeMonetarySignPosition(_signPosition);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
MyMoneyMoney MyMoneyQifProfile::value(const TQChar& def, const TQString& valuein) const
|
|
|
|
{
|
|
|
|
unsigned char _decimalSeparator;
|
|
|
|
unsigned char _thousandsSeparator;
|
|
|
|
MyMoneyMoney res;
|
|
|
|
|
|
|
|
_decimalSeparator = MyMoneyMoney::decimalSeparator();
|
|
|
|
_thousandsSeparator = MyMoneyMoney::thousandSeparator();
|
|
|
|
MyMoneyMoney::signPosition _signPosition = MyMoneyMoney::negativeMonetarySignPosition();
|
|
|
|
|
|
|
|
MyMoneyMoney::setDecimalSeparator(amountDecimal(def));
|
|
|
|
MyMoneyMoney::setThousandSeparator(amountThousands(def));
|
|
|
|
MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney);
|
|
|
|
|
|
|
|
res = MyMoneyMoney(valuein);
|
|
|
|
|
|
|
|
MyMoneyMoney::setDecimalSeparator(_decimalSeparator);
|
|
|
|
MyMoneyMoney::setThousandSeparator(_thousandsSeparator);
|
|
|
|
MyMoneyMoney::setNegativeMonetarySignPosition(_signPosition);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setFilterScriptImport(const TQString& script)
|
|
|
|
{
|
|
|
|
if(m_filterScriptImport != script)
|
|
|
|
m_isDirty = true;
|
|
|
|
|
|
|
|
m_filterScriptImport = script;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setFilterScriptExport(const TQString& script)
|
|
|
|
{
|
|
|
|
if(m_filterScriptExport != script)
|
|
|
|
m_isDirty = true;
|
|
|
|
|
|
|
|
m_filterScriptExport = script;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::setFilterFileType(const TQString& 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString MyMoneyQifProfile::inputDateFormat(void) const
|
|
|
|
{
|
|
|
|
TQStringList list;
|
|
|
|
possibleDateFormats(list);
|
|
|
|
if(list.count() == 1)
|
|
|
|
return list.first();
|
|
|
|
return TQString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::possibleDateFormats(TQStringList& list) const
|
|
|
|
{
|
|
|
|
TQStringList defaultList = TQStringList::split(":", "y,m,d:y,d,m:m,d,y:m,y,d:d,m,y:d,y,m");
|
|
|
|
list.clear();
|
|
|
|
TQStringList::const_iterator it_d;
|
|
|
|
for(it_d = defaultList.begin(); it_d != defaultList.end(); ++it_d) {
|
|
|
|
TQStringList parts = TQStringList::split(",", *it_d);
|
|
|
|
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) {
|
|
|
|
TQString 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.begin(); it_d != defaultList.end(); ++it_d) {
|
|
|
|
TQString format = *it_d;
|
|
|
|
format.replace('y', "%y");
|
|
|
|
format.replace('m', "%m");
|
|
|
|
format.replace('d', "%d");
|
|
|
|
format.replace(',', " ");
|
|
|
|
list << format;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::autoDetect(const TQStringList& lines)
|
|
|
|
{
|
|
|
|
m_dateFormat = TQString();
|
|
|
|
m_decimal.clear();
|
|
|
|
m_thousands.clear();
|
|
|
|
|
|
|
|
TQString numericRecords = "BT$OIQ";
|
|
|
|
TQStringList::const_iterator it;
|
|
|
|
int datesScanned = 0;
|
|
|
|
// section: used to switch between different TQIF sections,
|
|
|
|
// because the Record identifiers are ambigous 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;
|
|
|
|
TQRegExp price("\"(.*)\",(.*),\"(.*)\"");
|
|
|
|
for(it = lines.begin(); it != lines.end(); ++it) {
|
|
|
|
TQChar c((*it)[0]);
|
|
|
|
if(c == '!') {
|
|
|
|
TQString sname = (*it).lower();
|
|
|
|
section = 0;
|
|
|
|
if(sname.startsWith("!account"))
|
|
|
|
section = 1;
|
|
|
|
else if(sname.startsWith("!type")) {
|
|
|
|
if(sname.startsWith("!type:cat")
|
|
|
|
|| sname.startsWith("!type:payee")
|
|
|
|
|| sname.startsWith("!type:security")
|
|
|
|
|| sname.startsWith("!type:class")) {
|
|
|
|
section = 0;
|
|
|
|
} else if(sname.startsWith("!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.search(*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) {
|
|
|
|
TQMap<int, int> 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQMap<int, int>::const_iterator it_a;
|
|
|
|
TQMap<int, int>::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.begin();
|
|
|
|
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.begin();
|
|
|
|
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 TQString& txt, TQChar& decimal, TQChar& thousands) const
|
|
|
|
{
|
|
|
|
TQChar first, second;
|
|
|
|
TQRegExp numericChars("[0-9-()]");
|
|
|
|
for(unsigned int i = 0; i < txt.length(); ++i) {
|
|
|
|
if(numericChars.search(txt[i]) == -1) {
|
|
|
|
first = second;
|
|
|
|
second = txt[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!second.isNull())
|
|
|
|
decimal = second;
|
|
|
|
if(!first.isNull())
|
|
|
|
thousands = first;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MyMoneyQifProfile::scanDate(const TQString& txt) const
|
|
|
|
{
|
|
|
|
// extract the parts from the txt
|
|
|
|
TQValueVector<TQString> parts(3); // the various parts of the date
|
|
|
|
d->dissectDate(parts, txt);
|
|
|
|
|
|
|
|
// now analyse 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "mymoneyqifprofile.moc"
|