Logo Search packages:      
Sourcecode: qbankmanager version File versions  Download package

app.cpp

/***************************************************************************
 $RCSfile: app.cpp,v $
                             -------------------
    cvs         : $Id: app.cpp,v 1.44 2005/09/01 07:27:38 aquamaniac Exp $
    begin       : Mon Mar 01 2004
    copyright   : (C) 2004 by Martin Preuss
    email       : martin@libchipcard.de

 ***************************************************************************
 *          Please see toplevel file COPYING for license details           *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif


#include "app.h"
#include "timport.h"

#include <aqbanking/jobsingletransfer.h>

#include <gwenhywfar/debug.h>
#include <gwenhywfar/directory.h>
#include <gwenhywfar/text.h>

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <regex.h>

#ifdef HAVE_ICONV_H
# include <iconv.h>
#endif

#ifdef WIN32
# include <io.h> // for open()
# define strcasecmp stricmp
# define snprintf _snprintf
# define DIRSEP "\\"
#else
# define DIRSEP "/"
#endif



App::App(const char *appname, const char *fname)
:FRONTEND_CLASS(appname, fname)
, _lastUniqueId(0)
, _fuzzyThreshold(APP_FUZZY_THRESHOLD)
, _sensitiveFuzzyThreshold(APP_FUZZY_SENSITIVE_THRESHOLD){
}



App::~App(){
}



std::list<Account*> &App::getAppAccounts(){
  return _accounts;
}



int App::init() {
  GWEN_DB_NODE *db;
  GWEN_DB_NODE *dbT;
  int rv;
  GWEN_TYPE_UINT32 currentVersion;

  rv=FRONTEND_CLASS::init();
  if (rv)
    return rv;

  /* read configuration */
  db=getAppData();
  db=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_DEFAULT, "prg");

  currentVersion=
    (AQBANKING_KDE_VERSION_MAJOR<<24)+
    (AQBANKING_KDE_VERSION_MINOR<<16)+
    (AQBANKING_KDE_VERSION_PATCHLEVEL<<8)+
    AQBANKING_KDE_VERSION_BUILD;

  _lastVersion=GWEN_DB_GetIntValue(db, "lastVersion", 0,
                                   APP_LAST_VERSION_NONE);

  _fuzzyThreshold=GWEN_DB_GetIntValue(db, "fuzzyThreshold", 0,
                                      APP_FUZZY_THRESHOLD);

  _sensitiveFuzzyThreshold=GWEN_DB_GetIntValue(db,
                                               "sensitiveFuzzyThreshold", 0,
                                               APP_FUZZY_SENSITIVE_THRESHOLD);

  dbT=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_DEFAULT,
                       "tMatcherRules");
  if (dbT)
    _xaMatcherRules=GWEN_DB_Group_dup(dbT);
  else
    _xaMatcherRules=GWEN_DB_Group_new("tMatcherRules");

  dbT=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_DEFAULT,
                       "exportRules");
  if (dbT)
    _exportRules=GWEN_DB_Group_dup(dbT);
  else
    _exportRules=GWEN_DB_Group_new("exportRules");

  _lastUniqueId=GWEN_DB_GetIntValue(db, "lastUniqueId", 0, 0);
  _lastPayeeId=GWEN_DB_GetIntValue(db, "lastPayeeId", 0, 0);
  _lastCategoryId=GWEN_DB_GetIntValue(db, "lastCategoryId", 0, 0);

  _optionAutoAssignPayee=GWEN_DB_GetIntValue(db, "autoAssignPayee", 0, 1);
  _optionAutoAskForPayee=GWEN_DB_GetIntValue(db, "autoAskForPayee", 0, 1);

  dbT=GWEN_DB_GetGroup(db, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                       "accounts");
  if (dbT) {
    GWEN_DB_NODE *dbAccount;

    dbAccount=GWEN_DB_FindFirstGroup(dbT, "account");
    while(dbAccount) {
      Account *a;

      a=new Account(this);
      if (!a->fromDb(dbAccount)) {
      DBG_ERROR(0, "Bad account in configuration file");
      delete a;
      }
      else {
      AB_ACCOUNT *ba;

        ba=getAccount(a->getBankingId());
      if (ba) {
        const char *s;

        DBG_INFO(0, "Updating account");
        s=AB_Account_GetBankName(ba);
        if (s)
          a->setBankName(s);
        s=AB_Account_GetAccountName(ba);
        if (s)
          a->setAccountName(s);
        s=AB_Account_GetOwnerName(ba);
        if (s)
          a->setOwnerName(s);
        }
        else {
          DBG_NOTICE(0, "Account "GWEN_TYPE_TMPL_UINT32" no longer "
                     "supported by AqBanking",
                     a->getBankingId());
        }
      DBG_INFO(0, "Adding account");
      _accounts.push_back(a);
      }
      dbAccount=GWEN_DB_FindNextGroup(dbAccount, "account");
    } /* while */
  }

  // read payees
  rv=loadPayees();
  if (rv) {
    DBG_INFO(0, "Error loading payees (%d)", rv);
    return rv;
  }

  if (_payees.empty()) {
    /* storing payees within the AqBanking configuration is now obsolete,
     * so we only try to read from it if there were no payees in the new
     * payee.db file */
    dbT=GWEN_DB_GetGroup(db, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                   "payees");
    if (dbT) {
      GWEN_DB_NODE *dbPayee;

      dbPayee=GWEN_DB_FindFirstGroup(dbT, "payee");
      while(dbPayee) {
      Payee *py;

      py=new Payee();
      if (!py->fromDb(dbPayee)) {
        DBG_ERROR(0, "Bad payee in configuration file");
        delete py;
      }
      else {
        _payees.push_back(py);
      }
      dbPayee=GWEN_DB_FindNextGroup(dbPayee, "payee");
      } /* while */
    }
  }

  // read categories
  rv=loadCategories();
  if (rv) {
    DBG_INFO(0, "Error loading categories (%d)", rv);
    return rv;
  }
  if (_categories.empty()) {
    rv=_loadCategoryFile(CATEGORYDIR "/gnucash-full.db");
    DBG_INFO(0, "Error loading categories (%d)", rv);
    return rv;
  }
  if (_categories.empty()) {
    Category *cat;
    std::string name;
    std::string descr;

    /* create both toplevel categories */
    name=tr("Income");
    descr=tr("All income categories");
    cat=new Category("", name.c_str(), descr.c_str(), 0);
    cat->setIsIncome(true);
    addCategory(cat, 0);

    name=tr("Expense");
    descr=tr("All expense categories");
    cat=new Category("", name.c_str(), descr.c_str(), 0);
    cat->setIsIncome(false);
    addCategory(cat, 0);
  }

  if ((_lastVersion!=APP_LAST_VERSION_NONE) &&
      (_lastVersion<
       ((0<<24)+
        (9<<16)+
        (18<<8)+
        4))) {
    DBG_NOTICE(0, "Updating from old version, scanning all jobs once");
    /* scan all jobs */
    scanAllJobs();
  }

  if ((_lastVersion!=APP_LAST_VERSION_NONE) &&
      (_lastVersion<
       ((0<<24)+
        (9<<16)+
        (28<<8)+
        3))) {
    std::list<Payee*>::iterator pit;

    DBG_NOTICE(0, "Updating from old version, adapting matcher rules");
    _adjustRules(_xaMatcherRules);

    for (pit=_payees.begin(); pit!=_payees.end(); pit++) {
      _adjustRules((*pit)->rules());
    }
  }

  /* read transfers */
  dbT=GWEN_DB_GetGroup(db, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                       "transfers");
  if (dbT) {
    GWEN_DB_NODE *dbTransfer;

    dbTransfer=GWEN_DB_FindFirstGroup(dbT, "transfer");
    while(dbTransfer) {
      addTransfer(new Transfer(dbTransfer));
      dbTransfer=GWEN_DB_FindNextGroup(dbTransfer, "transfer");
    } // while
  }

  /* sync account list */
  if (!updateAccountList())
    return AB_ERROR_GENERIC;
  return 0;
}



int App::fini() {
  std::list<Account*>::iterator it;
  std::list<RefPointer<Transfer> >::iterator itt;
  std::list<RefPointer<StandingOrder> >::iterator its;
  std::list<Payee*>::iterator pit;
  std::list<Category*>::iterator cit;
  GWEN_DB_NODE *db;
  GWEN_DB_NODE *dbT;
  int rv;

  db=getAppData();
  db=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_OVERWRITE_GROUPS, "prg");
  assert(db);

  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "lastVersion",
                      (AQBANKING_KDE_VERSION_MAJOR<<24)+
                      (AQBANKING_KDE_VERSION_MINOR<<16)+
                      (AQBANKING_KDE_VERSION_PATCHLEVEL<<8)+
                      AQBANKING_KDE_VERSION_BUILD);

  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "lastPayeeId", _lastPayeeId);
  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "lastCategoryId", _lastCategoryId);
  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS,
                  "lastUniqueId", _lastUniqueId);

  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "fuzzyThreshold",
                      _fuzzyThreshold);
  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "sensitiveFuzzyThreshold",
                      _sensitiveFuzzyThreshold);

  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "autoAssignPayee",
                      _optionAutoAssignPayee);
  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS,
                      "autoAskForPayee",
                      _optionAutoAskForPayee);

  dbT=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_OVERWRITE_GROUPS, "tMatcherRules");
  assert(dbT);
  GWEN_DB_AddGroupChildren(dbT, _xaMatcherRules);
  GWEN_DB_Group_free(_xaMatcherRules);
  _xaMatcherRules=0;

  dbT=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_OVERWRITE_GROUPS, "exportRules");
  assert(dbT);
  GWEN_DB_AddGroupChildren(dbT, _exportRules);
  GWEN_DB_Group_free(_exportRules);
  _exportRules=0;

  // save accounts
  dbT=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_DEFAULT, "accounts");
  for (it=_accounts.begin(); it!=_accounts.end(); it++) {
    GWEN_DB_NODE *dbAccount;

    dbAccount=GWEN_DB_GetGroup(dbT, GWEN_PATH_FLAGS_CREATE_GROUP,
                         "account");
    assert(dbAccount);
    if (!(*it)->toDb(dbAccount)) {
      DBG_ERROR(0, "Could not save account");
      return AB_ERROR_GENERIC;
    }
  }

  // save payees
  rv=savePayees();
  if (rv) {
    DBG_INFO(0, "Error saving payees (%d)", rv);
    return rv;
  }

  // save categories
  rv=saveCategories();
  if (rv) {
    DBG_INFO(0, "Error saving categories (%d)", rv);
    return rv;
  }

  // clear lists
  for (pit=_payees.begin(); pit!=_payees.end(); pit++)
    delete *pit;
  _payees.clear();

  for (it=_accounts.begin(); it!=_accounts.end(); it++)
    delete *it;
  _accounts.clear();

  for (cit=_categories.begin(); cit!=_categories.end(); cit++)
    delete *cit;
  _categories.clear();

  return FRONTEND_CLASS::fini();
}



