* src/import-export/qif: an incomplete implementation of a new

(written-in-C) QIF importer.  The code compiles but has not
	  been tested.  Yet to do: merging, conversion to gnc, and UI.


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@8873 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Derek Atkins 2003-07-15 00:27:39 +00:00
parent 55d30ac378
commit 319dd4d7aa
17 changed files with 3024 additions and 2 deletions

View File

@ -1,3 +1,9 @@
2003-07-14 Derek Atkins <derek@ihtfp.com>
* src/import-export/qif: an incomplete implementation of a new
(written-in-C) QIF importer. The code compiles but has not
been tested. Yet to do: merging, conversion to gnc, and UI.
2003-07-10 Derek Atkins <derek@ihtfp.com> 2003-07-10 Derek Atkins <derek@ihtfp.com>
* src/import-export/import-parse.[ch]: routines to parse numbers * src/import-export/import-parse.[ch]: routines to parse numbers

View File

@ -1193,12 +1193,14 @@ AC_OUTPUT( m4/Makefile intl/Makefile po/Makefile.in
src/import-export/binary-import/Makefile src/import-export/binary-import/Makefile
src/import-export/binary-import/test/Makefile src/import-export/binary-import/test/Makefile
src/import-export/qif-import/Makefile src/import-export/qif-import/Makefile
src/import-export/qif/Makefile
src/import-export/qif/test/Makefile
src/import-export/qif-import/test/Makefile src/import-export/qif-import/test/Makefile
src/import-export/qif-io-core/Makefile src/import-export/qif-io-core/Makefile
src/import-export/qif-io-core/test/Makefile src/import-export/qif-io-core/test/Makefile
src/import-export/ofx/Makefile src/import-export/ofx/Makefile
src/import-export/ofx/test/Makefile src/import-export/ofx/test/Makefile
src/import-export/log-replay/Makefile src/import-export/log-replay/Makefile
src/import-export/hbci/Makefile src/import-export/hbci/Makefile
src/import-export/hbci/glade/Makefile src/import-export/hbci/glade/Makefile
src/import-export/hbci/test/Makefile src/import-export/hbci/test/Makefile

View File

@ -1,4 +1,4 @@
SUBDIRS = . binary-import qif-import ${OFX_DIR} ${HBCI_DIR} test log-replay SUBDIRS = . binary-import qif qif-import ${OFX_DIR} ${HBCI_DIR} test log-replay
pkglib_LTLIBRARIES=libgncmod-generic-import.la pkglib_LTLIBRARIES=libgncmod-generic-import.la

View File

@ -0,0 +1,31 @@
SUBDIRS = . test
pkglib_LTLIBRARIES=libgncmod-qif.la
libgncmod_qif_la_SOURCES = \
qif-context.c \
qif-defaults.c \
qif-file.c \
qif-objects.c \
qif-parse.c
noinst_HEADERS = \
qif-file.h \
qif-defaults.h \
qif-import-p.h \
qif-import.h \
qif-objects.h \
qif-objects-p.h \
qif-parse.h
libgncmod_qif_la_LDFLAGS = -module
libgncmod_qif_la_LIBADD =
AM_CFLAGS = \
-I${top_srcdir}/src \
-I${top_srcdir}/src/engine \
-I${top_srcdir}/src/gnc-module \
-I${top_srcdir}/src/app-utils \
-I${top_srcdir}/src/import-export \
${GLIB_CFLAGS}

View File

@ -0,0 +1,262 @@
/*
* qif-context.c -- create/destroy QIF Contexts
*
* Written By: Derek Atkins <derek@ihtfp.com>
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib.h>
#include "qif-import-p.h"
QifContext
qif_context_new(QifContext parent)
{
QifContext ctx = g_new0(struct _QifContext, 1);
if (parent)
ctx->parent = parent;
ctx->object_lists = g_hash_table_new(g_str_hash, g_str_equal);
ctx->object_maps = g_hash_table_new(g_str_hash, g_str_equal);
/* we should assume that we've got a bank account... just in case.. */
qif_parse_bangtype(ctx, "!type:bank");
/* Return the new context */
return ctx;
}
void
qif_context_destroy(QifContext ctx)
{
/* force the end of record */
if (ctx->handler && ctx->handler->end)
ctx->handler->end(ctx);
/* destroy the state objects */
qif_object_list_destroy(ctx);
qif_object_map_destroy(ctx);
g_free(ctx);
}
/*****************************************************************************/
/*
* Insert and remove a QifObject from the Object Maps in this Qif Context
*/
void
qif_object_map_foreach(QifContext ctx, const char *type, GHFunc func, gpointer arg)
{
GHashTable *ht;
g_return_if_fail(ctx);
g_return_if_fail(ctx->object_maps);
g_return_if_fail(type);
ht = g_hash_table_lookup(ctx->object_maps, type);
if (ht)
g_hash_table_foreach(ht, func, arg);
}
void
qif_object_map_insert(QifContext ctx, const char *key, QifObject obj)
{
GHashTable *ht;
g_return_if_fail(ctx);
g_return_if_fail(ctx->object_maps);
g_return_if_fail(key);
g_return_if_fail(obj);
g_return_if_fail(obj->type);
ht = g_hash_table_lookup(ctx->object_maps, obj->type);
if (!ht) {
ht = g_hash_table_new(g_str_hash, g_str_equal);
g_assert(ht);
g_hash_table_insert(ctx->object_maps, (gpointer)obj->type, ht);
}
g_hash_table_insert(ht, (gpointer)key, obj);
}
void
qif_object_map_remove(QifContext ctx, const char *type, const char *key)
{
GHashTable *ht;
g_return_if_fail(ctx);
g_return_if_fail(ctx->object_maps);
g_return_if_fail(type);
g_return_if_fail(key);
ht = g_hash_table_lookup(ctx->object_maps, type);
if (!ht) return;
g_hash_table_remove(ht, key);
}
QifObject
qif_object_map_lookup(QifContext ctx, const char *type, const char *key)
{
GHashTable *ht;
g_return_val_if_fail(ctx, NULL);
g_return_val_if_fail(ctx->object_maps, NULL);
g_return_val_if_fail(type, NULL);
g_return_val_if_fail(key, NULL);
ht = g_hash_table_lookup(ctx->object_maps, type);
if (!ht) return NULL;
return g_hash_table_lookup(ht, key);
}
/* This GList _SHOULD_ be freed by the caller */
static void
qif_object_map_get_helper(gpointer key, gpointer value, gpointer arg)
{
GList **listp = arg;
g_return_if_fail(listp);
*listp = g_list_prepend(*listp, value);
}
GList *
qif_object_map_get(QifContext ctx, const char *type)
{
GHashTable *ht;
GList *list = NULL;
g_return_val_if_fail(ctx, NULL);
g_return_val_if_fail(ctx->object_maps, NULL);
g_return_val_if_fail(type, NULL);
ht = g_hash_table_lookup(ctx->object_maps, type);
if (!ht)
return NULL;
g_hash_table_foreach(ht, qif_object_map_get_helper, &list);
return list;
}
static gboolean
qif_object_map_remove_each(gpointer key, gpointer value, gpointer arg)
{
QifObject obj = value;
obj->destroy(obj);
return TRUE;
}
static gboolean
qif_object_map_remove_all(gpointer key, gpointer value, gpointer arg)
{
GHashTable *ht = value;
g_hash_table_foreach_remove(ht, qif_object_map_remove_each, NULL);
g_hash_table_destroy(ht);
return TRUE;
}
void qif_object_map_destroy(QifContext ctx)
{
g_return_if_fail(ctx);
g_return_if_fail(ctx->object_maps);
g_hash_table_foreach_remove(ctx->object_lists, qif_object_map_remove_all, NULL);
g_hash_table_destroy(ctx->object_lists);
}
/*****************************************************************************/
/*
* Insert and remove a QifObject from the Object Lists in this Qif Context
*/
void
qif_object_list_foreach(QifContext ctx, const char *type, GFunc func, gpointer arg)
{
GList *list;
g_return_if_fail(ctx);
g_return_if_fail(ctx->object_lists);
g_return_if_fail(type);
list = qif_object_list_get(ctx, type);
g_list_foreach(list, func, arg);
}
void
qif_object_list_insert(QifContext ctx, QifObject obj)
{
GList *list;
g_return_if_fail(ctx);
g_return_if_fail(ctx->object_lists);
g_return_if_fail(obj);
g_return_if_fail(obj->type && *obj->type);
list = g_hash_table_lookup(ctx->object_lists, obj->type);
list = g_list_prepend(list, obj);
g_hash_table_insert(ctx->object_lists, (gpointer)obj->type, list);
}
void
qif_object_list_remove(QifContext ctx, QifObject obj)
{
GList *list;
g_return_if_fail(ctx);
g_return_if_fail(ctx->object_lists);
g_return_if_fail(obj);
g_return_if_fail(obj->type && *obj->type);
list = g_hash_table_lookup(ctx->object_lists, obj->type);
list = g_list_remove(list, obj);
g_hash_table_insert(ctx->object_lists, (gpointer)obj->type, list);
}
GList *
qif_object_list_get(QifContext ctx, const char *type)
{
g_return_val_if_fail(ctx, NULL);
g_return_val_if_fail(ctx->object_lists, NULL);
g_return_val_if_fail(type, NULL);
return g_hash_table_lookup(ctx->object_lists, type);
}
static gboolean
qif_object_list_remove_all(gpointer key, gpointer value, gpointer arg)
{
GList *list = value;
GList *node;
QifObject obj;
for (node = list; node; node = node->next) {
obj = node->data;
obj->destroy(obj);
}
g_list_free(list);
return TRUE;
}
void
qif_object_list_destroy(QifContext ctx)
{
g_return_if_fail(ctx);
g_return_if_fail(ctx->object_lists);
g_hash_table_foreach_remove(ctx->object_lists, qif_object_list_remove_all, NULL);
g_hash_table_destroy(ctx->object_lists);
}

