Skip to content
Browse files

* 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/gnucash/trunk@8873 57a11ea4-9604-0410-9ed3-97b8803252fd
  • Loading branch information...
1 parent d732df0 commit 88ed391b6ab1499555286b0520ec64edbe05813b @derekatkins derekatkins committed Jul 15, 2003
View
6 ChangeLog
@@ -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>
* src/import-export/import-parse.[ch]: routines to parse numbers
View
4 configure.in
@@ -1193,12 +1193,14 @@ AC_OUTPUT( m4/Makefile intl/Makefile po/Makefile.in
src/import-export/binary-import/Makefile
src/import-export/binary-import/test/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-io-core/Makefile
src/import-export/qif-io-core/test/Makefile
src/import-export/ofx/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/glade/Makefile
src/import-export/hbci/test/Makefile
View
2 src/import-export/Makefile.am
@@ -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
View
31 src/import-export/qif/Makefile.am
@@ -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
262 src/import-export/qif/qif-context.c
@@ -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
136 src/import-export/qif/qif-defaults.c
@@ -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
27 src/import-export/qif/qif-defaults.h
@@ -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
175 src/import-export/qif/qif-file.c
@@ -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
18 src/import-export/qif/qif-file.h
@@ -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
71 src/import-export/qif/qif-import-p.h
@@ -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
113 src/import-export/qif/qif-import.h
@@ -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
146 src/import-export/qif/qif-objects-p.h
@@ -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 */
View
1,313 src/import-export/qif/qif-objects.c
@@ -0,0 +1,1313 @@
+/*
+ * qif-objects.c -- Objects for the QIF Importer
+ *
+ * Written by: Derek Atkins <derek@ihtfp.com>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <string.h>
+#include "Account.h"
+
+#include "gnc-engine-util.h"
+
+#include "qif-import-p.h"
+#include "qif-objects-p.h"
+#include "qif-defaults.h"
+
+static short module = MOD_IMPORT;
+
+/* create a new object of type t, with type-string type and
+ * destroy function dest. Requires 'obj' to be set.
+ */
+#define qif_object_new(t,typ,dest) ({ \
+ obj = (QifObject) g_new0(t, 1); \
+ obj->type = typ; \
+ obj->destroy = dest; \
+ obj; \
+})
+
+/* Set and clear flags in bit-flags */
+#define qif_set_flag(i,f) (i |= f)
+#define qif_clear_flag(i,f) (i &= ~f)
+
+/* Save the string from this "line". Also:
+ * - make sure we're not over-writing anything.
+ * - make sure the 'line' object no longer references the string.
+ */
+#define qif_save_str(var) { \
+ if (var) { \
+ PERR("duplicate found at line %d: %s", line->lineno, line->line); \
+ g_free(var); \
+ } \
+ (var) = line->line; \
+ line->line = NULL; \
+}
+
+/* QIF Account */
+static void
+qif_account_destroy(QifObject obj)
+{
+ QifAccount acct = (QifAccount) obj;
+
+ g_free(acct->name);
+ g_free(acct->desc);
+ g_free(acct->limitstr);
+ g_free(acct->budgetstr);
+
+ g_free(acct);
+};
+
+static QifAccount
+qif_account_new(void)
+{
+ QifObject obj;
+ QifAccount acct;
+
+ obj = qif_object_new(struct _QifAccount, QIF_O_ACCOUNT, qif_account_destroy);
+
+ acct = (QifAccount)obj;
+ acct->type_list = qif_parse_acct_type("bank", -1);
+
+ acct->limit = gnc_numeric_zero();
+ acct->budget = gnc_numeric_zero();
+ return acct;
+}
+
+/*
+ * Merge acct into ctx. If this account already exists in ctx then
+ * merge in any new values from acct into the ctx version and return
+ * the existing acct. If the account does not already exist, then
+ * insert it into the ctx and return it.
+ */
+static QifAccount
+qif_account_merge(QifContext ctx, QifAccount acct)
+{
+ QifAccount acct2 =
+ (QifAccount)qif_object_map_lookup(ctx, acct->obj.type, acct->name);
+
+ if (!acct2) {
+ qif_object_map_insert(ctx, acct->obj.type, (QifObject)acct);
+ return acct;
+ }
+
+ /* obviously the name is the same, so don't worry about that */
+
+ if (!acct2->desc && acct->desc)
+ acct2->desc = g_strdup(acct->desc);
+
+ if (!acct2->type_list && acct->type_list)
+ acct2->type_list = acct->type_list;
+
+ if (!acct2->limitstr && acct->limitstr) {
+ acct2->limitstr = g_strdup(acct->limitstr);
+ acct2->limit = acct->limit;
+ }
+
+ if (!acct2->budgetstr && acct->budgetstr) {
+ acct2->budgetstr = g_strdup(acct->budgetstr);
+ acct2->budget = acct->budget;
+ }
+
+ return acct2;
+}
+
+static QifError
+qif_account_parse(QifContext ctx, GList *record)
+{
+ QifAccount acct, temp;
+ QifLine line;
+
+ g_return_val_if_fail(ctx, QIF_E_INTERNAL);
+ g_return_val_if_fail(record, QIF_E_BADSTATE);
+
+ acct = qif_account_new();
+
+ for (; record; record = record->next) {
+ line = record->data;
+
+ switch (line->type) {
+ case 'N': /* N : account name */
+ qif_save_str(acct->name);
+ ctx->last_seen_acct = acct;
+ break;
+ case 'D': /* D : account description */
+ qif_save_str(acct->desc);
+ break;
+ case 'T': /* T : account type */
+ acct->type_list = qif_parse_acct_type(line->line, line->lineno);
+ break;
+ case 'L': /* L : account limit */
+ qif_save_str(acct->limitstr);
+ break;
+ case 'B': /* B : account budget */
+ qif_save_str(acct->budgetstr);
+ break;
+ default:
+ PERR("Unknown QIF account data at line %d: %s", line->lineno, line->line);
+ }
+ }
+
+ /* Merge the account into the context */
+ temp = qif_account_merge(ctx, acct);
+ if (! (ctx->parse_flags & QIF_F_IGNORE_ACCOUNTS))
+ ctx->current_acct = temp;
+ if (temp != acct)
+ qif_account_destroy((QifObject)acct);
+
+ return QIF_E_OK;
+}
+
+/* QIF Category */
+static void
+qif_cat_destroy(QifObject obj)
+{
+ QifCategory cat = (QifCategory) obj;
+
+ g_free(cat->name);
+ g_free(cat->desc);
+ g_free(cat->taxclass);
+ g_free(cat->budgetstr);
+
+ g_free(cat);
+}
+
+static QifCategory
+qif_cat_new(void)
+{
+ QifObject obj;
+ QifCategory cat;
+
+ obj = qif_object_new(struct _QifCategory, QIF_O_CATEGORY, qif_cat_destroy);
+ cat = (QifCategory)obj;
+ cat->budget = gnc_numeric_zero();
+
+ return cat;
+}
+
+/*
+ * Merge cat into ctx. If this category already exists in ctx then
+ * merge in any new values from cat into the ctx version and return
+ * the existing cat. If the category does not already exist, then
+ * insert it into the ctx and return it.
+ */
+static QifCategory
+qif_cat_merge(QifContext ctx, QifCategory cat)
+{
+ QifCategory cat2 =
+ (QifCategory)qif_object_map_lookup(ctx, cat->obj.type, cat->name);
+
+ if (!cat2) {
+ qif_object_map_insert(ctx, cat->obj.type, (QifObject)cat);
+ return cat;
+ }
+
+ /* obviously the name is the same, so don't worry about that */
+
+ if (!cat2->desc && cat->desc)
+ cat2->desc = g_strdup(cat->desc);
+
+ if (!cat2->taxclass && cat->taxclass)
+ cat2->taxclass = g_strdup(cat->taxclass);
+
+ cat2->taxable = (cat2->taxable || cat->taxable);
+ cat2->expense = (cat2->expense || cat->expense);
+ cat2->income = (cat2->income || cat->income);
+
+ if (!cat2->budgetstr && cat->budgetstr) {
+ cat2->budgetstr = g_strdup(cat->budgetstr);
+ cat2->budget = cat->budget;
+ }
+
+ return cat2;
+}
+
+static QifError
+qif_cat_parse(QifContext ctx, GList *record)
+{
+ QifCategory cat;
+ QifLine line;
+
+ g_return_val_if_fail(ctx, QIF_E_INTERNAL);
+ g_return_val_if_fail(record, QIF_E_BADSTATE);
+
+ cat = qif_cat_new();
+
+ for (; record; record = record->next) {
+ line = record->data;
+
+ switch (line->type) {
+ case 'N': /* N : category name */
+ qif_save_str(cat->name);
+ break;
+ case 'D': /* D : category description */
+ qif_save_str(cat->desc);
+ break;
+ case 'T': /* T : category is taxable? */
+ cat->taxable = TRUE;
+ break;
+ case 'E': /* E : category is expense? */
+ cat->expense = TRUE;
+ break;
+ case 'I': /* I : category is income? */
+ cat->income = TRUE;
+ break;
+ case 'R': /* R : category taxclass XXX */
+ /* XXX: a number? */
+ qif_save_str(cat->taxclass);
+ break;
+ case 'B': /* B : category budget */
+ qif_save_str(cat->budgetstr);
+ break;
+ default:
+ PERR("Unknown QIF category data at line %d: %s", line->lineno, line->line);
+ }
+ }
+
+ if (qif_cat_merge(ctx, cat) != cat)
+ qif_cat_destroy((QifObject)cat);
+
+ return QIF_E_OK;
+}
+
+/* QIF Class */
+static void
+qif_class_destroy(QifObject obj)
+{
+ QifClass qclass = (QifClass) obj;
+
+ g_free(qclass->name);
+ g_free(qclass->desc);
+ g_free(qclass->taxdesig);
+
+ g_free(qclass);
+}
+
+static QifClass
+qif_class_new()
+{
+ QifObject obj;
+
+ obj = qif_object_new(struct _QifClass, QIF_O_CLASS, qif_class_destroy);
+ return (QifClass)obj;
+}
+
+/*
+ * Merge qclass into ctx. If this class already exists in ctx then
+ * merge in any new values from qclass into the ctx version and return
+ * the existing qclass. If the class does not already exist, then
+ * insert it into the ctx and return it.
+ */
+static QifClass
+qif_class_merge(QifContext ctx, QifClass qclass)
+{
+ QifClass qclass2 =
+ (QifClass)qif_object_map_lookup(ctx, qclass->obj.type, qclass->name);
+
+ if (!qclass2) {
+ qif_object_map_insert(ctx, qclass->obj.type, (QifObject)qclass);
+ return qclass;
+ }
+
+ /* obviously the name is the same, so don't worry about that */
+
+ if (!qclass2->desc && qclass->desc)
+ qclass2->desc = g_strdup(qclass->desc);
+
+ if (!qclass2->taxdesig && qclass->taxdesig)
+ qclass2->taxdesig = g_strdup(qclass->taxdesig);
+
+ return qclass2;
+}
+
+static QifError
+qif_class_parse(QifContext ctx, GList *record)
+{
+ QifClass qclass;
+ QifLine line;
+
+ g_return_val_if_fail(ctx, QIF_E_INTERNAL);
+ g_return_val_if_fail(record, QIF_E_BADSTATE);
+
+ qclass = qif_class_new();
+
+ for (; record; record = record->next) {
+ line = record->data;
+
+ switch (line->type) {
+ case 'N': /* N : class name */
+ qif_save_str(qclass->name);
+ break;
+ case 'D': /* D : class description */
+ qif_save_str(qclass->desc);
+ break;
+ case 'R': /* R : Tax designator */
+ qif_save_str(qclass->taxdesig);
+ break;
+ default:
+ PERR("Unknown QIF class data at line %d: %s", line->lineno, line->line);
+ }
+ }
+
+ if (qif_class_merge(ctx, qclass) != qclass)
+ qif_class_destroy((QifObject)qclass);
+
+ return QIF_E_OK;
+}
+
+/* QIF Security Symbol */
+static void
+qif_security_destroy(QifObject obj)
+{
+ QifSecurity security = (QifSecurity) obj;
+
+ g_free(security->name);
+ g_free(security->symbol);
+ g_free(security->type);
+
+ g_free(security);
+}
+
+static QifSecurity
+qif_security_new()
+{
+ QifObject obj;
+
+ obj = qif_object_new(struct _QifSecurity, QIF_O_SECURITY, qif_security_destroy);
+ return (QifSecurity)obj;
+}
+
+/*
+ * Merge security into ctx. If this security already exists in ctx then
+ * merge in any new values from security into the ctx version and return
+ * the existing security. If the security does not already exist, then
+ * insert it into the ctx and return it.
+ */
+static QifSecurity
+qif_security_merge(QifContext ctx, QifSecurity security)
+{
+ QifSecurity security2 =
+ (QifSecurity)qif_object_map_lookup(ctx, security->obj.type, security->name);
+
+ if (!security2) {
+ qif_object_map_insert(ctx, security->obj.type, (QifObject)security);
+ return security;
+ }
+
+ /* obviously the name is the same, so don't worry about that */
+
+ if (!security2->symbol && security->symbol)
+ security2->symbol = g_strdup(security->symbol);
+
+ if (!security2->type && security->type)
+ security2->type = g_strdup(security->type);
+
+ return security2;
+}
+
+static QifError
+qif_security_parse(QifContext ctx, GList *record)
+{
+ QifSecurity security;
+ QifLine line;
+
+ g_return_val_if_fail(ctx, QIF_E_INTERNAL);
+ g_return_val_if_fail(record, QIF_E_BADSTATE);
+
+ security = qif_security_new();
+
+ for (; record; record = record->next) {
+ line = record->data;
+
+ switch (line->type) {
+ case 'N': /* N : security name */
+ qif_save_str(security->name);
+ break;
+ case 'S': /* S : security symbol */
+ qif_save_str(security->symbol);
+ break;
+ case 'T': /* T : security type */
+ qif_save_str(security->type);
+ break;
+ default:
+ PERR("Unknown QIF security data at line %d: %s", line->lineno, line->line);
+ }
+ }
+
+ if (qif_security_merge(ctx, security) != security)
+ qif_security_destroy((QifObject)security);
+
+ return QIF_E_OK;
+}
+
+/********************* TXN *********************/
+
+static QifSplit
+qif_split_new()
+{
+ QifSplit split = g_new0(struct _QifSplit, 1);
+
+ /* Initialize to 'zero' (even though they are not valid) */
+ split->amount = gnc_numeric_zero();
+ split->value = gnc_numeric_zero();
+
+ return split;
+}
+
+static void
+qif_split_destroy(QifSplit split)
+{
+ if (!split) return;
+
+ g_free(split->memo);
+ g_free(split->catstr);
+ g_free(split->amountstr);
+
+ g_free(split);
+}
+
+static QifSplit
+qif_split_copy(QifSplit split)
+{
+ QifSplit s = qif_split_new();
+
+ memcpy(s, split, sizeof(*s));
+ if (s->memo) s->memo = g_strdup(s->memo);
+ if (s->catstr) s->memo = g_strdup(s->catstr);
+
+ return s;
+}
+
+/* Forward declarations */
+static void qif_txn_invst_destroy(QifInvstTxn);
+
+/* QIF Transaction */
+
+static void
+qif_split_parse_category(QifContext ctx, QifSplit split)
+{
+ char *cat = NULL;
+ char *cat_class = NULL;
+ char *miscx_cat = NULL;
+ char *miscx_class = NULL;
+
+ gboolean miscx_is_acct;
+
+ static GList *types = NULL;
+
+ g_return_if_fail(ctx);
+ g_return_if_fail(split);
+ g_return_if_fail(split->cat.cat == NULL && split->cat_class == NULL);
+
+ if (qif_parse_split_category(split->catstr,
+ &cat, &split->cat_is_acct, &cat_class,
+ &miscx_cat, &miscx_is_acct, &miscx_class)) {
+ g_assert(cat);
+
+ if (split->cat_is_acct) {
+ if (types == NULL)
+ types = qif_parse_acct_type("__any_bank__", -1);
+
+ split->cat.acct = find_or_make_acct(ctx, cat, types);
+
+ } else
+ split->cat.cat = find_or_make_cat(ctx, cat);
+
+ if (cat_class)
+ split->cat_class = find_or_make_class(ctx, cat_class);
+
+ /* miscx isn't used in a normal transaction, so just ignore it */
+ if (miscx_cat)
+ g_free(miscx_cat);
+ if (miscx_class)
+ g_free(miscx_class);
+
+ } else
+ PERR("Problem parsing split category: %s", split->catstr);
+}
+
+static void
+qif_txn_destroy(QifObject obj)
+{
+ QifTxn txn = (QifTxn) obj;
+ GList *node;
+ QifSplit split;
+
+ g_free(txn->datestr);
+ g_free(txn->payee);
+ g_free(txn->address);
+ g_free(txn->num);
+
+ if (txn->invst_info)
+ qif_txn_invst_destroy(txn->invst_info);
+
+ for (node = txn->splits; node; node = node->next) {
+ split = node->data;
+ if (split == txn->default_split)
+ txn->default_split = NULL;
+ if (split == txn->current_split)
+ txn->current_split = NULL;
+
+ qif_split_destroy(split);
+ }
+
+ g_list_free(txn->splits);
+ qif_split_destroy(txn->default_split);
+ qif_split_destroy(txn->current_split);
+
+ g_free(txn);
+}
+
+static QifTxn
+qif_txn_new(void)
+{
+ QifObject obj;
+ QifTxn txn;
+
+ obj = qif_object_new(struct _QifTxn, "qif-txn", qif_txn_destroy);
+ txn = (QifTxn) obj;
+ txn->default_split = qif_split_new();
+
+ return txn;
+}
+
+static void
+qif_txn_init(QifContext ctx)
+{
+ if (ctx->parse_flags & QIF_F_IGNORE_ACCOUNTS)
+ ctx->current_acct = ctx->last_seen_acct;
+
+ qif_clear_flag(ctx->parse_flags, QIF_F_IGNORE_ACCOUNTS);
+ ctx->parse_state = NULL;
+}
+
+static gboolean
+qif_is_bad_numeric_string(const char* line)
+{
+ return (strncmp(line, "...", 3) == 0);
+}
+
+/*
+ * this is called for the first transaction after each !Type: tag.
+ *
+ * if the first transaction after a !Type: tag has a payee of "Opening
+ * Balance" or "Initial Balance", we have to massage the transaction a
+ * little. The meaning of an OB transaction is "transfer from Equity
+ * to the account specified in the L line." Idiomatically, ms-money
+ * and some others use this transaction instead of an Account record
+ * to specify "this" account (the from-account for all following
+ * transactions), so we have to allow for that.
+ *
+ * Even if the payee isn't "Opening Balance", we if we have no default
+ * from-account by this time we need to set one. In that case we set
+ * the default account based on the file name.
+ *
+ * If we DO know the account already, and this is a tranfer to it,
+ * it's also an opening balance regardless of the payee.
+ *
+ * In the end make sure that the context 'current account' is set.
+ */
+static void
+qif_process_opening_balance_txn(QifContext ctx, QifTxn txn)
+{
+ QifSplit split = txn->default_split;
+ QifAccount cur_acct = ctx->current_acct;
+
+ g_return_if_fail(txn->invst_info == NULL);
+
+ if ((!cur_acct && txn->payee &&
+ (!strcasecmp(txn->payee, "Opening Balance") ||
+ !strcasecmp(txn->payee, "Initial Balance")) && split->cat_is_acct) ||
+ (cur_acct &&
+ ((split->cat_is_acct && !strcasecmp(split->cat.acct->name, cur_acct->name))
+ ||
+ (!split->cat_is_acct && !strcasecmp(split->cat.cat->name, cur_acct->name))))
+ ) {
+
+ /* This is an explicit "Opening Balance" transactions. We need to
+ * change the "from account" to point to the equity account that
+ * the opening balance comes from...
+ */
+ if (split->cat_is_acct)
+ cur_acct = split->cat.acct;
+ else {
+ g_assert(split->cat.cat);
+ cur_acct = find_or_make_acct(ctx, g_strdup(split->cat.cat->name),
+ qif_parse_acct_type_guess(txn->txn_type));
+ split->cat_is_acct = TRUE;
+ }
+ split->cat.acct = qif_default_equity_acct(ctx);
+ }
+
+ if (!ctx->current_acct)
+ ctx->current_acct = cur_acct;
+}
+
+/* process all the splits in the transaction -- if this is a "split
+ * transaction" then make sure the sum of all the amounts (including
+ * the default split) does NOT equal zero -- if it does then we want
+ * to reverse all the splits. The "amount" should be the 'T' amount
+ * from the txn.
+ */
+static void
+qif_txn_fix_amounts(QifTxn txn, gnc_numeric amount)
+{
+ gnc_numeric sum = amount;
+ QifSplit split;
+ GList *node;
+
+ g_return_if_fail(txn);
+
+ /* No current_split, so this is NOT a split transaction. */
+ if (!txn->current_split) return;
+
+ /* Then add in every split in the split-list */
+ for (node = txn->splits; node; node = node->next) {
+ split = node->data;
+ sum = gnc_numeric_add(sum, split->amount, GNC_DENOM_AUTO, GNC_DENOM_LCD);
+ }
+
+ /* if the sum is not zero then reverse all the amounts in the split list */
+ if (!gnc_numeric_zero_p(sum))
+ for (node = txn->splits; node; node = node->next) {
+ split = node->data;
+ split->amount = gnc_numeric_neg(split->amount);
+ }
+}
+
+static QifError
+qif_txn_parse(QifContext ctx, GList *record)
+{
+ QifTxn txn;
+ QifLine line;
+ GList *node;
+ QifSplit split;
+
+ g_return_val_if_fail(ctx, QIF_E_INTERNAL);
+ g_return_val_if_fail(record, QIF_E_BADSTATE);
+
+ txn = qif_txn_new();
+ txn->txn_type = ctx->parse_type;
+
+ for (; record; record = record->next) {
+ line = record->data;
+
+ switch (line->type) {
+ case 'D': /* D : transaction date */
+ qif_save_str(txn->datestr);
+ break;
+ case 'P': /* P : payee */
+ qif_save_str(txn->payee);
+ break;
+ case 'A': /* A : address */
+ /* multiple 'A' lines are appended together with newlines */
+ if (txn->address) {
+ char *tmp = txn->address;
+ txn->address = g_strconcat(tmp, "\n", line->line, NULL);
+ g_free(tmp);
+ } else
+ qif_save_str(txn->address);
+ break;
+ case 'N': /* N : check/transaction number */
+ qif_save_str(txn->num);
+ break;
+ case 'C': /* C : transaction cleared flag */
+ txn->cleared = qif_parse_cleared(line);
+ break;
+ case 'L': /* L : default split category */
+ if (!txn->current_split) qif_save_str(txn->default_split->catstr);
+ break;
+ case 'M': /* M : default split memo */
+ if (!txn->current_split) qif_save_str(txn->default_split->memo);
+ break;
+ case 'T': /* T : total transaction amount */
+ if (!txn->current_split && !qif_is_bad_numeric_string(line->line))
+ qif_save_str(txn->default_split->amountstr);
+ break;
+ case 'S': /* S : split category */
+ /* This implies a quicken-style "split transaction", so we're mostly
+ * going to ignore the default_split except for internal verification.
+ */
+ txn->current_split = qif_split_new();
+ txn->splits = g_list_prepend(txn->splits, txn->current_split);
+ qif_save_str(txn->current_split->catstr);
+ break;
+ case 'E': /* E : split memo */
+ if (txn->current_split)
+ qif_save_str(txn->current_split->memo);
+ break;
+ case '$': /* split amount */
+ if (txn->current_split && !qif_is_bad_numeric_string(line->line))
+ qif_save_str(txn->current_split->amountstr);
+ break;
+ default:
+ PERR("Unknown QIF transaction data at line %d: %s", line->lineno, line->line);
+ }
+ }
+
+ /* If we have no date string then there is no reason to do anything else */
+ if (txn->datestr) {
+ /* We delay processing the date and amount strings until later.. */
+
+ /* parse the category on each split */
+ for (node = txn->splits; node; node = node->next) {
+ split = node->data;
+ if (split->catstr)
+ qif_split_parse_category(ctx, split);
+ }
+ /* ... including the default split */
+ if (txn->default_split->catstr)
+ qif_split_parse_category(ctx, txn->default_split);
+
+ /* if this is the first transaction, then deal with the opening balance */
+ if (!ctx->parse_state || !ctx->current_acct) {
+ qif_process_opening_balance_txn(ctx, txn);
+
+ /* If we didn't actually succeed in finding an account then
+ * set a flag so we can go back later and look for it.
+ */
+ if (!ctx->current_acct)
+ qif_set_flag(ctx->parse_flags, QIF_F_TXN_NEEDS_ACCT);
+ }
+
+ /* Set the transaction's from account */
+ txn->from_acct = ctx->current_acct;
+
+ /* And add it to the process list */
+ ctx->parse_state = (gpointer)g_list_prepend((GList *)ctx->parse_state, txn);
+
+ } else
+ /* no date? Ignore this txn */
+ qif_txn_destroy((QifObject)txn);
+
+ return QIF_E_OK;
+}
+
+/* after we parse the amounts, fix up the transaction */
+void
+qif_txn_post_parse_amounts(QifTxn txn)
+{
+ QifSplit split, this_split;
+ GList *node;
+ gnc_numeric total;
+
+ if (txn->splits) {
+ /* We have a bunch of "far" splits -- maybe fix up the totals.. */
+ qif_txn_fix_amounts(txn, txn->default_split->amount);
+
+ /* Re-Compute the total for the "near" (default) split */
+ total = gnc_numeric_zero();
+ for (node = txn->splits; node; node = node->next) {
+ split = node->data;
+ split->value = split->amount;
+ total = gnc_numeric_add(total, split->amount, 0, GNC_DENOM_LCD);
+ }
+
+ /* And re-set the default-split amount */
+ txn->default_split->amount = gnc_numeric_neg(total);
+
+ } else {
+ /* not a split txn. Compute the "far" split by copying the "near"
+ * split and then moving the 'near' split to the far split.
+ */
+
+ /* First make a copy of this transaction and move the copy to the 'near' */
+ split = txn->default_split;
+ this_split = qif_split_copy(split);
+ txn->default_split = this_split;
+
+ /* then adjust the 'far' txn */
+ split->amount = gnc_numeric_neg(split->amount);
+ split->value = split->amount;
+ txn->splits = g_list_prepend(txn->splits, split);
+ }
+
+ /* Set the default-split value from the default-split amount */
+ txn->default_split->value = txn->default_split->amount;
+}
+
+/* This is called when we're done processing an account. We want
+ * to merge the transactions in the "parse_state" into the Qif Context
+ */
+static QifError
+qif_txn_end_acct(QifContext ctx)
+{
+ GList *node;
+ QifTxn txn;
+
+ g_return_val_if_fail(ctx, QIF_E_INTERNAL);
+
+ /* Return now if there is nothing to do. */
+ if (!ctx->parse_state) return QIF_E_OK;
+
+ /* Walk through the list of transactions. First check if it
+ * needs a from-account; then add it to the context.
+ */
+
+ for (node = (GList*)ctx->parse_state; node; node = node->next) {
+ txn = node->data;
+
+ /* If we need a from account, then set it.. */
+ if (ctx->parse_flags & QIF_F_TXN_NEEDS_ACCT && !txn->from_acct)
+ txn->from_acct = ctx->current_acct;
+
+ /* merge the txn into the context */
+ qif_object_list_insert(ctx, (QifObject)txn);
+ }
+
+ qif_clear_flag(ctx->parse_flags, QIF_F_TXN_NEEDS_ACCT);
+
+ /* clean up our state */
+ g_list_free((GList*)ctx->parse_state);
+ ctx->parse_state = NULL;
+
+ return QIF_E_OK;
+}
+
+/* Extra info in an Investment Transaction */
+static QifInvstTxn
+qif_invst_txn_new(void)
+{
+ QifInvstTxn itxn = g_new0(struct _QifInvstTxn, 1);
+
+ itxn->amount = gnc_numeric_zero();
+ itxn->d_amount = gnc_numeric_zero();
+ itxn->price = gnc_numeric_zero();
+ itxn->shares = gnc_numeric_zero();
+ itxn->commission = gnc_numeric_zero();
+
+ return itxn;
+}
+
+static void
+qif_txn_invst_destroy(QifInvstTxn itxn)
+{
+ if (!itxn) return;
+
+ g_free(itxn->amountstr);
+ g_free(itxn->d_amountstr);
+ g_free(itxn->pricestr);
+ g_free(itxn->sharesstr);
+ g_free(itxn->commissionstr);
+ g_free(itxn->security);
+
+ g_free(itxn);
+}
+
+static QifError
+qif_txn_invst_parse(QifContext ctx, GList *record)
+{
+ QifTxn txn;
+ QifInvstTxn itxn;
+ QifLine line;
+
+ char *cat = NULL;
+ char *cat_class = NULL;
+ gboolean cat_is_acct = FALSE;
+ char *miscx = NULL;
+ char *miscx_class = NULL;
+ gboolean miscx_is_acct = FALSE;
+
+ gboolean invalid_action = FALSE;
+
+ /* Cached account-type lists */
+ static GList *bank_list = NULL;
+
+ g_return_val_if_fail(ctx, QIF_E_INTERNAL);
+ g_return_val_if_fail(record, QIF_E_BADSTATE);
+
+ txn = qif_txn_new();
+ txn->txn_type = ctx->parse_type;
+ itxn = qif_invst_txn_new();
+ txn->invst_info = itxn;
+
+ for (; record; record = record->next) {
+ line = record->data;
+
+ switch (line->type) {
+ case 'D': /* D : transaction date */
+ qif_save_str(txn->datestr);
+ break;
+ case 'P': /* P : txn payee */
+ qif_save_str(txn->payee);
+ break;
+ case 'N': /* N : action */
+ itxn->action = qif_parse_action(line);
+ break;
+ case 'C': /* C : cleared flag */
+ txn->cleared = qif_parse_cleared(line);
+ break;
+ case 'M': /* M : memo */
+ if (!txn->current_split)
+ qif_save_str(txn->default_split->memo);
+ break;
+ case 'T': /* T : total amount */
+ if (!qif_is_bad_numeric_string(line->line))
+ qif_save_str(itxn->amountstr);
+ break;
+ case '$': /* $ : transfer amount */
+ if (!qif_is_bad_numeric_string(line->line))
+ qif_save_str(itxn->d_amountstr);
+ break;
+ case 'I': /* I : share price */
+ qif_save_str(itxn->pricestr);
+ break;
+ case 'Q': /* Q : number of shares */
+ qif_save_str(itxn->sharesstr);
+ break;
+ case 'Y': /* Y : name of security */
+ qif_save_str(itxn->security);
+ break;
+ case 'O': /* O : commission */
+ qif_save_str(itxn->commissionstr);
+ break;
+ case 'L': /* L : category */
+ if (!qif_parse_split_category(line->line,
+ &cat, &cat_is_acct, &cat_class,
+ &miscx, &miscx_is_acct, &miscx_class))
+ PERR("Failure parsing category at line %d: %s", line->lineno, line->line);
+ break;
+ default:
+ PERR("Unknown QIF Investment transaction data at line %d: %s",
+ line->lineno, line->line);
+ }
+ }
+
+ /* If we have no date string then there is no reason to do anything else */
+ if (txn->datestr && itxn->action != QIF_A_NONE) {
+
+ /* Make sure we've got a cached list */
+ if (bank_list == NULL)
+ bank_list = qif_parse_acct_type("__any_bank__", -1);
+
+ /* Make sure we've got a security name */
+ if (!itxn->security)
+ itxn->security = g_strdup(""); /* XXX */
+
+ /* find the NEAR account */
+ switch (itxn->action) {
+ case QIF_A_BUY: case QIF_A_BUYX: case QIF_A_REINVDIV: case QIF_A_REINVINT:
+ case QIF_A_REINVLG: case QIF_A_REINVMD: case QIF_A_REINVSG: case QIF_A_REINVSH:
+ case QIF_A_SELL: case QIF_A_SELLX: case QIF_A_SHRSIN: case QIF_A_SHRSOUT:
+ case QIF_A_STKSPLIT:
+ txn->from_acct = qif_default_stock_acct(ctx, itxn->security);
+ break;
+
+ case QIF_A_CGLONG: case QIF_A_CGMID: case QIF_A_CGSHORT: case QIF_A_DIV:
+ case QIF_A_INTINC: case QIF_A_MARGINT: case QIF_A_MISCEXP: case QIF_A_MISCINC:
+ case QIF_A_RTRNCAP: case QIF_A_XIN: case QIF_A_XOUT:
+ txn->from_acct = ctx->current_acct;
+ break;
+
+ case QIF_A_CGLONGX: case QIF_A_CGMIDX: case QIF_A_CGSHORTX: case QIF_A_DIVX:
+ case QIF_A_INTINCX: case QIF_A_MARGINTX: case QIF_A_RTRNCAPX:
+ txn->from_acct = find_or_make_acct(ctx, cat, bank_list);
+ cat = NULL;
+ break;
+
+ case QIF_A_MISCEXPX: case QIF_A_MISCINCX:
+ txn->from_acct = find_or_make_acct(ctx, miscx, bank_list);
+ miscx = NULL;
+ break;
+
+ default:
+ PERR("Unhandled Action: %d", itxn->action);
+ invalid_action = TRUE;
+ break;
+ }
+
+ /* find the FAR account */
+ itxn->far_cat_is_acct = TRUE;
+ switch (itxn->action) {
+ case QIF_A_BUY: case QIF_A_SELL:
+ itxn->far_cat.acct = ctx->current_acct;
+ break;
+
+ case QIF_A_BUYX: case QIF_A_MISCEXP: case QIF_A_MISCEXPX: case QIF_A_MISCINC:
+ case QIF_A_MISCINCX: case QIF_A_SELLX: case QIF_A_XIN: case QIF_A_XOUT:
+ itxn->far_cat.cat = find_or_make_cat(ctx, cat);
+ itxn->far_cat_is_acct = FALSE;
+ cat = NULL;
+ break;
+
+ case QIF_A_CGLONG: case QIF_A_CGLONGX: case QIF_A_REINVLG:
+ itxn->far_cat.acct = qif_default_cglong_acct(ctx, itxn->security);
+ break;
+
+ case QIF_A_CGMID: case QIF_A_CGMIDX: case QIF_A_REINVMD:
+ itxn->far_cat.acct = qif_default_cgmid_acct(ctx, itxn->security);
+ break;
+
+ case QIF_A_CGSHORT: case QIF_A_CGSHORTX: case QIF_A_REINVSG: case QIF_A_REINVSH:
+ itxn->far_cat.acct = qif_default_cgshort_acct(ctx, itxn->security);
+ break;
+
+ case QIF_A_DIV: case QIF_A_DIVX: case QIF_A_REINVDIV:
+ itxn->far_cat.acct = qif_default_dividend_acct(ctx, itxn->security);
+ break;
+
+ case QIF_A_INTINC: case QIF_A_INTINCX: case QIF_A_REINVINT:
+ itxn->far_cat.acct = qif_default_interest_acct(ctx, itxn->security);
+ break;
+
+ case QIF_A_MARGINT: case QIF_A_MARGINTX:
+ itxn->far_cat.acct = qif_default_margin_interest_acct(ctx);
+ break;
+
+ case QIF_A_RTRNCAP: case QIF_A_RTRNCAPX:
+ itxn->far_cat.acct = qif_default_capital_return_acct(ctx, itxn->security);
+ break;
+
+ case QIF_A_SHRSIN: case QIF_A_SHRSOUT:
+ itxn->far_cat.acct = qif_default_equity_holding(ctx, itxn->security);
+ break;
+
+ case QIF_A_STKSPLIT:
+ itxn->far_cat.acct = qif_default_stock_acct(ctx, itxn->security);
+ break;
+
+ default:
+ break;
+ }
+
+ /* If we dont have a far acct (or far category) then reset the flag */
+ if (!itxn->far_cat.obj)
+ itxn->far_cat_is_acct = FALSE;
+
+ /* If this is invalid then destroy it */
+ if (invalid_action)
+ qif_txn_destroy((QifObject)txn);
+ else
+ /* Add this transaction to the parse state for later processing */
+ ctx->parse_state = (gpointer)g_list_prepend((GList*)ctx->parse_state, txn);
+
+ } else {
+ /* no date? Just destroy it */
+ qif_txn_destroy((QifObject)txn);
+ }
+
+ /* Free parsed strings.. */
+ g_free(cat);
+ g_free(cat_class);
+ g_free(miscx);
+ g_free(miscx_class);
+
+ return QIF_E_OK;
+}
+
+
+void
+qif_invst_txn_setup_splits(QifContext ctx, QifTxn txn)
+{
+ QifInvstTxn itxn;
+ QifSplit near_split, far_split, comm_split;
+
+ gnc_numeric split_value;
+
+ g_return_if_fail(ctx);
+ g_return_if_fail(txn);
+ g_return_if_fail(txn->invst_info);
+
+ itxn = txn->invst_info;
+
+ /* Compute the share value, because we'll probably need it */
+ split_value = gnc_numeric_mul(itxn->shares, itxn->price, 0, GNC_DENOM_REDUCE);
+
+ /* Make sure that "amount" is a valid "transaction amount" */
+ if (!itxn->amountstr && itxn->d_amountstr)
+ itxn->amount = itxn->d_amount;
+
+ /* near and far splits.. for simplicity */
+ near_split = txn->default_split;
+ far_split = qif_split_new();
+
+ /* And now fill in the "near" and "far" splits. In particular we need
+ *
+ * NEAR: txn->from_acct, near_split->amount, value
+ * FAR: cat, far_split->amount, value
+ */
+ switch (itxn->action) {
+ case QIF_A_BUY: case QIF_A_BUYX: case QIF_A_REINVDIV: case QIF_A_REINVINT:
+ case QIF_A_REINVLG: case QIF_A_REINVMD: case QIF_A_REINVSG: case QIF_A_REINVSH:
+ case QIF_A_SHRSIN:
+ near_split->amount = itxn->shares;
+ near_split->value = split_value;
+ far_split->amount = far_split->value = gnc_numeric_neg(itxn->amount);
+ break;
+
+ case QIF_A_SELL: case QIF_A_SELLX: case QIF_A_SHRSOUT:
+ near_split->amount = gnc_numeric_neg(itxn->shares);
+ near_split->value = gnc_numeric_neg(split_value);
+ far_split->amount = far_split->value = itxn->amount;
+ break;
+
+ case QIF_A_CGLONG: case QIF_A_CGLONGX: case QIF_A_CGMID: case QIF_A_CGMIDX:
+ case QIF_A_CGSHORT: case QIF_A_CGSHORTX: case QIF_A_DIV: case QIF_A_DIVX:
+ case QIF_A_INTINC: case QIF_A_INTINCX: case QIF_A_MISCINC: case QIF_A_MISCINCX:
+ case QIF_A_RTRNCAP: case QIF_A_RTRNCAPX: case QIF_A_XIN:
+ near_split->amount = near_split->value = itxn->amount;
+ far_split->amount = far_split->value = gnc_numeric_neg(itxn->amount);
+ break;
+
+ case QIF_A_MARGINT: case QIF_A_MARGINTX: case QIF_A_MISCEXP:
+ case QIF_A_MISCEXPX: case QIF_A_XOUT:
+ near_split->amount = near_split->value = gnc_numeric_neg(itxn->amount);
+ far_split->amount = far_split->value = itxn->amount;
+ break;
+
+ case QIF_A_STKSPLIT:
+ /* QIF just specifies the split ratio, not the number of shares
+ * in and out, so we have to fetch the number of shares from the
+ * security account.. FEH!
+ */
+
+ near_split->value = gnc_numeric_neg(split_value);
+ far_split->value = split_value;
+
+ /* XXX: FIXME: compute in-shares/out-shares based on ratio here:
+ *
+ * splitratio = num-shares / 10;
+ * in_shares = gnc_account_get_balance(near_acct);
+ * out_shares = in_shares * splitratio;
+ *
+ * near_split->amount = out_shares;
+ * far_split->amount = gnc_numeric_neg(in_shares);
+ *
+ * We know (later) that near_split == txn->default_split and
+ * far_split == txn->splits->data, so we'll just special-case this
+ * kind of txn when we convert to GNC later.
+ */
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* Just make sure to set that it's an account, not a category */
+ far_split->cat.obj = itxn->far_cat.obj;
+ if (itxn->far_cat_is_acct)
+ far_split->cat_is_acct = TRUE;
+
+ /* make the commission split if we need it, then add it to the split-list */
+ if (itxn->commissionstr) {
+ comm_split = qif_split_new();
+ comm_split->cat.acct = qif_default_commission_acct(ctx);
+ comm_split->cat_is_acct = TRUE;
+ comm_split->amount = itxn->commission;
+ comm_split->value = itxn->commission;
+
+ txn->splits = g_list_prepend(txn->splits, comm_split);
+ }
+
+ /* Push the "far split" into the txn split-list */
+ txn->splits = g_list_prepend(txn->splits, far_split);
+}
+
+
+/* Other handlers */
+static void
+qif_autoswitch_set(QifContext ctx)
+{
+ qif_set_flag(ctx->parse_flags, QIF_F_IGNORE_ACCOUNTS);
+}
+
+static void
+qif_autoswitch_clear(QifContext ctx)
+{
+ qif_clear_flag(ctx->parse_flags, QIF_F_IGNORE_ACCOUNTS);
+}
+
+/********************************************************************************
+ * find or make ...
+ */
+
+QifAccount
+find_or_make_acct(QifContext ctx, char *name, GList *types)
+{
+ QifAccount res;
+
+ res = (QifAccount)qif_object_map_lookup(ctx, QIF_O_ACCOUNT, name);
+ if (res)
+ g_free(name);
+ else {
+ res = qif_account_new();
+ res->name = name;
+ res->type_list = types;
+
+ qif_object_map_insert(ctx, name, (QifObject)res);
+ }
+
+ return res;
+}
+
+QifCategory
+find_or_make_cat(QifContext ctx, char *name)
+{
+ QifCategory res;
+
+ res = (QifCategory)qif_object_map_lookup(ctx, QIF_O_CATEGORY, name);
+ if (res)
+ g_free(name);
+ else {
+ res = qif_cat_new();
+
+ res->name = name;
+
+ qif_object_map_insert(ctx, name, (QifObject)res);
+ }
+
+ return res;
+}
+
+QifClass
+find_or_make_class(QifContext ctx, char *name)
+{
+ QifClass res;
+
+ res = (QifClass)qif_object_map_lookup(ctx, QIF_O_CLASS, name);
+ if (res)
+ g_free(name);
+ else {
+ res = qif_class_new();
+ res->name = name;
+ qif_object_map_insert(ctx, name, (QifObject)res);
+ }
+ return res;
+}
+
+/*****************************************************************************/
+
+/*
+ * initialize handlers
+ */
+void
+qif_object_init(void)
+{
+ static struct {
+ QifType type;
+ struct _QifHandler handler;
+ } handlers[] = {
+ { QIF_TYPE_BANK, { qif_txn_init, qif_txn_parse, qif_txn_end_acct } },
+ { QIF_TYPE_CASH, { qif_txn_init, qif_txn_parse, qif_txn_end_acct } },
+ { QIF_TYPE_CCARD, { qif_txn_init, qif_txn_parse, qif_txn_end_acct } },
+ { QIF_TYPE_INVST, { qif_txn_init, qif_txn_invst_parse, qif_txn_end_acct } },
+ { QIF_TYPE_PORT, { qif_txn_init, qif_txn_invst_parse, qif_txn_end_acct } },
+ { QIF_TYPE_OTH_A, { qif_txn_init, qif_txn_parse, qif_txn_end_acct } },
+ { QIF_TYPE_OTH_L, { qif_txn_init, qif_txn_parse, qif_txn_end_acct } },
+ { QIF_TYPE_CLASS, { NULL, qif_class_parse, NULL } },
+ { QIF_TYPE_CAT, { NULL, qif_cat_parse, NULL } },
+ { QIF_TYPE_SECURITY, { NULL, qif_security_parse, NULL } },
+ { QIF_ACCOUNT, { NULL, qif_account_parse, NULL } },
+ { QIF_AUTOSWITCH, { qif_autoswitch_set, NULL, NULL } },
+ { QIF_CLEAR_AUTOSWITCH, { qif_autoswitch_clear, NULL, NULL } },
+ { -1, {NULL} },
+ };
+
+ (void)handlers; /* XXX */
+
+}
View
44 src/import-export/qif/qif-objects.h
@@ -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
647 src/import-export/qif/qif-parse.c
@@ -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