void App::_adjustRules(GWEN_DB_NODE *db) {
  GWEN_DB_NODE *dbT;

  dbT=GWEN_DB_FindFirstGroup(db, "rule");
  while(dbT) {
    GWEN_DB_NODE *dbD;

    dbD=GWEN_DB_GetGroup(dbT, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                         "data/text");
    if (dbD) {
      const char *s;

      s=GWEN_DB_GetCharValue(dbD, "purpose", 0, 0);
      if (s) {
        GWEN_BUFFER *dbuf;
        GWEN_DB_NODE *dbNR;

        dbNR=GWEN_DB_GetGroup(dbD, GWEN_DB_FLAGS_OVERWRITE_GROUPS, "compare");
        assert(dbNR);
        GWEN_DB_SetCharValue(dbNR, GWEN_DB_FLAGS_OVERWRITE_VARS,
                             "operation", "contains");
        dbuf=GWEN_Buffer_new(0, 32, 0, 1);
        while(*s) {
          if (*s=='*' || *s=='?')
            GWEN_Buffer_AppendByte(dbuf, ' ');
          else
            GWEN_Buffer_AppendByte(dbuf, *s);
          s++;
        }
        GWEN_Text_CondenseBuffer(dbuf);
        s=GWEN_Buffer_GetStart(dbuf);
        while(*s && *s==' ')
          s++;
        GWEN_DB_SetCharValue(dbNR, GWEN_DB_FLAGS_OVERWRITE_VARS,
                             "argument", s);
        GWEN_Buffer_free(dbuf);
      }
      GWEN_DB_DeleteVar(dbD, "purpose");
      GWEN_DB_DeleteVar(dbD, "purposeSenseCase");
    }
    dbD=GWEN_DB_GetGroup(dbT, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                         "data/payee");
    if (dbD) {
      const char *s;

      s=GWEN_DB_GetCharValue(dbD, "payee", 0, 0);
      if (s) {
        GWEN_BUFFER *dbuf;
        GWEN_DB_NODE *dbNR;

        dbNR=GWEN_DB_GetGroup(dbD, GWEN_DB_FLAGS_OVERWRITE_GROUPS, "compare");
        assert(dbNR);
        GWEN_DB_SetCharValue(dbNR, GWEN_DB_FLAGS_OVERWRITE_VARS,
                             "operation", "contains");
        dbuf=GWEN_Buffer_new(0, 32, 0, 1);
        while(*s) {
          if (*s=='*' || *s=='?')
            GWEN_Buffer_AppendByte(dbuf, ' ');
          else
            GWEN_Buffer_AppendByte(dbuf, *s);
          s++;
        }
        GWEN_Text_CondenseBuffer(dbuf);
        s=GWEN_Buffer_GetStart(dbuf);
        while(*s && *s==' ')
          s++;
        GWEN_DB_SetCharValue(dbNR, GWEN_DB_FLAGS_OVERWRITE_VARS,
                             "argument", s);
        GWEN_Buffer_free(dbuf);
      }
      GWEN_DB_DeleteVar(dbD, "payee");
      GWEN_DB_DeleteVar(dbD, "payeeSenseCase");
    }

    dbT=GWEN_DB_FindNextGroup(dbT, "rule");
  }
}



int App::_getPayeesFileName(GWEN_BUFFER *bdir) {
  int rv;

  rv=AB_Banking_GetAppUserDataDir(getCInterface(), bdir);
  if (rv) {
    DBG_ERROR(0, "Could not get AppUserDataDir (%d)", rv);
    return rv;
  }

  rv=GWEN_Directory_GetPath(GWEN_Buffer_GetStart(bdir),
                      GWEN_PATH_FLAGS_CHECKROOT);
  if (rv) {
    DBG_ERROR(0, "Could not create path \"%s\" (%d)",
              GWEN_Buffer_GetStart(bdir), rv);
    return rv;
  }

  GWEN_Buffer_AppendString(bdir, DIRSEP "payees.db");

  return 0;
}



int App::savePayees() {
  GWEN_BUFFER *bdir;
  int rv;
  GWEN_DB_NODE *db;
  GWEN_DB_NODE *dbT;
  std::list<Payee*>::iterator pit;

  bdir=GWEN_Buffer_new(0, 256, 0, 1);
  rv=_getPayeesFileName(bdir);
  if (rv) {
    DBG_INFO(0, "here (%d)", rv);
    GWEN_Buffer_free(bdir);
    return AB_ERROR_GENERIC;
  }
  db=GWEN_DB_Group_new("root");

  // save payees
  dbT=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_DEFAULT, "payees");
  for (pit=_payees.begin(); pit!=_payees.end(); pit++) {
    GWEN_DB_NODE *dbPayee;

    dbPayee=GWEN_DB_GetGroup(dbT, GWEN_PATH_FLAGS_CREATE_GROUP,
                             "payee");
    assert(dbPayee);
    if (!(*pit)->toDb(dbPayee)) {
      DBG_ERROR(0, "Could not save payee");
      GWEN_DB_Group_free(db);
      GWEN_Buffer_free(bdir);
      return AB_ERROR_GENERIC;
    }
  }

  rv=GWEN_DB_WriteFile(db, GWEN_Buffer_GetStart(bdir),
                   GWEN_DB_FLAGS_DEFAULT);
  if (rv) {
    DBG_ERROR(0, "Could not write file \"%s\" (%d)",
            GWEN_Buffer_GetStart(bdir), rv);
    GWEN_DB_Group_free(db);
    GWEN_Buffer_free(bdir);
    return AB_ERROR_GENERIC;
  }

  GWEN_DB_Group_free(db);
  GWEN_Buffer_free(bdir);
  return 0;
}



int App::loadPayees() {
  GWEN_BUFFER *bdir;
  int rv;
  GWEN_DB_NODE *db;
  GWEN_DB_NODE *dbT;
  FILE *f;

  bdir=GWEN_Buffer_new(0, 256, 0, 1);
  rv=_getPayeesFileName(bdir);
  if (rv) {
    DBG_INFO(0, "here (%d)", rv);
    GWEN_Buffer_free(bdir);
    return rv;
  }

  f=fopen(GWEN_Buffer_GetStart(bdir), "r");
  if (!f) {
    DBG_INFO(0, "Payee db does not exist, will create it later");
    GWEN_Buffer_free(bdir);
    return 0;
  }
  fclose(f);

  db=GWEN_DB_Group_new("root");

  rv=GWEN_DB_ReadFile(db, GWEN_Buffer_GetStart(bdir),
                  GWEN_DB_FLAGS_DEFAULT |
                  GWEN_PATH_FLAGS_CREATE_GROUP);
  if (rv) {
    DBG_INFO(0, "Error reading payee file (%d)", rv);
    GWEN_Buffer_free(bdir);
    return AB_ERROR_GENERIC;
  }

  dbT=GWEN_DB_GetGroup(db, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                       "payees");
  if (dbT) {
    GWEN_DB_NODE *dbPayee;

    dbPayee=GWEN_DB_FindFirstGroup(dbT, "payee");
    while(dbPayee) {
      Payee *py;

      py=new Payee();
      if (!py->fromDb(dbPayee)) {
        DBG_ERROR(0, "Bad payee in configuration file");
        delete py;
      }
      else {
        _payees.push_back(py);
      }
      dbPayee=GWEN_DB_FindNextGroup(dbPayee, "payee");
    } /* while */
  }

  GWEN_DB_Group_free(db);
  GWEN_Buffer_free(bdir);

  return 0;
}



int App::_getCategoriesFileName(GWEN_BUFFER *bdir) {
  int rv;

  rv=AB_Banking_GetAppUserDataDir(getCInterface(), bdir);
  if (rv) {
    DBG_ERROR(0, "Could not get AppUserDataDir (%d)", rv);
    return rv;
  }

  rv=GWEN_Directory_GetPath(GWEN_Buffer_GetStart(bdir),
                      GWEN_PATH_FLAGS_CHECKROOT);
  if (rv) {
    DBG_ERROR(0, "Could not create path \"%s\" (%d)",
              GWEN_Buffer_GetStart(bdir), rv);
    return rv;
  }

  GWEN_Buffer_AppendString(bdir, DIRSEP "categories.db");

  return 0;
}