View File

@ -0,0 +1,136 @@
/*
* qif-defaults.c -- QIF Defaults -- default accounts...
*
* Created by: Derek Atkins <derek@ihtfp.com>
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib.h>
#include "gnc-helpers.h"
#include "messages.h"
#include "qif-import-p.h"
#include "qif-objects-p.h"
#include "qif-defaults.h"
static GList *stock_list = NULL;
static GList *ext_stock_list = NULL;
static GList *income_list = NULL;
static GList *expense_list = NULL;
static GList *equity_list = NULL;
#define RETURN_ACCT(c,n,l) { if (stock_list == NULL) acct_type_init(); \
return find_or_make_acct(c, n, l); \
}
static void
acct_type_init(void)
{
stock_list = qif_parse_acct_type("__stock__", -1);
ext_stock_list = qif_parse_acct_type("__extstock__", -1);
income_list = qif_parse_acct_type("__income__", -1);
expense_list = qif_parse_acct_type("__expense__", -1);
equity_list = qif_parse_acct_type("__equity__", -1);
}
QifAccount qif_default_equity_acct(QifContext ctx)
{
char *name = g_strdup(_("Retained Earnings"));
RETURN_ACCT(ctx, name, equity_list);
}
QifAccount qif_default_margin_interest_acct(QifContext ctx)
{
char *name = g_strdup_printf("%s%s%s", _("Margin Interest"),
gnc_get_account_separator_string(),
ctx->current_acct->name);
RETURN_ACCT(ctx, name, expense_list);
}
QifAccount qif_default_commission_acct(QifContext ctx)
{
char *name = g_strdup_printf("%s%s%s", _("Commissions"),
gnc_get_account_separator_string(),
ctx->current_acct->name);
RETURN_ACCT(ctx, name, expense_list);
}
QifAccount qif_default_stock_acct(QifContext ctx, const char *security)
{
char *name = g_strdup_printf("%s%s%s", ctx->current_acct->name,
gnc_get_account_separator_string(),
security);
RETURN_ACCT(ctx, name, stock_list);
}
QifAccount qif_default_cglong_acct(QifContext ctx, const char *security)
{
char *name = g_strdup_printf("%s%s%s%s%s", _("Cap. gain (long)"),
gnc_get_account_separator_string(),
ctx->current_acct->name,
gnc_get_account_separator_string(),
security);
RETURN_ACCT(ctx, name, income_list);
}
QifAccount qif_default_cgmid_acct(QifContext ctx, const char *security)
{
char *name = g_strdup_printf("%s%s%s%s%s", _("Cap. gain (mid)"),
gnc_get_account_separator_string(),
ctx->current_acct->name,
gnc_get_account_separator_string(),
security);
RETURN_ACCT(ctx, name, income_list);
}
QifAccount qif_default_cgshort_acct(QifContext ctx, const char *security)
{
char *name = g_strdup_printf("%s%s%s%s%s", _("Cap. gain (short)"),
gnc_get_account_separator_string(),
ctx->current_acct->name,
gnc_get_account_separator_string(),
security);
RETURN_ACCT(ctx, name, income_list);
}
QifAccount qif_default_dividend_acct(QifContext ctx, const char *security)
{
char *name = g_strdup_printf("%s%s%s%s%s", _("Dividends"),
gnc_get_account_separator_string(),
ctx->current_acct->name,
gnc_get_account_separator_string(),
security);
RETURN_ACCT(ctx, name, income_list);
}
QifAccount qif_default_interest_acct(QifContext ctx, const char *security)
{
char *name = g_strdup_printf("%s%s%s%s%s", _("Interest"),
gnc_get_account_separator_string(),
ctx->current_acct->name,
gnc_get_account_separator_string(),
security);
RETURN_ACCT(ctx, name, income_list);
}
QifAccount qif_default_capital_return_acct(QifContext ctx, const char *security)
{
char *name = g_strdup_printf("%s%s%s%s%s", _("Cap Return"),
gnc_get_account_separator_string(),
ctx->current_acct->name,
gnc_get_account_separator_string(),
security);
RETURN_ACCT(ctx, name, income_list);
}
QifAccount qif_default_equity_holding(QifContext ctx, const char *security)
{
return qif_default_equity_acct(ctx);
}

View File

@ -0,0 +1,27 @@
/*
* qif-defaults.h -- QIF Defaults -- default accounts...
*
* Created by: Derek Atkins <derek@ihtfp.com>
*
*/
#ifndef QIF_DEFAULTS_H
#define QIF_DEFAULTS_H
#include "qif-objects.h"
#include "qif-import.h"
QifAccount qif_default_equity_acct(QifContext ctx);
QifAccount qif_default_equity_holding(QifContext ctx, const char *security);
QifAccount qif_default_margin_interest_acct(QifContext ctx);
QifAccount qif_default_commission_acct(QifContext ctx);
QifAccount qif_default_stock_acct(QifContext ctx, const char *security);
QifAccount qif_default_cglong_acct(QifContext ctx, const char *security);
QifAccount qif_default_cgmid_acct(QifContext ctx, const char *security);
QifAccount qif_default_cgshort_acct(QifContext ctx, const char *security);
QifAccount qif_default_dividend_acct(QifContext ctx, const char *security);
QifAccount qif_default_interest_acct(QifContext ctx, const char *security);
QifAccount qif_default_capital_return_acct(QifContext ctx, const char *security);
#endif /* QIF_DEFAULTS_H */