int App::saveCategories() {
  GWEN_BUFFER *bdir;
  int rv;
  GWEN_DB_NODE *db;
  GWEN_DB_NODE *dbT;
  std::list<Category*>::iterator cit;

  bdir=GWEN_Buffer_new(0, 256, 0, 1);
  rv=_getCategoriesFileName(bdir);
  if (rv) {
    DBG_INFO(0, "here (%d)", rv);
    GWEN_Buffer_free(bdir);
    return AB_ERROR_GENERIC;
  }
  db=GWEN_DB_Group_new("root");

  // save categories
  dbT=GWEN_DB_GetGroup(db, GWEN_DB_FLAGS_DEFAULT, "categories");
  for (cit=_categories.begin(); cit!=_categories.end(); cit++) {
    GWEN_DB_NODE *dbCategory;

    dbCategory=GWEN_DB_GetGroup(dbT, GWEN_PATH_FLAGS_CREATE_GROUP,
                                "category");
    assert(dbCategory);
    (*cit)->toDb(dbCategory);
  }

  rv=GWEN_DB_WriteFile(db, GWEN_Buffer_GetStart(bdir),
                   GWEN_DB_FLAGS_DEFAULT);
  if (rv) {
    DBG_ERROR(0, "Could not write file \"%s\" (%d)",
            GWEN_Buffer_GetStart(bdir), rv);
    GWEN_DB_Group_free(db);
    GWEN_Buffer_free(bdir);
    return AB_ERROR_GENERIC;
  }

  GWEN_DB_Group_free(db);
  GWEN_Buffer_free(bdir);
  return 0;
}



int App::_loadCategoryFile(const char *fname) {
  int rv;
  GWEN_DB_NODE *db;
  GWEN_DB_NODE *dbT;
  FILE *f;

  f=fopen(fname, "r");
  if (!f) {
    DBG_INFO(0, "Category db does not exist, will create it later");
    return 0;
  }
  fclose(f);

  db=GWEN_DB_Group_new("root");
  rv=GWEN_DB_ReadFile(db, fname,
                  GWEN_DB_FLAGS_DEFAULT |
                  GWEN_PATH_FLAGS_CREATE_GROUP);
  if (rv) {
    DBG_INFO(0, "Error reading category file (%d)", rv);
    return AB_ERROR_GENERIC;
  }

  dbT=GWEN_DB_GetGroup(db, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                       "categories");
  if (dbT) {
    GWEN_DB_NODE *dbCategory;

    dbCategory=GWEN_DB_FindFirstGroup(dbT, "category");
    while(dbCategory) {
      _categories.push_back(new Category(dbCategory, 0));
      dbCategory=GWEN_DB_FindNextGroup(dbCategory, "category");
    } /* while */
  }

  GWEN_DB_Group_free(db);

  return 0;
}



int App::loadCategories() {
  GWEN_BUFFER *bdir;
  int rv;

  bdir=GWEN_Buffer_new(0, 256, 0, 1);
  rv=_getCategoriesFileName(bdir);
  if (rv) {
    DBG_INFO(0, "here (%d)", rv);
    GWEN_Buffer_free(bdir);
    return rv;
  }

  rv=_loadCategoryFile(GWEN_Buffer_GetStart(bdir));
  GWEN_Buffer_free(bdir);
  return rv;
}



Account *App::findAccount(const char *bankCode,
                          const char *accountNumber) {
  std::list<Account*>::iterator it;

  if (!bankCode)
    bankCode="";
  if (!accountNumber)
    accountNumber="";

  for (it=_accounts.begin(); it!=_accounts.end(); it++) {
    if ((strcasecmp((*it)->getBankCode().c_str(),
                    bankCode)==0) &&
        (strcasecmp((*it)->getAccountNumber().c_str(),
                    accountNumber)==0))
      return *it;
  }

  return 0;
}



bool App::updateAccountList() {
  std::list<AB_ACCOUNT*> oba;
  std::list<AB_ACCOUNT*>::iterator it;

  oba=getAccounts();
  if (oba.size()==0) {
    DBG_WARN(0, "No accounts");
  }
  for (it=oba.begin(); it!=oba.end(); it++) {
    Account *a;

    a=findAccount(AB_Account_GetBankCode(*it),
                  AB_Account_GetAccountNumber(*it));
    if (!a) {
      /* account is new, create it */
      a=new Account(this, *it);
      DBG_INFO(0, "Importing account");
      _accounts.push_back(a);
    }
    else {
      DBG_INFO(0, "Account already exists");
      a->setBankingId(AB_Account_GetUniqueId(*it));
    }
  }

  return true;
}



std::list<RefPointer<Transfer> > App::getTransfers(){
  std::list<RefPointer<Transfer> > tl;
  std::list<Account*>::iterator it;

  for (it=_accounts.begin(); it!=_accounts.end(); it++) {
    std::list<RefPointer<Transfer> >::iterator itt;

    for (itt=(*it)->getTransfers().begin();
         itt!=(*it)->getTransfers().end();
         itt++)
      tl.push_back(*itt);
  } // for accounts

  return tl;
}



void App::addTransfer(RefPointer<Transfer> t){
  Account *a;

  a=findAccount(t.ref().getLocalBankCode().c_str(),
                t.ref().getLocalAccountNumber().c_str());
  if (a)
    a->addTransfer(t);
  else {
    DBG_ERROR(0, "Unknown account \"%s/%s\"",
              t.ref().getLocalBankCode().c_str(),
              t.ref().getLocalAccountNumber().c_str());
  }
}



std::list<RefPointer<StandingOrder> > App::getStandingOrders() {
  std::list<RefPointer<StandingOrder> > tl;
  std::list<Account*>::iterator it;

  for (it=_accounts.begin(); it!=_accounts.end(); it++) {
    std::list<RefPointer<StandingOrder> >::iterator its;

    for (its=(*it)->getStandingOrders().begin();
         its!=(*it)->getStandingOrders().end();
         its++) {
      tl.push_back(*its);
    }
  } // for accounts

  return tl;
}



void App::addStandingOrder(RefPointer<StandingOrder> t) {
  Account *a;

  a=findAccount(t.ref().getLocalBankCode().c_str(),
                t.ref().getLocalAccountNumber().c_str());
  if (a)
    a->addStandingOrder(t);
  else {
    DBG_ERROR(0, "Unknown account \"%s/%s\"",
              t.ref().getLocalBankCode().c_str(),
              t.ref().getLocalAccountNumber().c_str());
  }
}



std::list<RefPointer<Transfer> > App::getDatedTransfers() {
  std::list<RefPointer<Transfer> > tl;
  std::list<Account*>::iterator it;

  for (it=_accounts.begin(); it!=_accounts.end(); it++) {
    std::list<RefPointer<Transfer> >::iterator its;

    for (its=(*it)->getDatedTransfers().begin();
         its!=(*it)->getDatedTransfers().end();
         its++) {
      tl.push_back(*its);
    }
  } // for accounts

  return tl;
}



void App::addDatedTransfer(RefPointer<Transfer> t) {
  Account *a;

  a=findAccount(t.ref().getLocalBankCode().c_str(),
                t.ref().getLocalAccountNumber().c_str());
  if (a)
    a->addDatedTransfer(t);
  else {
    DBG_ERROR(0, "Unknown account \"%s/%s\"",
              t.ref().getLocalBankCode().c_str(),
              t.ref().getLocalAccountNumber().c_str());
  }
}



void App::appInfoTransactionsChanged(Account *a){
  a->infoTransactionsChanged();
}



GWEN_TYPE_UINT32 App::getNextUniqueId() {
  return ++_lastUniqueId;
}



bool App::importContext(AB_IMEXPORTER_CONTEXT *ctx,
                        GWEN_TYPE_UINT32 flags){
  TransactionImporter imp(this, ctx, flags);

  if (!imp.checkTransactions()) {
    DBG_INFO(0, "Error checking transactions");
    return false;
  }

  if (!imp.importContext()) {
    DBG_INFO(0, "Error importing transactions");
    return false;
  }

  return true;
}



RefPointer<Transfer> App::findTransferByTransactionId(GWEN_TYPE_UINT32 id){
  std::list<Account*>::iterator it;

  for (it=_accounts.begin(); it!=_accounts.end(); it++) {
    std::list<RefPointer<Transfer> >::iterator itt;

    for (itt=(*it)->getTransfers().begin();
         itt!=(*it)->getTransfers().end();
         itt++)
      if (id==(*itt).ref().getTransactionId())
        return *itt;
  } // for accounts

  return 0;
}



void App::scanJobList(AB_JOB_LIST2 *jl) {
  AB_JOB_LIST2_ITERATOR *it;
  AB_JOB *j;

  it=AB_Job_List2_First(jl);
  if (!it)
    return;

  j=AB_Job_List2Iterator_Data(it);
  assert(j);
  while(j) {
    switch(AB_Job_GetType(j)) {
    case AB_Job_TypeGetBalance:
      break;
    case AB_Job_TypeGetTransactions:
      break;
    case AB_Job_TypeTransfer: {
      const AB_TRANSACTION *bt;

      bt=AB_JobSingleTransfer_GetTransaction(j);
      if (bt) {
        GWEN_TYPE_UINT32 tid;
        RefPointer<Transfer> t;

        DBG_INFO(0, "Checking transfer");
        tid=AB_Transaction_GetUniqueId(bt);
        if (tid) {
          t=findTransferByTransactionId(tid);
          if (t.isValid()) {
            DBG_INFO(0, "Transfer is known");
            if (t.ref().getTransactionId()==0)
              t.ref().setTransactionId(getNextUniqueId());
          }
          if (!t.isValid()) {
            t=new Transfer(bt);
            t.ref().setJobId(AB_Job_GetJobId(j));
            DBG_INFO(0, "Adding transfer");
            addTransfer(t);
          }
          if (!t.ref().getDate()) {
            DBG_INFO(0, "Adjusting date");
            t.ref().setDate(AB_Job_GetLastStatusChange(j));
          }
          if (t.ref().getStatus()<Transfer::StatusOk) {
            const GWEN_STRINGLIST *sl;

            sl=AB_Transaction_GetRemoteName(bt);
            if (sl) {
              GWEN_STRINGLISTENTRY *se;

              t.ref().clearRemoteName();
              se=GWEN_StringList_FirstEntry(sl);
              while(se) {
                const char *p;

                p=GWEN_StringListEntry_Data(se);
                assert(p);
                t.ref().addRemoteName(p);
                se=GWEN_StringListEntry_Next(se);
              } /* while */
            }
            sl=AB_Transaction_GetPurpose(bt);
            if (sl) {
              GWEN_STRINGLISTENTRY *se;

              t.ref().clearPurpose();
              se=GWEN_StringList_FirstEntry(sl);
              while(se) {
                const char *p;

                p=GWEN_StringListEntry_Data(se);
                assert(p);
                t.ref().addPurpose(p);
                se=GWEN_StringListEntry_Next(se);
              } /* while */
            }

            switch(AB_Job_GetStatus(j)) {
            case AB_Job_StatusEnqueued:
              DBG_INFO(0, "Status: Enqueued");
              t.ref().setStatus(Transfer::StatusEnqueued);
              break;

            case AB_Job_StatusNew:
            case AB_Job_StatusUpdated:
            case AB_Job_StatusSent:
            case AB_Job_StatusPending:
            case AB_Job_StatusDeferred:
              DBG_INFO(0, "Status: Pending");
              t.ref().setStatus(Transfer::StatusPending);
              break;
            case AB_Job_StatusFinished:
              DBG_INFO(0, "Status: Finished");
              t.ref().setStatus(Transfer::StatusOk);
              break;
            case AB_Job_StatusError:
              DBG_INFO(0, "Status: Error");
              t.ref().setStatus(Transfer::StatusError);
              break;
            default:
              t.ref().setStatus(Transfer::StatusUnknown);
              DBG_INFO(0, "Transfer status unknown");
              break;
            } // switch
          } // if status is changeable
          else {
            DBG_INFO(0, "Unmutable status %d", t.ref().getStatus());
          }
        } // if transaction id is valid
        else {
          DBG_INFO(0, "Ignoring job with no transfer id");
        }
      }
      break;
    }
    case AB_Job_TypeDebitNote:
      break;
    default:
      break;
    } // switch

    j=AB_Job_List2Iterator_Next(it);
  } // while
  AB_Job_List2Iterator_free(it);
}



void App::scanAllJobs() {
  AB_JOB_LIST2 *jl;

  jl=AB_Banking_GetPendingJobs(getCInterface());
  if (jl) {
    scanJobList(jl);
    AB_Job_List2_FreeAll(jl);
  }

  jl=AB_Banking_GetArchivedJobs(getCInterface());
  if (jl) {
    scanJobList(jl);
    AB_Job_List2_FreeAll(jl);
  }

  jl=AB_Banking_GetFinishedJobs(getCInterface());
  if (jl) {
    scanJobList(jl);
    AB_Job_List2_FreeAll(jl);
  }

  jl=AB_Banking_GetEnqueuedJobs(getCInterface());
  if (jl) {
    scanJobList(jl);
    AB_Job_List2_free(jl);
  }
}



GWEN_DB_NODE *App::getTransactionMatcherRules(){
  return _xaMatcherRules;
}



void App::setTransactionMatcherRules(GWEN_DB_NODE *db){
  assert(db);
  GWEN_DB_Group_free(_xaMatcherRules);
  _xaMatcherRules=GWEN_DB_Group_dup(db);
}



GWEN_DB_NODE *App::getExportRules(){
  return _exportRules;
}



void App::setExportRules(GWEN_DB_NODE *db){
  assert(db);
  GWEN_DB_Group_free(_exportRules);
  _exportRules=GWEN_DB_Group_dup(db);
}



bool App::_textMatches(const std::string &text, GWEN_DB_NODE *db) {
  GWEN_DB_NODE *dbR;
  bool allMustMatch;
  int matches=0;

  allMustMatch=GWEN_DB_GetIntValue(db, "allMustMatch", 0, 0);
  dbR=GWEN_DB_FindFirstGroup(db, "compare");
  if (!dbR) {
    DBG_DEBUG(0, "No \"compare\" group found");
    return true;
  }
  while(dbR) {
    const char *s;

    s=GWEN_DB_GetCharValue(dbR, "operation", 0, 0);
    if (s) {
      const char *arg;
      int thisMatches=false;

      arg=GWEN_DB_GetCharValue(dbR, "argument", 0, 0);
      if (arg) {
        TEXTMATCHER_OPERATION op;

        op=string2TextMatcherOperation(s);
        switch(op) {
        case TextMatcherOperation_Equals:
          thisMatches=(strcasecmp(text.c_str(), arg)==0);
          break;
        case TextMatcherOperation_EqualsNot:
          thisMatches=(strcasecmp(text.c_str(), arg)!=0);
          break;
        case TextMatcherOperation_Contains:
          thisMatches=(std::string::npos!=text.find(arg));
          break;
        case TextMatcherOperation_ContainsNot:
          thisMatches=(std::string::npos==text.find(arg));
          break;
        case TextMatcherOperation_MatchesRegExp: {
          regex_t preg;
          int regres;

          regres=regcomp(&preg, arg, REG_EXTENDED);
          if (regres) {
            char errBuf[256];

            regerror(regres, &preg, errBuf, sizeof(errBuf));
            DBG_ERROR(0, "regcomp(%s): %s", arg,
                      errBuf);
            regfree(&preg);
            return false;
          }
          regres=regexec(&preg, text.c_str(), 0, 0, 0);
          if (regres) {
            if (regres==REG_NOMATCH) {
              regfree(&preg);
              thisMatches=false;
            }
            else {
              char errBuf[256];

              regerror(regres, &preg, errBuf, sizeof(errBuf));
              DBG_ERROR(0, "regexec(%s): %s", arg,
                        errBuf);
              regfree(&preg);
              return false;
            }
          }
          else
            thisMatches=true;
          break;
        }

        case TextMatcherOperation_MatchesRegExpNot: {
          regex_t preg;
          int regres;

          regres=regcomp(&preg, arg, REG_EXTENDED);
          if (regres) {
            char errBuf[256];

            regerror(regres, &preg, errBuf, sizeof(errBuf));
            DBG_ERROR(0, "regcomp(%s): %s", arg,
                      errBuf);
            regfree(&preg);
            return false;
          }
          regres=regexec(&preg, text.c_str(), 0, 0, 0);
          if (regres) {
            if (regres==REG_NOMATCH) {
              DBG_ERROR(0, "No match: %s!=%s",
                        text.c_str(), arg);
              regfree(&preg);
              thisMatches=true;
            }
            else {
              char errBuf[256];

              regerror(regres, &preg, errBuf, sizeof(errBuf));
              DBG_ERROR(0, "regexec(%s): %s", arg,
                        errBuf);
              regfree(&preg);
              return false;
            }
          }
          else
            thisMatches=false;
          break;
        }

        default:
          DBG_ERROR(0, "Unknown matching operation \"%s\"", s);
          thisMatches=false;
        }

        if (allMustMatch) {
          if (!thisMatches)
            return false;
          else
            matches++;
        }
        else {
          if (thisMatches)
            return true;
        }
      }
      else {
        DBG_ERROR(0, "No argument");
      }
    }
    dbR=GWEN_DB_FindNextGroup(dbR, "compare");
  }

  return matches!=0;
}