View File

@ -0,0 +1,175 @@
/*
* qif-file.c -- parse a QIF File into its pieces
*
* Written by: Derek Atkins <derek@@ihtfp.com>
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib.h>
#include <stdio.h>
#include <string.h>
#include "gnc-engine-util.h"
#include "qif-import-p.h"
static short module = MOD_IMPORT;
static QifLine
qif_make_line(const char* buf, gint lineno)
{
QifLine line;
g_return_val_if_fail(buf && *buf, NULL);
line = g_new0(struct _QifLine, 1);
line->type = *buf;
line->lineno = lineno;
line->line = g_strdup(buf+1);
return line;
}
void
qif_record_destroy(GList *record)
{
GList *node;
QifLine line;
for (node = record; node; node = node->next) {
line = node->data;
g_free(line->line);
g_free(line);
}
g_list_free(record);
}
/* This returns a record, which is a bunch of QifLines, ending
* with a line with just a '^'. If it finds a line that begins
* with a !, then destroy the current record state, set the "found_bangtype",
* and return NULL.
*/
static GList *
qif_make_record(QifContext ctx, char *buf, size_t bufsiz, gboolean *found_bangtype)
{
GList *record = NULL;
QifLine line;
g_return_val_if_fail(ctx, NULL);
g_return_val_if_fail(buf, NULL);
g_return_val_if_fail(found_bangtype, NULL);
*found_bangtype = FALSE;
while (fgets(buf, bufsiz, ctx->fp) != NULL) {
/* increment the line number */
ctx->lineno++;
/* strip start/end whitespace */
g_strstrip(buf);
/* if there is nothing left in the string, ignore it */
if (strlen(buf) == 0)
continue;
/* If this is a bangline, then set the flag, clear our state, and return NULL */
if (*buf == '!') {
*found_bangtype = TRUE;
break;
}
/* See if this is an End of Record marker */
if (*buf == '^') {
/* Yep. If we've got a record then break and return ... */
if (record)
break;
/* ... otherwise just continue reading (i.e. ignore empty records) */
else
continue;
}
/* otherwise, add the line to the list */
line = qif_make_line(buf, ctx->lineno);
if (line)
record = g_list_prepend(record, line);
/* and continue... */
}
/* If we found a bangtype, destroy anything we've collected */
if (*found_bangtype) {
if (record)
PERR("error loading file: incomplete record at line %d", ctx->lineno);
qif_record_destroy(record);
record = NULL;
}
return g_list_reverse(record);
}
/* read a qif file and parse it, line by line
* return FALSE on fatal error, TRUE otherwise
*/
QifError
qif_read_file(QifContext ctx, FILE *f)
{
char buf[BUFSIZ];
GList *record;
gboolean found_bang;
QifError err = QIF_E_OK;
g_return_val_if_fail(ctx, FALSE);
g_return_val_if_fail(f, FALSE);
ctx->fp = f;
ctx->lineno = -1;
do {
found_bang = FALSE;
record = qif_make_record(ctx, buf, sizeof(buf), &found_bang);
/* If we got a record, process it */
if (record) {
if (!ctx->handler || !ctx->handler->parse_record) {
PERR("Trying to process QIF record without a handler at %d", ctx->lineno);
} else {
err = ctx->handler->parse_record(ctx, record);
}
/* Now destroy it; we don't need it anymore */
qif_record_destroy(record);
}
/* if we found a bangtype, process that */
if (found_bang) {
g_assert(*buf == '!');
/* First, process the end of the last handler. This could possibly
* merge items into the context or perform some other operation
*/
if (ctx->handler && ctx->handler->end) {
err = ctx->handler->end(ctx);
if (err != QIF_E_OK)
break;
}
/* Now process the bangtype (stored in buf) to set the new handler */
qif_parse_bangtype(ctx, buf);
}
} while ((record || found_bang) && err == QIF_E_OK);
/* Make sure to run any end processor */
if (err == QIF_E_OK && ctx->handler && ctx->handler->end)
err = ctx->handler->end(ctx);
return err;
}

View File

@ -0,0 +1,18 @@
/* qif-import-p.h -- a QIF Importer module (private headers)
*
* Written By: Derek Atkins <derek@ihtfp.com>
*
*/
#ifndef QIF_FILE_H
#define QIF_FILE_H
struct _QifLine {
char type;
gint lineno;
char * line;
};
void qif_record_destroy(GList *record);
#endif /* QIF_FILE_H */

View File