bool App::_ruleAppliesToTransaction(GWEN_DB_NODE *dbRule,
                                    const Transaction *t,
                                    bool prematched) {
  int allAccounts;
  GWEN_DB_NODE *dbData;
  GWEN_DB_NODE *dbT;
  bool accMatch=true;
  bool xaMatch=true;

  dbData=GWEN_DB_GetGroup(dbRule, GWEN_DB_FLAGS_DEFAULT, "data");
  assert(dbData);

  allAccounts=GWEN_DB_GetIntValue(dbData, "account/allAccounts", 0, 0);

  if (!prematched) {
    // check date
    dbT=GWEN_DB_GetGroup(dbData, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                         "date");
    if (dbT) {
      GWEN_TIME *fromTime=0;
      GWEN_TIME *toTime=0;
      int mode;
      GWEN_TIME *cti;
      GWEN_TIME *tti;
      const GWEN_TIME *ti;
      int year, month, day;
      int tyear, tmonth, tday;
      GWEN_TYPE_UINT32 secs;
      const char *s;
  
      cti=GWEN_CurrentTime();
      assert(cti);
      GWEN_Time_GetBrokenDownDate(cti, &day, &month, &year);
  
      mode=GWEN_DB_GetIntValue(dbT, "range", 0, 0);
      switch(mode) {
      case TMatcherDateRange_All:
        // nothing to be done in this case
        break;
      case TMatcherDateRange_UntilToday:
        toTime=GWEN_Time_dup(cti);
        break;
      case TMatcherDateRange_CurrentMonth:
        fromTime=GWEN_Time_new(year, month, 1, 0, 0, 0, 0);
        // calculate last day of the current month
        tyear=year;
        tmonth=month+1;
        if (tmonth>11) {
          tmonth-=12;
          tyear++;
        }
        tti=GWEN_Time_new(tyear, tmonth, 1, 0, 0, 0, 0);
        secs=GWEN_Time_Seconds(tti);
        secs-=60*60*24;
        GWEN_Time_free(tti);
        toTime=GWEN_Time_fromSeconds(secs);
        break;
      case TMatcherDateRange_MonthToDate:
        fromTime=GWEN_Time_new(year, month, 1, 0, 0, 0, 0);
        toTime=GWEN_Time_dup(cti);
        break;
      case TMatcherDateRange_CurrentYear:
        fromTime=GWEN_Time_new(year, 0, 1, 0, 0, 0, 0);
        toTime=GWEN_Time_new(year, 11, 31, 0, 0, 0, 0);
        break;
      case TMatcherDateRange_YearToDate:
        fromTime=GWEN_Time_new(year, 0, 1, 0, 0, 0, 0);
        toTime=GWEN_Time_dup(cti);
        break;
      case TMatcherDateRange_LastMonth:
        tti=GWEN_Time_new(year, month, 1, 0, 0, 0, 0);
        secs=GWEN_Time_Seconds(tti);
        secs-=60*60*24;
        GWEN_Time_free(tti);
        toTime=GWEN_Time_fromSeconds(secs);
        GWEN_Time_GetBrokenDownDate(toTime, &tday, &tmonth, &tyear);
        fromTime=GWEN_Time_new(tyear, tmonth, 1, 0, 0, 0, 0);
        break;
      case TMatcherDateRange_LastYear:
        fromTime=GWEN_Time_new(year-1, 0, 1, 0, 0, 0, 0);
        toTime=GWEN_Time_new(year-1, 11, 31, 0, 0, 0, 0);
        break;
      case TMatcherDateRange_Last30Days:
        secs=GWEN_Time_Seconds(cti);
        secs-=60*60*24*30;
        fromTime=GWEN_Time_fromSeconds(secs);
        break;
      case TMatcherDateRange_Last3Months:
        secs=GWEN_Time_Seconds(cti);
        secs-=60*60*24*30*3;
        fromTime=GWEN_Time_fromSeconds(secs);
        break;
      case TMatcherDateRange_Last6Months:
        secs=GWEN_Time_Seconds(cti);
        secs-=60*60*24*30*6;
        fromTime=GWEN_Time_fromSeconds(secs);
        break;
      case TMatcherDateRange_Last12Months:
        secs=GWEN_Time_Seconds(cti);
        secs-=60*60*24*365;
        fromTime=GWEN_Time_fromSeconds(secs);
        break;
      case TMatcherDateRange_UserDefined:
        s=GWEN_DB_GetCharValue(dbT, "fromDate", 0, 0);
        if (s) {
          fromTime=GWEN_Time_fromString(s, "YYYY/MM/DD");
          assert(fromTime);
        }
  
        s=GWEN_DB_GetCharValue(dbT, "toDate", 0, 0);
        if (s) {
          toTime=GWEN_Time_fromString(s, "YYYY/MM/DD");
          assert(toTime);
        }
        break;
      default:
        DBG_ERROR(0, "Unknown date range type (%d)", mode);
        abort();
      } // switch
      GWEN_Time_free(cti);
  
      // now compare dates
      ti=t->getDate();
      if (!ti)
        ti=t->getValutaDate();
      if (!ti) {
        DBG_ERROR(0, "No date/valutaDate in transaction");
        return false;
      }
      if (fromTime) {
        if (GWEN_Time_Diff(ti, fromTime)<0.0) {
          DBG_VERBOUS(0, "fromTime does not match");
          GWEN_Time_free(toTime);
          GWEN_Time_free(fromTime);
          return false;
        }
      }
      if (toTime) {
        if (GWEN_Time_Diff(toTime, ti)<0.0) {
          DBG_VERBOUS(0, "toTime does not match");
          GWEN_Time_free(toTime);
          GWEN_Time_free(fromTime);
          return false;
        }
      }
      GWEN_Time_free(toTime);
      GWEN_Time_free(fromTime);
    } // if date mentioned

    DBG_VERBOUS(0, "Checking account");
    accMatch=false;
    if (allAccounts!=0)
      accMatch=true;
    else {
      dbT=GWEN_DB_GetGroup(dbData, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                           "account/selected");
      if (dbT) {
        DBG_VERBOUS(0, "Have selected group...");
        dbT=GWEN_DB_FindFirstGroup(dbT, "account");
        while (dbT) {
          const char *accountId;
          const char *bankId;
  
          DBG_VERBOUS(0, "Have account group...");
          bankId=GWEN_DB_GetCharValue(dbT, "bankId", 0, "");
          accountId=GWEN_DB_GetCharValue(dbT, "accountId", 0, "");
  
          DBG_VERBOUS(0, "BankID: %s, AccountID: %s",
                     bankId, accountId);
          if (strcasecmp(t->getLocalBankCode().c_str(),
                         bankId)==0 &&
              strcasecmp(t->getLocalAccountNumber().c_str(),
                         accountId)==0) {
            accMatch=true;
            break;
          }
          dbT=GWEN_DB_FindNextGroup(dbT, "account");
        } // while
      } // if "account/selected"
    }
  
    if (!accMatch) {
      DBG_VERBOUS(0, "Account does not match");
      return false;
    }
  }

  xaMatch=true;

  // check purpose
  dbT=GWEN_DB_GetGroup(dbData, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                       "text");
  if (dbT) {
    std::string purpose;
    std::list<std::string>::const_iterator sit;

    /* combine purpose */
    for (sit=t->getPurpose().begin();
         sit!=t->getPurpose().end();
         sit++) {
      if (!purpose.empty() && !(*sit).empty())
        purpose+=" ";
      purpose+=*sit;
    }
    xaMatch=_textMatches(purpose, dbT);
  }

  if (!xaMatch)
    return false;

  // check amount
  dbT=GWEN_DB_GetGroup(dbData, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                       "amount");
  if (dbT) {
    int mode;
    const AB_VALUE *v;
    AB_VALUE *tv1;
    AB_VALUE *tv2;
    const char *s;

    v=t->getValue();
    if (v) {
      mode=GWEN_DB_GetIntValue(dbT, "mode", 0, 0);
      switch(mode) {
      case TMatcherAmountRange_All:
        break;
      case TMatcherAmountRange_Exactly:
        s=GWEN_DB_GetCharValue(dbT, "thisAmount", 0, 0);
        assert(s);
        tv1=AB_Value_fromString(s);
        assert(tv1);
        xaMatch=AB_Value_IsEqual(v, tv1);
        AB_Value_free(tv1);
        break;
      case TMatcherAmountRange_Range:
        s=GWEN_DB_GetCharValue(dbT, "fromAmount", 0, 0);
        assert(s);
        tv1=AB_Value_fromString(s);
        assert(tv1);
        s=GWEN_DB_GetCharValue(dbT, "toAmount", 0, 0);
        assert(s);
        tv2=AB_Value_fromString(s);
        assert(tv2);
        xaMatch=((AB_Value_Compare(v, tv1)!=-1) &&
                 (AB_Value_Compare(tv2, v)!=-1));
        AB_Value_free(tv2);
        AB_Value_free(tv1);
        break;
      case TMatcherAmountRange_Less:
        s=GWEN_DB_GetCharValue(dbT, "lessAmount", 0, 0);
        assert(s);
        tv1=AB_Value_fromString(s);
        assert(tv1);
        xaMatch=(AB_Value_Compare(v, tv1)==-1);
        AB_Value_free(tv1);
        break;
      case TMatcherAmountRange_More:
        s=GWEN_DB_GetCharValue(dbT, "moreAmount", 0, 0);
        assert(s);
        tv1=AB_Value_fromString(s);
        assert(tv1);
        xaMatch=(AB_Value_Compare(tv1, v)==-1);
        AB_Value_free(tv1);
        break;
      default:
        DBG_ERROR(0, "Unknown amount matching mode %d", mode);
        break;
      }
    }
  }

  if (!xaMatch)
    return false;

  // check payee
  dbT=GWEN_DB_GetGroup(dbData, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                       "payee");
  if (dbT) {
    std::string payee;
    std::list<std::string>::const_iterator sit;

    /* combine purpose */
    for (sit=t->getRemoteName().begin();
         sit!=t->getRemoteName().end();
         sit++) {
      if (!payee.empty() && !(*sit).empty())
        payee+=" ";
      payee+=*sit;
    }
    xaMatch=_textMatches(payee, dbT);
  }

  if (!xaMatch)
    return false;

  // check payee2
  dbT=GWEN_DB_GetGroup(dbData, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                       "payee2");
  if (dbT) {
    if (GWEN_DB_GetIntValue(dbT, "allPayees", 0, 1)==0) {
      std::string payee;
      unsigned int i;

      xaMatch=false;
      payee=t->getPayee();
      for (i=0; ; i++) {
        const char *s;

        s=GWEN_DB_GetCharValue(dbT, "payees", i, 0);
        if (!s)
          break;
        if (strcasecmp(payee.c_str(), s)==0) {
          xaMatch=true;
          break;
        }
      }
    }
  }

  if (!xaMatch)
    return false;


  // check category
  dbT=GWEN_DB_GetGroup(dbData, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                       "category");
  if (dbT) {
    if (GWEN_DB_GetIntValue(dbT, "allCategories", 0, 1)==0) {
      std::string cat;
      unsigned int i;

      xaMatch=false;
      cat=t->getCategory();
      for (i=0; ; i++) {
        const char *s;

        s=GWEN_DB_GetCharValue(dbT, "categories", i, 0);
        if (!s)
          break;
        if (strcasecmp(cat.c_str(), s)==0) {
          xaMatch=true;
          break;
        }
      }
    }
  }

  if (!xaMatch)
    return false;

  return true;
}



bool App::ruleAppliesToTransaction(GWEN_DB_NODE *dbRule,
                                   const Transaction *t) {
  return _ruleAppliesToTransaction(dbRule, t, false);
}



bool App::getMatchingTransactions(GWEN_DB_NODE *dbRule,
                                  std::list<RefPointer<Transaction> > &tl){
  std::list<Account*>::iterator ait;
  int allAccounts;
  GWEN_TIME *fromTime;
  GWEN_TIME *toTime;
  GWEN_DB_NODE *dbData;
  GWEN_DB_NODE *dbT;

  fromTime=toTime=0;

  dbData=GWEN_DB_GetGroup(dbRule, GWEN_DB_FLAGS_DEFAULT, "data");
  assert(dbData);

  allAccounts=GWEN_DB_GetIntValue(dbData, "account/allAccounts", 0, 0);

  // check date
  dbT=GWEN_DB_GetGroup(dbData, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                       "date");
  if (dbT) {
    int mode;
    GWEN_TIME *cti;
    GWEN_TIME *tti;
    int year, month, day;
    int tyear, tmonth, tday;
    GWEN_TYPE_UINT32 secs;
    const char *s;

    cti=GWEN_CurrentTime();
    assert(cti);
    GWEN_Time_GetBrokenDownDate(cti, &day, &month, &year);

    mode=GWEN_DB_GetIntValue(dbT, "range", 0, 0);
    switch(mode) {
    case TMatcherDateRange_All:
      // nothing to be done in this case
      break;
    case TMatcherDateRange_UntilToday:
      toTime=GWEN_Time_dup(cti);
      break;
    case TMatcherDateRange_CurrentMonth:
      fromTime=GWEN_Time_new(year, month, 1, 0, 0, 0, 0);
      // calculate last day of the current month
      tyear=year;
      tmonth=month+1;
      if (tmonth>11) {
        tmonth-=12;
        tyear++;
      }
      tti=GWEN_Time_new(tyear, tmonth, 1, 0, 0, 0, 0);
      secs=GWEN_Time_Seconds(tti);
      secs-=60*60*24;
      GWEN_Time_free(tti);
      toTime=GWEN_Time_fromSeconds(secs);
      break;
    case TMatcherDateRange_MonthToDate:
      fromTime=GWEN_Time_new(year, month, 1, 0, 0, 0, 0);
      toTime=GWEN_Time_dup(cti);
      break;
    case TMatcherDateRange_CurrentYear:
      fromTime=GWEN_Time_new(year, 0, 1, 0, 0, 0, 0);
      toTime=GWEN_Time_new(year, 11, 31, 0, 0, 0, 0);
      break;
    case TMatcherDateRange_YearToDate:
      fromTime=GWEN_Time_new(year, 0, 1, 0, 0, 0, 0);
      toTime=GWEN_Time_dup(cti);
      break;
    case TMatcherDateRange_LastMonth:
      tti=GWEN_Time_new(year, month, 1, 0, 0, 0, 0);
      secs=GWEN_Time_Seconds(tti);
      secs-=60*60*24;
      GWEN_Time_free(tti);
      toTime=GWEN_Time_fromSeconds(secs);
      GWEN_Time_GetBrokenDownDate(toTime, &tday, &tmonth, &tyear);
      fromTime=GWEN_Time_new(tyear, tmonth, 1, 0, 0, 0, 0);
      break;
    case TMatcherDateRange_LastYear:
      fromTime=GWEN_Time_new(year-1, 0, 1, 0, 0, 0, 0);
      toTime=GWEN_Time_new(year-1, 11, 31, 0, 0, 0, 0);
      break;
    case TMatcherDateRange_Last30Days:
      secs=GWEN_Time_Seconds(cti);
      secs-=60*60*24*30;
      fromTime=GWEN_Time_fromSeconds(secs);
      break;
    case TMatcherDateRange_Last3Months:
      secs=GWEN_Time_Seconds(cti);
      secs-=60*60*24*30*3;
      fromTime=GWEN_Time_fromSeconds(secs);
      break;
    case TMatcherDateRange_Last6Months:
      secs=GWEN_Time_Seconds(cti);
      secs-=60*60*24*30*6;
      fromTime=GWEN_Time_fromSeconds(secs);
      break;
    case TMatcherDateRange_Last12Months:
      secs=GWEN_Time_Seconds(cti);
      secs-=60*60*24*365;
      fromTime=GWEN_Time_fromSeconds(secs);
      break;
    case TMatcherDateRange_UserDefined:
      s=GWEN_DB_GetCharValue(dbT, "fromDate", 0, 0);
      if (s) {
        fromTime=GWEN_Time_fromString(s, "YYYY/MM/DD");
        assert(fromTime);
      }

      s=GWEN_DB_GetCharValue(dbT, "toDate", 0, 0);
      if (s) {
        toTime=GWEN_Time_fromString(s, "YYYY/MM/DD");
        assert(toTime);
      }
      break;
    default:
      DBG_ERROR(0, "Unknown date range type (%d)", mode);
      abort();
    } // switch
    GWEN_Time_free(cti);
  } // if date mentioned


  // read all matching transactions
  for (ait=_accounts.begin(); ait!=_accounts.end(); ait++) {
    bool accMatch;

    DBG_INFO(0, "Checking account");
    accMatch=false;
    if (allAccounts!=0)
      accMatch=true;
    else {
      dbT=GWEN_DB_GetGroup(dbData, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                           "account/selected");
      if (dbT) {
        DBG_DEBUG(0, "Have selected group...");
        dbT=GWEN_DB_FindFirstGroup(dbT, "account");
        while (dbT) {
          const char *accountId;
          const char *bankId;

          DBG_DEBUG(0, "Have account group...");
          bankId=GWEN_DB_GetCharValue(dbT, "bankId", 0, "");
          accountId=GWEN_DB_GetCharValue(dbT, "accountId", 0, "");

          DBG_DEBUG(0, "BankID: %s, AccountID: %s",
                     bankId, accountId);
          if (strcasecmp((*ait)->getBankCode().c_str(),
                         bankId)==0 &&
              strcasecmp((*ait)->getAccountNumber().c_str(),
                         accountId)==0) {
            accMatch=true;
            break;
          }
          dbT=GWEN_DB_FindNextGroup(dbT, "account");
        } // while
      } // if "account/selected"
    }

    if (accMatch) {
      std::list<RefPointer<Transaction> > tmptl;
      std::list<RefPointer<Transaction> >::iterator tit;

      DBG_DEBUG(0, "Account matches");
      if (1) {
        GWEN_BUFFER *tbuf;

        tbuf=GWEN_Buffer_new(0, 256, 0, 1);
        if (fromTime) {
          GWEN_Time_toString(fromTime, "YYYY/MM/DD", tbuf);
          DBG_DEBUG(0, "FromTime: %s", GWEN_Buffer_GetStart(tbuf));
          GWEN_Buffer_Reset(tbuf);
        }
        if (toTime) {
          GWEN_Time_toString(toTime, "YYYY/MM/DD", tbuf);
          DBG_DEBUG(0, "ToTime: %s", GWEN_Buffer_GetStart(tbuf));
          GWEN_Buffer_Reset(tbuf);
        }
        GWEN_Buffer_free(tbuf);
      }

      // load all transactions of this account
      DBG_DEBUG(0, "Loading transactions");
      if (!(*ait)->loadTransactions(fromTime, toTime, tmptl)) {
        DBG_ERROR(0, "Error loading transactions");
        GWEN_Time_free(fromTime);
        GWEN_Time_free(toTime);
        return false;
      }

      // copy matching transactions to final list
      DBG_DEBUG(0, "Copying transactions (%d)", tmptl.size());
      for (tit=tmptl.begin(); tit!=tmptl.end(); tit++) {
        DBG_DEBUG(0, "Checking transactions");
        if (_ruleAppliesToTransaction(dbRule, (*tit).ptr(), true)) {
          DBG_DEBUG(0, "Adding transaction");
          tl.push_back(*tit);
        }
      } // for
    } // if account matches
  } // for

  GWEN_Time_free(fromTime);
  GWEN_Time_free(toTime);
  return true;
}