@ -0,0 +1,71 @@
/* qif-import-p.h -- a QIF Importer module (private headers)
*
* Written By: Derek Atkins <derek@ihtfp.com>
*
*/
#ifndef QIF_IMPORT_P_H
#define QIF_IMPORT_P_H
#include "qif-import.h"
#include "qif-objects.h"
#include "qif-parse.h"
#include "qif-file.h"
#include <stdio.h>
struct _QifHandler {
void (*init)(QifContext ctx);
QifError (*parse_record)(QifContext ctx, GList *record);
QifError (*end)(QifContext ctx);
};
struct _QifContext {
/* The parent context */
QifContext parent;
/* file information */
FILE * fp;
gint lineno;
/* This describes what we are parsing right now */
QifType parse_type;
QifHandler handler;
/* A bunch of flags for the current handler */
gint parse_flags;
/* The current and last seen account */
QifAccount current_acct;
QifAccount last_seen_acct;
/* Current parse state */
QifObject parse_state;
/* HashTable of Lists of data objects */
GHashTable * object_lists;
/* HashTable of Maps of data objects */
GHashTable * object_maps;
};
/* Object Maps */
void qif_object_map_foreach(QifContext ctx, const char *type,
GHFunc func, gpointer arg);
void qif_object_map_insert(QifContext ctx, const char *key, QifObject obj);
void qif_object_map_remove(QifContext ctx, const char *type, const char *key);
QifObject qif_object_map_lookup(QifContext ctx, const char *type, const char *key);
void qif_object_map_destroy(QifContext ctx);
/* GList _SHOULD_ be freed by the caller */
GList * qif_object_map_get(QifContext ctx, const char *type);
/* Object Lists */
void qif_object_list_foreach(QifContext ctx, const char *type,
GFunc func, gpointer arg);
void qif_object_list_insert(QifContext ctx, QifObject obj);
void qif_object_list_remove(QifContext ctx, QifObject obj);
void qif_object_list_destroy(QifContext ctx);
/* GList should NOT be freed by the caller */
GList *qif_object_list_get(QifContext ctx, const char *type);
#endif /* QIF_IMPORT_P_H */

View File

@ -0,0 +1,113 @@
/*
* qif-import.h -- a QIF Import module
*
* Written By: Derek Atkins <derek@ihtfp.com>
*
*/
#ifndef QIF_IMPORT_H
#define QIF_IMPORT_H
#include <stdio.h>
#include "gnc-numeric.h"
typedef enum {
QIF_TYPE_BANK,
QIF_TYPE_CASH,
QIF_TYPE_CCARD,
QIF_TYPE_INVST,
QIF_TYPE_PORT,
QIF_TYPE_OTH_A,
QIF_TYPE_OTH_L,
QIF_TYPE_CLASS,
QIF_TYPE_CAT,
QIF_TYPE_SECURITY,
QIF_ACCOUNT,
QIF_AUTOSWITCH,
QIF_CLEAR_AUTOSWITCH
} QifType;
/* Make sure this patches */
#define QIF_TYPE_MAX QIF_CLEAR_AUTOSWITCH
typedef struct _QifHandler *QifHandler;
typedef struct _QifContext *QifContext;
typedef struct _QifLine *QifLine;
/* Qif Flags */
#define QIF_F_IGNORE_ACCOUNTS (1 << 0)
#define QIF_F_TXN_NEEDS_ACCT (1 << 1)
/* Qif Reconciled Flag */
typedef enum {
QIF_R_NO = 0,
QIF_R_CLEARED,
QIF_R_RECONCILED,
QIF_R_BUDGETED,
} QifRecnFlag;
/* Qif Errors */
typedef enum {
QIF_E_OK = 0,
QIF_E_INTERNAL,
QIF_E_BADSTATE,
} QifError;
/* Qif (investment?) Actions */
typedef enum {
QIF_A_NONE = 0,
QIF_A_BUY,
QIF_A_BUYX,
QIF_A_CGLONG,
QIF_A_CGLONGX,
QIF_A_CGMID,
QIF_A_CGMIDX,
QIF_A_CGSHORT,
QIF_A_CGSHORTX,
QIF_A_DIV,
QIF_A_DIVX,
QIF_A_EXERCISE,
QIF_A_EXERCISEX,
QIF_A_EXPIRE,
QIF_A_GRANT,
QIF_A_INTINC,
QIF_A_INTINCX,
QIF_A_MARGINT,
QIF_A_MARGINTX,
QIF_A_MISCEXP,
QIF_A_MISCEXPX,
QIF_A_MISCINC,
QIF_A_MISCINCX,
QIF_A_REINVDIV,
QIF_A_REINVINT,
QIF_A_REINVLG,
QIF_A_REINVMD,
QIF_A_REINVSG,
QIF_A_REINVSH,
QIF_A_REMINDER,
QIF_A_RTRNCAP,
QIF_A_RTRNCAPX,
QIF_A_SELL,
QIF_A_SELLX,
QIF_A_SHRSIN,
QIF_A_SHRSOUT,
QIF_A_STKSPLIT,
QIF_A_VEST,
QIF_A_XIN,
QIF_A_XOUT,
} QifAction;
/* Public API Functions */
QifContext qif_context_new(QifContext parent);
void qif_context_destroy(QifContext ctx);
/* Reads the file into the qif context */
QifError qif_read_file(QifContext ctx, FILE *f);
/* Parse all objects */
void qif_parse_all(QifContext ctx, gpointer arg);
#endif /* QIF_IMPORT_H */

View File

@ -0,0 +1,146 @@
/*
* qif-objects-p.h -- Private header: QIF objects for the QIF importer
*
* Written By: Derek Atkins <derek@ihtfp.com>
*
*/
#ifndef QIF_OBJECTS_P_H
#define QIF_OBJECTS_P_H
#include "qif-import.h"
#include "qif-objects.h"
#include "gnc-numeric.h"
struct _QifAccount {
struct _QifObject obj;
char * name;
char * desc;
char * limitstr;
gnc_numeric limit;
char * budgetstr;
gnc_numeric budget;
GList * type_list;
};
struct _QifCategory {
struct _QifObject obj;
char * name;
char * desc;
char * taxclass;
gboolean taxable;
gboolean expense;
gboolean income;
char * budgetstr;
gnc_numeric budget;
};
struct _QifClass {
struct _QifObject obj;
char * name;
char * desc;
char * taxdesig;
};
struct _QifSecurity {
struct _QifObject obj;
char * name;
char * symbol;
char * type;
};
struct _QifTxn {
struct _QifObject obj;
QifType txn_type;
char * datestr;
Timespec date;
char * payee;
char * address;
char * num;
QifRecnFlag cleared;
/* Investment info */
QifInvstTxn invst_info;
/* The default_split is the default (forward) part of the QIF transaction */
QifSplit default_split;
/* The current_split (if any) defines the current "qif split" we are handling */
QifSplit current_split;
/* The "from" account */
QifAccount from_acct;
/* The list of splits for this txn */
GList * splits;
};
struct _QifSplit {
char * memo;
char * amountstr;
gnc_numeric amount;
gnc_numeric value;
char * catstr;
/* parsed category/account info */
union {
QifObject obj;
QifCategory cat;
QifAccount acct;
} cat;
gboolean cat_is_acct;
QifClass cat_class;
};
struct _QifInvstTxn {
QifAction action;
gnc_numeric amount;
gnc_numeric d_amount;
gnc_numeric price;
gnc_numeric shares;
gnc_numeric commission;
char * amountstr;
char * d_amountstr;
char * pricestr;
char * sharesstr;
char * commissionstr;
char * security;
union {
QifObject obj;
QifCategory cat;
QifAccount acct;
} far_cat;
gboolean far_cat_is_acct;
};
void qif_txn_post_parse_amounts(QifTxn txn);
void qif_invst_txn_setup_splits(QifContext ctx, QifTxn txn);
#endif /* QIF_OBJECTS_P_H */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
/*
* qif-objects.h -- QIF objects for the QIF importer
*
* Written By: Derek Atkins <derek@ihtfp.com>
*
*/
#ifndef QIF_OBJECTS_H
#define QIF_OBJECTS_H
typedef struct _QifObject *QifObject;
typedef struct _QifData *QifData;
struct _QifObject {
const char* type;
void (*destroy)(QifObject);
/* QIF Objects contain data beyond this point.. */
};
#define QIF_O_ACCOUNT "qif-acct"
typedef struct _QifAccount *QifAccount;
#define QIF_O_CATEGORY "qif-cat"
typedef struct _QifCategory *QifCategory;
#define QIF_O_CLASS "qif-class"
typedef struct _QifClass *QifClass;
#define QIF_O_SECURITY "qif-security"
typedef struct _QifSecurity *QifSecurity;
#define QIF_O_TXN "qif-txn"
typedef struct _QifTxn *QifTxn;
typedef struct _QifSplit *QifSplit;
typedef struct _QifInvstTxn *QifInvstTxn;
void qif_object_init(void);
QifAccount find_or_make_acct(QifContext ctx, char *name, GList *types);
QifCategory find_or_make_cat(QifContext ctx, char *name);
QifClass find_or_make_class(QifContext ctx, char *name);
#endif /* QIF_OBJECTS_H */

View File