int App::exportTransactions(GWEN_DB_NODE *dbRule,
                            const std::list<RefPointer<Transaction> > &tl){
  AB_IMEXPORTER_CONTEXT *ctx;
  std::list<RefPointer<Transaction> >::const_iterator it;
  AB_IMEXPORTER *exporter;
  const char *exporterName;
  GWEN_DB_NODE *dbProfile;
  int rv;
  GWEN_BUFFEREDIO *bio;
  const char *fname;
  int fd;
  GWEN_ERRORCODE err;
  GWEN_TYPE_UINT32 wid;

  /* get exporter name */
  exporterName=GWEN_DB_GetCharValue(dbRule, "exporterText", 0, 0);
  if (!exporterName) {
    DBG_ERROR(0, "No exporter set");
    return AB_ERROR_INVALID;
  }

  /* get profile */
  dbProfile=GWEN_DB_GetGroup(dbRule, GWEN_PATH_FLAGS_NAMEMUSTEXIST,
                             "profile");
  if (!dbProfile) {
    DBG_ERROR(0, "No export profile set");
    return AB_ERROR_INVALID;
  }

  /* get exporter */
  exporter=AB_Banking_GetImExporter(getCInterface(), exporterName);
  if (!exporter) {
    DBG_ERROR(0, "Exporter %s not found", exporterName);
    return AB_ERROR_INVALID;
  }

  /* check for file */
  fname=GWEN_DB_GetCharValue(dbRule, "outfileText", 0, 0);
  if (!fname) {
    DBG_ERROR(0, "No file name set");
    return AB_ERROR_INVALID;
  }

  /* open file */
  fd=open(fname,
          O_RDWR | O_TRUNC | O_CREAT,
#ifdef WIN32
              0
#else
              S_IRUSR | S_IWUSR
#endif
              );
  if (fd==-1) {
    DBG_ERROR(0, "open(%s): %s", fname, strerror(errno));
    return AB_ERROR_GENERIC;
  }
  bio=GWEN_BufferedIO_File_new(fd);
  GWEN_BufferedIO_SetWriteBuffer(bio, 0, 1024);

  /* add transactions to context */
  ctx=AB_ImExporterContext_new();
  for (it=tl.begin(); it!=tl.end(); it++) {
    AB_TRANSACTION *bt;

    bt=(*it).ref().toBankingTransaction();
    assert(bt);
    AB_ImExporterContext_AddTransaction(ctx, bt);
  } // for

  /* let exporter work */
  wid=AB_Banking_ProgressStart(getCInterface(),
                               "Exporting Transactions ...",
                               "The selected transactions are now being "
                               "exported, please wait...",
                               tl.size());
  rv=AB_ImExporter_Export(exporter, ctx, bio, dbProfile);
  AB_ImExporterContext_free(ctx);
  if (rv) {
    DBG_ERROR(0, "Export plugin signalled an error (%d)", rv);
    AB_Banking_ProgressLog(getCInterface(),
                           wid,
                           AB_Banking_LogLevelError,
                           "Export plugin signalled an error");
    GWEN_BufferedIO_Abandon(bio);
    GWEN_BufferedIO_free(bio);
  }
  else {
    err=GWEN_BufferedIO_Close(bio);
    if (!GWEN_Error_IsOk(err)) {
      DBG_ERROR_ERR(0, err);
      AB_Banking_ProgressLog(getCInterface(),
                             wid,
                             AB_Banking_LogLevelError,
                             "Error closing output stream");
      GWEN_BufferedIO_Abandon(bio);
      GWEN_BufferedIO_free(bio);
      return AB_ERROR_GENERIC;
    }
    AB_Banking_ProgressLog(getCInterface(),
                           wid,
                           AB_Banking_LogLevelNotice,
                           "Export completed.");
    GWEN_BufferedIO_free(bio);
  }
  AB_Banking_ProgressEnd(getCInterface(), wid);
  return rv;
}



Payee *App::findPayeeById(const char *id){
  std::list<Payee*>::iterator it;

  assert(id);
  assert(*id);
  for (it=_payees.begin(); it!=_payees.end(); it++) {
    if (strcasecmp(id, (*it)->id().c_str())==0)
      return *it;
  }

  return 0;
}



Payee *App::findPayeeByName(const char *name){
  std::list<Payee*>::iterator it;

  for (it=_payees.begin(); it!=_payees.end(); it++) {
    if (-1!=GWEN_Text_ComparePattern((*it)->name().c_str(), name, 0))
      return *it;
  }
  return 0;
}



Payee *App::findPayeeByTransactionMatch(const Transaction *t){
  std::list<Payee*>::iterator it;

  for (it=_payees.begin(); it!=_payees.end(); it++) {
    GWEN_DB_NODE *dbRule;

    dbRule=GWEN_DB_FindFirstGroup((*it)->rules(), "rule");
    while(dbRule) {
      if (ruleAppliesToTransaction(dbRule, t))
        return *it;
      dbRule=GWEN_DB_FindNextGroup(dbRule, "rule");
    }
  } // for payees
  return 0;
}



const std::list<Payee*> &App::getPayees(){
  return _payees;
}



bool App::addPayee(Payee *p){
  char numbuf[32];
  Payee *tp;
  int i;

  tp=0;
  for (i=0; i<100; i++) {

    snprintf(numbuf, sizeof(numbuf), "%d", ++_lastPayeeId);
    tp=findPayeeById(numbuf);
    if (!tp)
      break;
  }
  if (tp) {
    DBG_ERROR(0, "Could not add payee");
    return false;
  }

  p->setId(numbuf);
  _payees.push_back(p);
  if (savePayees()) {
    DBG_ERROR(0, "Could not save payees");
    return false;
  }
  return true;
}



bool App::removePayee(Payee *p){
  std::list<Payee*>::iterator it;

  for (it=_payees.begin(); it!=_payees.end(); it++) {
    if (*it==p) {
      _payees.erase(it);
      return true;
    }
  }

  return false;
}



bool App::optionAutoAssignPayee(){
  return _optionAutoAssignPayee;
}



void App::setOptionAutoAssignPayee(bool b){
  _optionAutoAssignPayee=b;
}



bool App::optionAutoAskForPayee(){
  return _optionAutoAskForPayee;
}



void App::setOptionAutoAskForPayee(bool b){
  _optionAutoAskForPayee=b;
}



GWEN_TIME *App::adjustedDate(const GWEN_TIME *pt){
  int hour, minute, sec;

  if (pt) {
    GWEN_Time_GetBrokenDownUtcTime(pt, &hour, &minute, &sec);
    if (hour!=12) {
      int day, month, year;

      GWEN_Time_GetBrokenDownUtcDate(pt, &day, &month, &year);
      if (hour==22 || hour==23) {
        GWEN_TIME *nt;

        nt=GWEN_Time_fromSeconds(GWEN_Time_Seconds(pt)+(24*60*60));
        GWEN_Time_GetBrokenDownUtcDate(nt, &day, &month, &year);
        GWEN_Time_free(nt);
      }

      return GWEN_Time_new(year, month, day, 12, 0, 0, 1);
    }
    return GWEN_Time_dup(pt);
  }

  return 0;
}



GWEN_TYPE_UINT32 App::getLastVersion() const {
  return _lastVersion;
}



GWEN_TYPE_UINT32 App::getFuzzyThreshold() const {
  return _fuzzyThreshold;
}



void App::setFuzzyThreshold(GWEN_TYPE_UINT32 i) {
  _fuzzyThreshold=i;
}



GWEN_TYPE_UINT32 App::getSensitiveFuzzyThreshold() const {
  return _sensitiveFuzzyThreshold;
}



void App::setSensitiveFuzzyThreshold(GWEN_TYPE_UINT32 i) {
  _sensitiveFuzzyThreshold=i;
}



TEXTMATCHER_OPERATION App::string2TextMatcherOperation(const char *s) {
  assert(s);
  if (strcasecmp(s, "equals")==0)
    return TextMatcherOperation_Equals;
  else if (strcasecmp(s, "equalsNot")==0)
    return TextMatcherOperation_EqualsNot;
  else if (strcasecmp(s, "contains")==0)
    return TextMatcherOperation_Contains;
  else if (strcasecmp(s, "containsNot")==0)
    return TextMatcherOperation_ContainsNot;
  else if (strcasecmp(s, "matchesRegExp")==0)
    return TextMatcherOperation_MatchesRegExp;
  else if (strcasecmp(s, "matchesRegExpNot")==0)
    return TextMatcherOperation_MatchesRegExpNot;
  else
    return TextMatcherOperation_Unknown;
}



const char *App::textMatcherOperation2String(TEXTMATCHER_OPERATION op) {
  switch(op) {
  case TextMatcherOperation_Equals:
    return "equals";
  case TextMatcherOperation_EqualsNot:
    return "equalsNot";
  case TextMatcherOperation_Contains:
    return "contains";
  case TextMatcherOperation_ContainsNot:
    return "containsNot";
  case TextMatcherOperation_MatchesRegExp:
    return "matchesRegExp";
  case TextMatcherOperation_MatchesRegExpNot:
    return "matchesRegExpNot";
  default:
    return "unknown";
  }
}



const std::list<Category*> &App::getCategories() {
  return _categories;
}



bool App::addCategory(Category *cat, Category *parentCat) {
  char numbuf[32];
  Category *tc;
  int i;

  tc=0;
  for (i=0; i<100; i++) {

    snprintf(numbuf, sizeof(numbuf), "%d", ++_lastCategoryId);
    tc=findCategoryById(numbuf);
    if (!tc)
      break;
  }
  if (tc) {
    DBG_ERROR(0, "Could not add category");
    return false;
  }

  cat->setId(numbuf);
  if (parentCat)
    parentCat->addCategory(cat);
  else
    _categories.push_back(cat);
  if (saveCategories()) {
    DBG_ERROR(0, "Could not save category");
    return false;
  }
  return true;
}



bool App::removeCategory(Category *cat) {
  Category *parentCat;

  parentCat=cat->getParent();
  if (parentCat)
    cat->unlink();
  else {
    std::list<Category*>::iterator it;

    for (it=_categories.begin(); it!=_categories.end(); it++) {
      if (*it==cat) {
        _categories.erase(it);
        return true;
      }
    }
  }

  return false;

}



void App::reparentCategory(Category *cat, Category *newParent) {
  if (cat->getParent())
    cat->unlink();
  else
    removeCategory(cat);

  if (newParent)
    newParent->addCategory(cat);
  else
    _categories.push_back(cat);
}