@ -0,0 +1,647 @@
/*
* qif-parse.c -- parse QIF
*
* Written by: Derek Atkins <derek@ihtfp.com>
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib.h>
#include <string.h>
/* For regex */
#include <sys/types.h>
#include <regex.h>
#include <stdarg.h>
#include "messages.h"
#include "gnc-engine-util.h"
#include "gnc-ui-util.h"
#include "qif-import-p.h"
#include "qif-objects-p.h"
#include "import-parse.h"
static short module = MOD_IMPORT;
/* An array of handlers for the various bang-types */
static QifHandler qif_handlers[QIF_TYPE_MAX] = { NULL };
/* Parser Regular Expressions */
static regex_t category_regex;
static gboolean regex_compiled = FALSE;
/* A Hash Table of bang-types */
static GHashTable *qif_bangtype_map = NULL;
/* A Hash Table of action strings */
static GHashTable *qif_action_map = NULL;
/* A Hash Table of account types */
static GHashTable *qif_atype_map = NULL;
/************************************************************************/
/* Register a handler */
void
qif_register_handler(QifType type, QifHandler handler)
{
qif_handlers[type] = handler;
}
static void
compile_regex()
{
regcomp(&category_regex,
"^ *(\\[)?([^]/\\|]*)(]?)(/([^\\|]*))?(\\|(\\[)?([^]/]*)(]?)(/(.*))?)? *$",
REG_EXTENDED);
regex_compiled = TRUE;
}
#define QIF_ADD_TYPE(ts,t) \
g_hash_table_insert(qif_bangtype_map, ts, GINT_TO_POINTER(t)); \
g_hash_table_insert(qif_bangtype_map, _(ts), GINT_TO_POINTER(t));
static void
build_bangtype_map()
{
g_return_if_fail(!qif_bangtype_map);
qif_bangtype_map = g_hash_table_new(g_str_hash, g_str_equal);
g_assert(qif_bangtype_map);
QIF_ADD_TYPE(N_("type:bank"), QIF_TYPE_BANK);
QIF_ADD_TYPE(N_("type:cash"), QIF_TYPE_CASH);
QIF_ADD_TYPE(N_("type:ccard"), QIF_TYPE_CCARD);
QIF_ADD_TYPE(N_("type:invst"), QIF_TYPE_INVST);
QIF_ADD_TYPE(N_("type:port"), QIF_TYPE_PORT);
QIF_ADD_TYPE(N_("type:oth a"), QIF_TYPE_OTH_A);
QIF_ADD_TYPE(N_("type:oth l"), QIF_TYPE_OTH_L);
QIF_ADD_TYPE(N_("type:class"), QIF_TYPE_CLASS);
QIF_ADD_TYPE(N_("type:cat"), QIF_TYPE_CAT);
QIF_ADD_TYPE(N_("type:security"), QIF_TYPE_SECURITY);
QIF_ADD_TYPE(N_("account"), QIF_ACCOUNT);
QIF_ADD_TYPE(N_("option:autoswitch"), QIF_AUTOSWITCH);
QIF_ADD_TYPE(N_("clear:autoswitch"), QIF_CLEAR_AUTOSWITCH);
}
#undef QIF_ADD_TYPE
#define QIF_ADD_ACT(ts,t) \
g_hash_table_insert(qif_action_map, ts, GINT_TO_POINTER(t));
static void
build_action_map()
{
g_return_if_fail(!qif_action_map);
qif_action_map = g_hash_table_new(g_str_hash, g_str_equal);
g_assert(qif_action_map);
QIF_ADD_ACT("buy", QIF_A_BUY);
QIF_ADD_ACT("kauf", QIF_A_BUY);
QIF_ADD_ACT("buyx", QIF_A_BUYX);
QIF_ADD_ACT("kaufx", QIF_A_BUYX);
QIF_ADD_ACT("cglong", QIF_A_CGLONG);
QIF_ADD_ACT("kapgew", QIF_A_CGLONG); /* Kapitalgewinnsteuer */
QIF_ADD_ACT("cglongx", QIF_A_CGLONG);
QIF_ADD_ACT("kapgewx", QIF_A_CGLONG);
QIF_ADD_ACT("cgmid", QIF_A_CGMID);
QIF_ADD_ACT("cgmidx", QIF_A_CGMIDX);
QIF_ADD_ACT("cgshort", QIF_A_CGSHORT);
QIF_ADD_ACT("k.gewsp", QIF_A_CGSHORT);
QIF_ADD_ACT("cgshortx", QIF_A_CGSHORTX);
QIF_ADD_ACT("k.gewspx", QIF_A_CGSHORTX);
QIF_ADD_ACT("div", QIF_A_DIV); /* dividende */
QIF_ADD_ACT("divx", QIF_A_DIVX);
//QIF_ADD_ACT("exercise", QIF_A_EXERCISE);
//QIF_ADD_ACT("exercisex", QIF_A_EXERCISEX);
//QIF_ADD_ACT("expire", QIF_A_EXPIRE);
//QIF_ADD_ACT("grant", QIF_A_GRANT);
QIF_ADD_ACT("int", QIF_A_INTINC);
QIF_ADD_ACT("intinc", QIF_A_INTINC);
QIF_ADD_ACT("aktzu", QIF_A_INTINC); /* zinsen */
QIF_ADD_ACT("intx", QIF_A_INTINCX);
QIF_ADD_ACT("intincx", QIF_A_INTINCX);
QIF_ADD_ACT("margint", QIF_A_MARGINT);
QIF_ADD_ACT("margintx", QIF_A_MARGINTX);
QIF_ADD_ACT("miscexp", QIF_A_MISCEXP);
QIF_ADD_ACT("miscexpx", QIF_A_MISCEXPX);
QIF_ADD_ACT("miscinc", QIF_A_MISCINC);
QIF_ADD_ACT("miscincx", QIF_A_MISCINCX);
QIF_ADD_ACT("reinvdiv", QIF_A_REINVDIV);
QIF_ADD_ACT("reinvint", QIF_A_REINVINT);
QIF_ADD_ACT("reinvzin", QIF_A_REINVINT);
QIF_ADD_ACT("reinvlg", QIF_A_REINVLG);
QIF_ADD_ACT("reinvkur", QIF_A_REINVLG);
QIF_ADD_ACT("reinvmd", QIF_A_REINVMD);
QIF_ADD_ACT("reinvsg", QIF_A_REINVSG);
QIF_ADD_ACT("reinvksp", QIF_A_REINVSG);
QIF_ADD_ACT("reinvsh", QIF_A_REINVSH);
QIF_ADD_ACT("reminder", QIF_A_REMINDER);
QIF_ADD_ACT("erinnerg", QIF_A_REMINDER);
QIF_ADD_ACT("rtrncap", QIF_A_RTRNCAP);
QIF_ADD_ACT("rtrncapx", QIF_A_RTRNCAPX);
QIF_ADD_ACT("sell", QIF_A_SELL);
QIF_ADD_ACT("verkauf", QIF_A_SELL); /* verkaufen */
QIF_ADD_ACT("sellx", QIF_A_SELLX);
QIF_ADD_ACT("verkaufx", QIF_A_SELLX); /* verkaufen */
QIF_ADD_ACT("shrsin", QIF_A_SHRSIN);
QIF_ADD_ACT("aktzu", QIF_A_SHRSIN);
QIF_ADD_ACT("shrsout", QIF_A_SHRSOUT);
QIF_ADD_ACT("aktab", QIF_A_SHRSOUT);
QIF_ADD_ACT("stksplit", QIF_A_STKSPLIT);
QIF_ADD_ACT("aktsplit", QIF_A_STKSPLIT);
//QIF_ADD_ACT("vest", QIF_A_VEST);
QIF_ADD_ACT("xin", QIF_A_XIN);
QIF_ADD_ACT("xout", QIF_A_XOUT);
}
#undef QIF_ADD_ACT
static GList *
make_list(int count, ...)
{
GList *result = NULL;
GNCAccountType type;
va_list ap;
va_start (ap, count);
while (count--) {
type = va_arg (ap, GNCAccountType);
result = g_list_prepend (result, GINT_TO_POINTER(type));
}
va_end (ap);
return g_list_reverse(result);
}
#define QIF_ADD_ATYPE(a,t) g_hash_table_insert(qif_atype_map, a, t);
static void
build_atype_map()
{
g_return_if_fail(!qif_atype_map);
qif_action_map = g_hash_table_new(g_str_hash, g_str_equal);
g_assert(qif_action_map);
QIF_ADD_ATYPE("bank", make_list(1, BANK));
QIF_ADD_ATYPE("port", make_list(1, BANK));
QIF_ADD_ATYPE("cash", make_list(1, CASH));
QIF_ADD_ATYPE("ccard", make_list(1, CREDIT));
QIF_ADD_ATYPE("invst", make_list(3, BANK, STOCK, MUTUAL));
QIF_ADD_ATYPE("oth a", make_list(3, ASSET, BANK, CASH));
QIF_ADD_ATYPE("oth l", make_list(2, LIABILITY, CREDIT));
QIF_ADD_ATYPE("mutual", make_list(3, BANK, MUTUAL, STOCK));
/* Internal types */
QIF_ADD_ATYPE("__any_bank__", make_list(5, BANK, CREDIT, CASH, ASSET,
LIABILITY));
QIF_ADD_ATYPE("__all__", make_list(7, BANK, CREDIT, CASH, ASSET, LIABILITY,
STOCK, MUTUAL));
QIF_ADD_ATYPE("__stock__", make_list(2, STOCK, MUTUAL));
QIF_ADD_ATYPE("__income__", make_list(1, INCOME));
QIF_ADD_ATYPE("__expense__", make_list(1, EXPENSE));
QIF_ADD_ATYPE("__equity__", make_list(1, EQUITY));
}
#undef QIF_ADD_ATYPE
/************************************************************************/
/*
* We've got a !Type line. Parse the line into the appropriate
* type and then initialize the handler.
*/
void
qif_parse_bangtype(QifContext ctx, const char *line)
{
QifType type;
char *bangtype;
gpointer result;
g_return_if_fail(line && *line == '!');
if (!qif_bangtype_map)
build_bangtype_map();
/* Make a local copy so we can manipulate it.
* - strip off leading/trailing whitespace
* - make it all lower case
*/
bangtype = g_strdup(line);
g_strstrip(bangtype);
g_strdown(bangtype);
/* In some cases we get "!Type Bank" -- change the space to a colon */
if (!strncmp(bangtype, "!type ", 6))
bangtype[5] = ':';
/* Lookup the bangtype in the map and then destroy the local copy */
result = g_hash_table_lookup(qif_bangtype_map, bangtype);
g_free(bangtype);
if (!result) {
PWARN("Unknown bang-type at line %d: %s. Ignored", ctx->lineno, line);
return;
}
type = GPOINTER_TO_INT(result);
/* Set the current context parse type and handler */
ctx->parse_type = type;
ctx->handler = qif_handlers[type];
/* now initialize this new parse type (if there's an init function) */
if (ctx->handler && ctx->handler->init)
ctx->handler->init(ctx);
}
/* returns TRUE if successful, FALSE if there is a problem */
gboolean
qif_parse_split_category(const char* str,
char** cat, gboolean *cat_is_acct, char** cat_class,
char** miscx_cat, gboolean *miscx_cat_is_acct,
char **miscx_class)
{
/* This is a pretty f**ked up string. Basically it looks like:
* ([)cat-or-acct(])(/(class))(|([)cat-of-acct(])(/ext))
*
* where data in parens is "optional" (depending on the context).
*
* examples from reality:
*
* category
* category:subcategory
* category/class
* category:subcat/class
* [account]
* [account]/class
*
* cat/cat-class|miscx-cat/miscx-class
*/
regmatch_t pmatch[12];
g_return_val_if_fail(cat && cat_is_acct && cat_class &&
miscx_cat && miscx_cat_is_acct && miscx_class, FALSE);
if (!regex_compiled)
compile_regex();
if (regexec(&category_regex, str, 12, pmatch, 0) != 0) {
PERR("category match failed");
return FALSE;
}
/*
* what the substrings mean:
* 1 the opening [ for a transfer
* 2 the category
* 3 the closing ]
* 4 the class /
* 5 the class
* 6 the miscx expression (whole thing)
* 7 the opening [
* 8 the miscx category
* 9 the closing ]
* 10 the class /
* 11 the class
*/
if (pmatch[2].rm_so == -1) {
PERR("no category match found!");
return FALSE;
}
/* catgory name */
*cat = g_strndup(str+pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[21].rm_so);
/* category is account? */
*cat_is_acct = (pmatch[1].rm_so != -1 && pmatch[3].rm_so != -1);
/* category class */
*cat_class = (pmatch[4].rm_so != -1 ?
g_strndup(str+pmatch[5].rm_so, pmatch[5].rm_eo - pmatch[5].rm_so) :
NULL);
/* miscx category name */
*miscx_cat = (pmatch[6].rm_so != -1 ?
g_strndup(str+pmatch[8].rm_so, pmatch[8].rm_eo - pmatch[8].rm_so) :
NULL);
/* miscx cat is acct */
*miscx_cat_is_acct = (pmatch[7].rm_so != -1 && pmatch[9].rm_so != -1);
/* miscx class */
*miscx_class = (pmatch[10].rm_so != -1 ?
g_strndup(str+pmatch[11].rm_so,
pmatch[11].rm_eo - pmatch[11].rm_so) : NULL);
return TRUE;
}
/*
* qif_parse_cleared -- parse the 'C'leared field of a QIF Transaction.
* returns the QIF reconciled flag.
*
* * means cleared, x or X means reconciled, and ! or ? mean some
* budget related stuff I don't understand.
*/
QifRecnFlag
qif_parse_cleared(QifLine line)
{
g_return_val_if_fail(line, QIF_R_NO);
g_return_val_if_fail(line->line, QIF_R_NO);
switch (*line->line) {
case '*':
return QIF_R_CLEARED;
case 'x':
case 'X':
return QIF_R_RECONCILED;
case '?':
case '!':
return QIF_R_BUDGETED;
default:
PERR("Unknown QIF Cleared flag at line %d: %s", line->lineno, line->line);
return QIF_R_NO;
}
}
QifAction qif_parse_action(QifLine line)
{
QifAction qaction;
gpointer result;
char *action;
g_return_val_if_fail(line, QIF_A_NONE);
g_return_val_if_fail(line->line, QIF_A_NONE);
if (!qif_action_map)
build_action_map();
/* Duplicate the action and force it to lower case and strip any spaces */
action = g_strdup(line->line);
g_strstrip(action);
g_strdown(action);
result = g_hash_table_lookup(qif_action_map, action);
g_free(action);
if (!result) {
/* XXX: pop up a dialog? */
PWARN("Unknown Action at line %d: %s. Some transactions may be discarded",
line->lineno, line->line);
return QIF_A_NONE;
}
qaction = GPOINTER_TO_INT(result);
return qaction;
}
GList * qif_parse_acct_type(const char *str, gint lineno)
{
GList *result;
char *type;
if (!qif_atype_map)
build_atype_map();
/* Duplicate the type and force it to lower case and strip any spaces */
type = g_strdup(str);
g_strstrip(type);
g_strdown(type);
result = g_hash_table_lookup(qif_atype_map, type);
g_free(type);
if (!result) {
PWARN("Unknown account type at line %d: %s. ", lineno, str);
result = g_hash_table_lookup(qif_atype_map, "bank");
g_return_val_if_fail(result, NULL);
}
return result;
}
GList * qif_parse_acct_type_guess(QifType type)
{
const char *atype = NULL;
switch (type) {
case QIF_TYPE_BANK: atype = "bank"; break;
case QIF_TYPE_CASH: atype = "cash"; break;
case QIF_TYPE_CCARD: atype = "ccard"; break;
case QIF_TYPE_INVST: atype = "invst"; break;
case QIF_TYPE_PORT: atype = "port"; break;
case QIF_TYPE_OTH_A: atype = "oth a"; break;
case QIF_TYPE_OTH_L: atype = "oth l"; break;
default: return NULL;
}
return qif_parse_acct_type(atype, -1);
}
/***********************************************************************
* Parsing numbers and dates...
*/
typedef struct _parse_helper {
QifContext ctx;
GncImportFormat budget;
GncImportFormat limit;
GncImportFormat amount;
GncImportFormat d_amount;
GncImportFormat price;
GncImportFormat shares;
GncImportFormat commission;
GncImportFormat date;
} *parse_helper_t;
#define QIF_PARSE_CHECK_NUMBER(str,help) { \
if (str) (help) = gnc_import_test_numeric((str), (help)); \
}
#define QIF_PARSE_PARSE_NUMBER(str,fmt,val) { \
if (str) gnc_import_parse_numeric((str), (fmt), (val)); \
}
static void
qif_parse_check_account(gpointer key, gpointer val, gpointer data)
{
parse_helper_t helper = data;
QifAccount acct = val;
QIF_PARSE_CHECK_NUMBER(acct->limitstr, helper->limit);
QIF_PARSE_CHECK_NUMBER(acct->budgetstr, helper->budget);
}
static void
qif_parse_parse_account(gpointer key, gpointer val, gpointer data)
{
parse_helper_t helper = data;
QifAccount acct = val;
QIF_PARSE_PARSE_NUMBER(acct->limitstr, helper->limit, &acct->limit);
QIF_PARSE_PARSE_NUMBER(acct->budgetstr, helper->budget, &acct->budget);
}
static void
qif_parse_check_category(gpointer key, gpointer val, gpointer data)
{
parse_helper_t helper = data;
QifCategory cat = val;
QIF_PARSE_CHECK_NUMBER(cat->budgetstr, helper->budget);
}
static void
qif_parse_parse_category(gpointer key, gpointer val, gpointer data)
{
parse_helper_t helper = data;
QifCategory cat = val;
QIF_PARSE_PARSE_NUMBER(cat->budgetstr, helper->budget, &cat->budget);
}
static void
qif_parse_check_txn(gpointer val, gpointer data)
{
parse_helper_t helper = data;
QifTxn txn = val;
QifSplit split;
QifInvstTxn itxn;
GList *node;
/* Check the date */
helper->date = gnc_import_test_date(txn->datestr, helper->date);
/* If this is an investment transaction, then all the info is in
* the invst_info. Otherwise it's all in the splits.
*/
itxn = txn->invst_info;
if (itxn) {
QIF_PARSE_CHECK_NUMBER(itxn->amountstr, helper->amount);
QIF_PARSE_CHECK_NUMBER(itxn->d_amountstr, helper->d_amount);
QIF_PARSE_CHECK_NUMBER(itxn->pricestr, helper->price);
QIF_PARSE_CHECK_NUMBER(itxn->sharesstr, helper->shares);
QIF_PARSE_CHECK_NUMBER(itxn->commissionstr, helper->commission);
} else {
split = txn->default_split;
node = txn->splits;
do {
QIF_PARSE_CHECK_NUMBER(split->amountstr, helper->amount);
if (node) {
split = node->data;
node = node->next;
} else
split = NULL;
} while (split);
}
}
static void
qif_parse_parse_txn(gpointer val, gpointer data)
{
parse_helper_t helper = data;
QifTxn txn = val;
QifSplit split;
QifInvstTxn itxn;
GList *node;
/* Parse the date */
gnc_import_parse_date(txn->datestr, helper->date, &txn->date);
/* If this is an investment transaction, then all the info is in
* the invst_info. Otherwise it's all in the splits.
*/
itxn = txn->invst_info;
if (itxn) {
QIF_PARSE_PARSE_NUMBER(itxn->amountstr, helper->amount, &itxn->amount);
QIF_PARSE_PARSE_NUMBER(itxn->d_amountstr, helper->d_amount, &itxn->d_amount);
QIF_PARSE_PARSE_NUMBER(itxn->pricestr, helper->price, &itxn->price);
QIF_PARSE_PARSE_NUMBER(itxn->sharesstr, helper->shares, &itxn->shares);
QIF_PARSE_PARSE_NUMBER(itxn->commissionstr, helper->commission,
&itxn->commission);
qif_invst_txn_setup_splits(helper->ctx, txn);
} else {
split = txn->default_split;
node = txn->splits;
do {
QIF_PARSE_PARSE_NUMBER(split->amountstr, helper->amount, &split->amount);
if (node) {
split = node->data;
node = node->next;
} else
split = NULL;
} while (split);
qif_txn_post_parse_amounts(txn);
}
}
void
qif_parse_all(QifContext ctx, gpointer arg)
{
struct _parse_helper helper;
helper.ctx = ctx;
/* PARSE ACCOUNTS */
/* First, figure out the formats */
helper.limit = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
helper.budget = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
qif_object_map_foreach(ctx, QIF_O_ACCOUNT, qif_parse_check_account, &helper);
/* Make sure it's not ambiguous */
if (helper.limit & (helper.limit - 1)) helper.limit = GNCIF_NUM_PERIOD;
if (helper.budget & (helper.budget - 1)) helper.budget = GNCIF_NUM_PERIOD;
/* Now convert the numbers */
qif_object_map_foreach(ctx, QIF_O_ACCOUNT, qif_parse_parse_account, &helper);
/* PARSE CATEGORIES */
helper.budget = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
qif_object_map_foreach(ctx, QIF_O_CATEGORY, qif_parse_check_category, &helper);
/* make sure it's not ambiguous */
if (helper.budget & (helper.budget - 1)) helper.budget = GNCIF_NUM_PERIOD;
/* Now convert the numbers */
qif_object_map_foreach(ctx, QIF_O_CATEGORY, qif_parse_parse_category, &helper);
/* PARSE TRANSACTIONS */
helper.amount = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
helper.d_amount = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
helper.price = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
helper.shares = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
helper.commission = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
helper.date = GNCIF_DATE_MDY | GNCIF_DATE_DMY | GNCIF_DATE_YMD | GNCIF_DATE_YDM;
qif_object_list_foreach(ctx, QIF_O_TXN, qif_parse_check_txn, &helper);
/* check/fix ambiguities */
if (helper.amount & (helper.amount - 1)) helper.amount = GNCIF_NUM_PERIOD;
if (helper.d_amount & (helper.d_amount - 1)) helper.d_amount = GNCIF_NUM_PERIOD;
if (helper.price & (helper.price - 1)) helper.price = GNCIF_NUM_PERIOD;
if (helper.shares & (helper.shares - 1)) helper.shares = GNCIF_NUM_PERIOD;
if (helper.commission & (helper.commission - 1))
helper.commission = GNCIF_NUM_PERIOD;
if (helper.date & (helper.date - 1)) {
helper.date = gnc_import_choose_fmt(_("The Date format is ambiguous. "
"Please choose the correct format."),
helper.date, arg);
}
/* now parse it.. */
qif_object_list_foreach(ctx, QIF_O_TXN, qif_parse_parse_txn, &helper);
}

View File

@ -0,0 +1,30 @@
/*
* qif-parse.h -- routines for parsing pieces of a QIF file
*
* Written By: Derek Atkins <derek@ihtfp.com>
*
*/
#ifndef QIF_PARSE_H
#define QIF_PARSE_H
#include "qif-import.h"
void qif_register_handler(QifType type, QifHandler handler);
void qif_parse_bangtype(QifContext ctx, const char *line);
gboolean
qif_parse_split_category(const char* str,
char** cat, gboolean *cat_is_acct, char** cat_class,
char** miscx_cat, gboolean *miscx_cat_is_acct,
char **miscx_class);
gboolean qif_parse_numeric(QifLine line, gnc_numeric *num);
QifRecnFlag qif_parse_cleared(QifLine line);
QifAction qif_parse_action(QifLine line);
/* The caller should never destroy this list */
GList * qif_parse_acct_type(const char *str, gint lineno);
GList * qif_parse_acct_type_guess(QifType type);
#endif /* QIF_PARSE_H */

View File

@ -0,0 +1 @@
TESTS=