Category *App::_findCategoryById(Category *where, const char *id){
  std::list<Category*>::const_iterator it;

  if (strcasecmp(id, where->getId().c_str())==0)
    return where;
  for (it=where->getChildren().begin();
       it!=where->getChildren().end();
       it++) {
    Category *cat;

    cat=_findCategoryById(*it, id);
    if (cat)
      return cat;
  }
  return 0;
}



Category *App::findCategoryById(const char *id){
  std::list<Category*>::iterator it;

  assert(id);
  assert(*id);
  for (it=_categories.begin(); it!=_categories.end(); it++) {
    Category *cat;

    cat=_findCategoryById(*it, id);
    if (cat)
      return cat;
  }

  return 0;
}



Category *App::_findCategoryByName(Category *where, const char *name){
  std::list<Category*>::iterator it;

  if (-1!=GWEN_Text_ComparePattern(where->getName().c_str(), name, 0))
    return where;
  for (it=where->getChildren().begin();
       it!=where->getChildren().end();
       it++) {
    Category *cat;

    cat=_findCategoryByName(*it, name);
    if (cat)
      return cat;
  }
  return 0;
}



Category *App::findCategoryByName(const char *name){
  std::list<Category*>::iterator it;

  assert(name);
  for (it=_categories.begin(); it!=_categories.end(); it++) {
    Category *cat;

    cat=_findCategoryByName(*it, name);
    if (cat)
      return cat;
  }

  return 0;
}



Category *App::_findCategoryByTransactionMatch(Category *where,
                                               const Transaction *t){
  std::list<Category*>::const_iterator it;
  GWEN_DB_NODE *dbRule;

  dbRule=GWEN_DB_FindFirstGroup(where->getRules(), "rule");
  while(dbRule) {
    if (ruleAppliesToTransaction(dbRule, t))
      return where;
    dbRule=GWEN_DB_FindNextGroup(dbRule, "rule");
  }

  for (it=where->getChildren().begin();
       it!=where->getChildren().end();
       it++) {
    Category *cat;

    cat=_findCategoryByTransactionMatch(*it, t);
    if (cat)
      return cat;
  }

  return 0;
}



Category *App::findCategoryByTransactionMatch(const Transaction *t){
  std::list<Category*>::iterator it;

  for (it=_categories.begin(); it!=_categories.end(); it++) {
    Category *cat;

    cat=_findCategoryByTransactionMatch(*it, t);
    if (cat)
      return cat;
  } // for categories
  return 0;
}



static int _compareCategories(const void *p1, const void *p2) {
  Category *c1;
  Category *c2;

  c1=*((Category**)p1);
  c2=*((Category**)p2);
  return (strcasecmp(c1->getPath().c_str(), c2->getPath().c_str()));
}



void App::_sortCategories(std::list<Category*> &cl){
  std::list<Category*>::iterator it;

  if (!cl.empty()) {
    Category **catArray;
    Category **p;
    int i;

    i=cl.size();
    catArray=(Category**)malloc((i+1)*sizeof(Category*));
    assert(catArray);

    p=catArray;
    for (it=cl.begin(); it!=cl.end(); it++)
      *(p++)=(*it);
    *p=0;
    cl.clear();

    qsort(catArray, i, sizeof(Category*),
          _compareCategories);
    p=catArray;
    while(i--) {
      cl.push_back(*(p++));
    }
    free(catArray);
  }

  for (it=cl.begin(); it!=cl.end(); it++)
    _sortCategories((*it)->getChildren());
}



void App::sortCategories(){
  _sortCategories(_categories);
}



std::string App::tr(const char *s) {
  return s;
}


















/* the following algorithm has been copied and adapted from fuzzy.c of
 * gtk-gnutella (Copyright (c) 2002, Vidar Madsen).
 * Gtk-Gnutella is licensed under the GPL.
 */

GWEN_TYPE_UINT32 App__word_similarity(const char *a, const char *b){
  GWEN_TYPE_UINT32 score = 0;
  size_t l = 0;

  while (*a && *b) {
    if (*a == *b)
      score += 1 << APP_FUZZY_SHIFT;
    else if (*a == b[1]) {
      score += 1 << (APP_FUZZY_SHIFT-2); b++;
    }
    else if (a[1] == *b) {
      score += 1 << (APP_FUZZY_SHIFT-2); a++; l++;
    }
    a++;
    b++;
    l++;
  }

  if ('\0' != *a)
    l += strlen(a);

  return score / l;
}



int App__is_ascii_alnum(unsigned char c) {
  return ((c>='A' || c<='Z') ||
        (c>='a' || c<='z') ||
        (c>='0' || c<='9') ||
        c>=128);
}



GWEN_STRINGLIST *App__make_word_list(const char *s) {
  GWEN_STRINGLIST *sl;
  const char *str;

  str=s;
  sl=GWEN_StringList_new();
  while(*str) {
    const char *p;
    size_t size;

    while (*str && !App__is_ascii_alnum(*str))
      str++;
    p=str;
    while (*str && App__is_ascii_alnum(*str)) {
      str++;
    }

    size=(str-p)+1;
    if (*p) {
      char *wptr;
      char *t;
      size_t i;

      wptr=(char*)malloc(size);
      assert(wptr);
      t=wptr;
      for (i=0; i<size-1; i++)
      *(t++)=tolower(*(p++));
      *t=0;
      GWEN_StringList_AppendString(sl, wptr, 1, 0); /* take this string */
    }
  } /* while */

  return sl;
}



GWEN_TYPE_UINT32 App__cmp_word_list(const char *s,
                                    GWEN_STRINGLIST *words) {
  GWEN_TYPE_UINT32 score = 0;
  GWEN_TYPE_UINT32 n = 0;
  GWEN_STRINGLISTENTRY *se;

  se=GWEN_StringList_FirstEntry(words);
  while(se) {
    const char *p;

    n++;
    p=GWEN_StringListEntry_Data(se);
    assert(p);

    if (strcmp(s, p)==0)
      return 1 << APP_FUZZY_SHIFT;
    else
      score+=App__word_similarity(s, p);
    se=GWEN_StringListEntry_Next(se);
  }

  return (n>0)?(score/n):0;
}



GWEN_TYPE_UINT32 App__find_score(GWEN_STRINGLIST *a,
                               GWEN_STRINGLIST *b) {
  GWEN_TYPE_UINT32 score = 0;
  GWEN_TYPE_UINT32 n = 0;
  GWEN_STRINGLISTENTRY *se;

  se=GWEN_StringList_FirstEntry(a);
  while(se) {
    const char *p;

    n++;
    p=GWEN_StringListEntry_Data(se);
    assert(p);

    score+=App__cmp_word_list(p, b);

    se=GWEN_StringListEntry_Next(se);
  }

  return (n>0)?(score/n):0;
}



int App__convertFromUtf8(const char *text,
                         int len,
                         GWEN_BUFFER *tbuf){
#ifndef HAVE_ICONV_H
  DBG_ERROR(0,
           "iconv not available, can not convert to \"%s\"",
           "latin1");
#else
  iconv_t ic;

  if (len==0)
    return 0;

  ic=iconv_open("latin1", "UTF-8");
  if (ic==((iconv_t)-1)) {
    DBG_INFO(AQBANKING_LOGDOMAIN, "Charset \"%s\" not available",
             "latin1");
  }
  else {
    char *outbuf;
    char *pOutbuf;
    char *pInbuf;
    size_t inLeft;
    size_t outLeft;
    size_t done;
    size_t space;

    /* convert */
    pInbuf=(char*)text;

    outLeft=len*2;
    space=outLeft;
    outbuf=(char*)malloc(outLeft);
    assert(outbuf);

    inLeft=len;
    pInbuf=(char*)text;
    pOutbuf=outbuf;
    done=iconv(ic, &pInbuf, &inLeft, &pOutbuf, &outLeft);
    if (done==(size_t)-1) {
      DBG_ERROR(0, "Error in conversion: %s (%d)",
                strerror(errno), errno);
      free(outbuf);
      iconv_close(ic);
    }
    else {
      GWEN_Buffer_AppendBytes(tbuf, outbuf, space-outLeft);
      if (done) {
        DBG_INFO(0, "Unrevertable: [%s] -> [%s] (%d)",
                 text,
                 GWEN_Buffer_GetStart(tbuf), done);
      }
      free(outbuf);
      DBG_DEBUG(AQBANKING_LOGDOMAIN, "Conversion done.");
      iconv_close(ic);
      return 0;
    }
  }
#endif

  GWEN_Buffer_AppendString(tbuf, text);
  return 0;
}



GWEN_TYPE_UINT32 App_FuzzyCompare(const char *str1, const char *str2) {
  GWEN_STRINGLIST *a;
  GWEN_STRINGLIST *b;
  GWEN_TYPE_UINT32 score;
  unsigned int l1, l2;
  GWEN_BUFFER *buf1;
  GWEN_BUFFER *buf2;

  l1=strlen(str1);
  l2=strlen(str2);
  buf1=GWEN_Buffer_new(0, 1+l1+l1/4, 0, 1);
  buf2=GWEN_Buffer_new(0, 1+l2+l2/4, 0, 1);

  App__convertFromUtf8(str1, l1, buf1);
  App__convertFromUtf8(str2, l2, buf2);

  a=App__make_word_list(GWEN_Buffer_GetStart(buf1));
  b=App__make_word_list(GWEN_Buffer_GetStart(buf2));
  GWEN_Buffer_free(buf2);
  GWEN_Buffer_free(buf1);

  score=(App__find_score(a, b) + App__find_score(b, a))/2;

  GWEN_StringList_free(b);
  GWEN_StringList_free(a);

  return score;
}





Generated by  Doxygen 1.6.0   Back to index