gnucash maint: Multiple changes pushed
John Ralls
jralls at code.gnucash.org
Fri Jun 15 18:21:26 EDT 2018
Updated via https://github.com/Gnucash/gnucash/commit/7ce8c9d3 (commit)
via https://github.com/Gnucash/gnucash/commit/d6de324b (commit)
via https://github.com/Gnucash/gnucash/commit/1f8f6817 (commit)
via https://github.com/Gnucash/gnucash/commit/9f1bfddc (commit)
from https://github.com/Gnucash/gnucash/commit/edd439a0 (commit)
commit 7ce8c9d3363ddb388933b8f5e41ddf8607d07f8f
Author: John Ralls <jralls at ceridwen.us>
Date: Fri Jun 15 15:21:14 2018 -0700
Bug 796595 - QIF Import Select Account button to add a new account...
is labled gnc-account-new but should be New.
Made it "New Account".
diff --git a/gnucash/gtkbuilder/dialog-account-picker.glade b/gnucash/gtkbuilder/dialog-account-picker.glade
index 0bd5aa9..6144047 100644
--- a/gnucash/gtkbuilder/dialog-account-picker.glade
+++ b/gnucash/gtkbuilder/dialog-account-picker.glade
@@ -154,7 +154,7 @@
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="newbutton">
- <property name="label">gnc-account-new</property>
+ <property name="label" translatable="yes">New Account</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
commit d6de324b3283af318e44ca16bd85db6eaf3f75b1
Author: John Ralls <jralls at ceridwen.us>
Date: Fri Jun 15 14:22:43 2018 -0700
Bug 795272 - QIF importer causes application crash if action is invalid.
Fixes the crash and also pauses after loading if there are load errors.
The load "protection" catches exceptions other than bad-date so that it's
real protection.
Check for unbalanced transactions (i.e. with only one account) and don't
try to match if there is; report the error in the error log space in the
assistant.
Don't proceed to finding duplicates if the new account tree hasn't been
created, there aren't any.
diff --git a/gnucash/import-export/qif-imp/assistant-qif-import.c b/gnucash/import-export/qif-imp/assistant-qif-import.c
index 21f3e74..1ab3788 100644
--- a/gnucash/import-export/qif-imp/assistant-qif-import.c
+++ b/gnucash/import-export/qif-imp/assistant-qif-import.c
@@ -59,6 +59,7 @@
#include "gnc-prefs.h"
#include "gnc-ui.h"
#include "guile-mappings.h"
+#include <gfec.h>
#include "swig-runtime.h"
@@ -1067,6 +1068,11 @@ gnc_ui_qif_import_commodity_update(QIFImportWindow * wind)
}
}
+static void
+_gfec_error_handler(const char *message)
+{
+ PERR("qif-import:qif-to-gnc-undo encountered an error: %s", message);
+}
/****************************************************************
* gnc_ui_qif_import_convert_undo
@@ -1082,7 +1088,7 @@ gnc_ui_qif_import_convert_undo(QIFImportWindow * wind)
gnc_set_busy_cursor(NULL, TRUE);
/* Undo the conversion. */
- scm_call_1(undo, wind->imported_account_tree);
+ gfec_apply(undo, wind->imported_account_tree, _gfec_error_handler);
/* There's no imported account tree any more. */
scm_gc_unprotect_object(wind->imported_account_tree);
@@ -1916,6 +1922,7 @@ gnc_ui_qif_import_load_progress_start_cb(GtkButton * button,
wind->ask_date_format = TRUE;
}
+ wind->load_stop = TRUE;
}
else
{
@@ -1937,22 +1944,27 @@ gnc_ui_qif_import_load_progress_start_cb(GtkButton * button,
gtk_widget_set_sensitive(wind->load_pause, FALSE);
gtk_widget_set_sensitive(wind->load_start, FALSE);
- if (wind->load_stop == FALSE)
- {
- /* The file was loaded successfully. */
- gnc_progress_dialog_set_sub(wind->load_progress, _("Loading completed"));
- gnc_progress_dialog_set_value(wind->load_progress, 1);
+ /* The file was loaded successfully. */
+ gnc_progress_dialog_set_sub(wind->load_progress, _("Loading completed"));
+ gnc_progress_dialog_set_value(wind->load_progress, 1);
- scm_gc_unprotect_object(wind->imported_files);
- wind->imported_files = imported_files;
- scm_gc_protect_object(wind->imported_files);
+ scm_gc_unprotect_object(wind->imported_files);
+ wind->imported_files = imported_files;
+ scm_gc_protect_object(wind->imported_files);
- gtk_widget_set_sensitive(wind->load_pause, FALSE);
- wind->busy = FALSE;
+ gtk_widget_set_sensitive(wind->load_pause, FALSE);
+ wind->busy = FALSE;
+
+ if (wind->load_stop == FALSE)
+ {
/* Auto step to next page */
gtk_assistant_set_current_page (assistant, num + 1);
}
+ else
+ {
+ wind->load_stop = FALSE;
+ }
}
@@ -2934,58 +2946,59 @@ gnc_ui_qif_import_convert_progress_start_cb(GtkButton * button,
wind->busy = FALSE;
wind->load_stop = TRUE;
}
+ if (wind->load_stop == FALSE)
+ {
+ /* Save the imported account tree. */
+ scm_gc_unprotect_object(wind->imported_account_tree);
+ wind->imported_account_tree = retval;
+ scm_gc_protect_object(wind->imported_account_tree);
- /* Save the imported account tree. */
- scm_gc_unprotect_object(wind->imported_account_tree);
- wind->imported_account_tree = retval;
- scm_gc_protect_object(wind->imported_account_tree);
-
- /*
- * Detect potentially duplicated transactions.
- */
+ /*
+ * Detect potentially duplicated transactions.
+ */
- /* This step will fill the remainder of the bar. */
- gnc_progress_dialog_push(wind->convert_progress, 1);
- retval = scm_call_3(find_duplicates,
- scm_c_eval_string("(gnc-get-current-root-account)"),
- wind->imported_account_tree, progress);
- gnc_progress_dialog_pop(wind->convert_progress);
+ /* This step will fill the remainder of the bar. */
+ gnc_progress_dialog_push(wind->convert_progress, 1);
+ retval = scm_call_3(find_duplicates,
+ scm_c_eval_string("(gnc-get-current-root-account)"),
+ wind->imported_account_tree, progress);
+ gnc_progress_dialog_pop(wind->convert_progress);
- /* Save the results. */
- scm_gc_unprotect_object(wind->match_transactions);
- wind->match_transactions = retval;
- scm_gc_protect_object(wind->match_transactions);
+ /* Save the results. */
+ scm_gc_unprotect_object(wind->match_transactions);
+ wind->match_transactions = retval;
+ scm_gc_protect_object(wind->match_transactions);
- if (retval == SCM_BOOL_T)
- {
- /* Canceled by the user. */
- gtk_widget_set_sensitive(wind->convert_pause, FALSE);
- gnc_progress_dialog_set_sub(wind->convert_progress, _("Canceling"));
- wind->busy = FALSE;
- wind->load_stop = TRUE;
- }
- else if (retval == SCM_BOOL_F)
- {
- /* An error occurred during duplicate checking. */
+ if (retval == SCM_BOOL_T)
+ {
+ /* Canceled by the user. */
+ gtk_widget_set_sensitive(wind->convert_pause, FALSE);
+ gnc_progress_dialog_set_sub(wind->convert_progress, _("Canceling"));
+ wind->busy = FALSE;
+ wind->load_stop = TRUE;
+ }
+ else if (retval == SCM_BOOL_F)
+ {
+ /* An error occurred during duplicate checking. */
- /* Remove any converted data. */
- gnc_progress_dialog_set_sub(wind->convert_progress, _("Cleaning up"));
- gnc_ui_qif_import_convert_undo(wind);
+ /* Remove any converted data. */
+ gnc_progress_dialog_set_sub(wind->convert_progress, _("Cleaning up"));
+ gnc_ui_qif_import_convert_undo(wind);
- /* Inform the user. */
- gnc_progress_dialog_append_log(wind->convert_progress,
- _( "A bug was detected while detecting duplicates."));
- gnc_progress_dialog_set_sub(wind->convert_progress, _("Failed"));
- gnc_progress_dialog_reset_value(wind->convert_progress);
- gnc_error_dialog (GTK_WINDOW (assistant), "%s",
- _( "A bug was detected while detecting duplicates."));
- /* FIXME: How should we request that the user report this problem? */
+ /* Inform the user. */
+ gnc_progress_dialog_append_log(wind->convert_progress,
+ _( "A bug was detected while detecting duplicates."));
+ gnc_progress_dialog_set_sub(wind->convert_progress, _("Failed"));
+ gnc_progress_dialog_reset_value(wind->convert_progress);
+ gnc_error_dialog (GTK_WINDOW (assistant), "%s",
+ _( "A bug was detected while detecting duplicates."));
+ /* FIXME: How should we request that the user report this problem? */
- gtk_widget_set_sensitive(wind->convert_pause, FALSE);
- wind->busy = FALSE;
- wind->load_stop = TRUE;
+ gtk_widget_set_sensitive(wind->convert_pause, FALSE);
+ wind->busy = FALSE;
+ wind->load_stop = TRUE;
+ }
}
-
/* Enable the Assistant Forward Button */
gtk_assistant_set_page_complete (assistant, page, TRUE);
diff --git a/gnucash/import-export/qif-imp/qif-to-gnc.scm b/gnucash/import-export/qif-imp/qif-to-gnc.scm
index 5f7e3f1..f5d0794 100644
--- a/gnucash/import-export/qif-imp/qif-to-gnc.scm
+++ b/gnucash/import-export/qif-imp/qif-to-gnc.scm
@@ -255,7 +255,11 @@
(length (qif-file:xtns b))))))
(work-to-do 0)
(work-done 0))
-
+ ;; Log any errors
+ (define (errorproc message)
+ (if (string? message)
+ (qif-import:log progress-dialog "qif-import:qif-to-gnc"
+ message)))
;; This procedure handles progress reporting, pause, and cancel.
(define (update-progress)
(set! work-done (+ 1 work-done))
@@ -379,7 +383,7 @@
(update-progress)
(if (not (qif-xtn:mark xtn))
- (qif-import:mark-matching-xtns xtn rest))
+ (qif-import:mark-matching-xtns xtn rest errorproc))
(if (not (null? (cdr rest)))
(xloop (car rest) (cdr rest)))))
@@ -431,7 +435,7 @@
(lambda ()
(catch 'cancel
(lambda ()
- (catch 'bad-date private-convert (lambda (key . args) key)))
+ (catch #t private-convert (lambda (key . args) key)))
(lambda (key . args) #t)))))
@@ -810,7 +814,7 @@
;; mark them so they won't be imported.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(define (qif-import:mark-matching-xtns xtn candidate-xtns)
+(define (qif-import:mark-matching-xtns xtn candidate-xtns errorproc)
(let splitloop ((splits-left (qif-xtn:splits xtn)))
;; splits-left starts out as all the splits of this transaction.
@@ -822,7 +826,7 @@
(qif-split:category-is-account? (car splits-left)))
(set! splits-left
(qif-import:mark-some-splits
- splits-left xtn candidate-xtns))
+ splits-left xtn candidate-xtns errorproc))
(set! splits-left (cdr splits-left))))
(if (not (null? splits-left))
@@ -835,7 +839,7 @@
;; don't get imported.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(define (qif-import:mark-some-splits splits xtn candidate-xtns)
+(define (qif-import:mark-some-splits splits xtn candidate-xtns errorproc)
(let* ((n- (lambda (n) (gnc-numeric-neg n)))
(nsub (lambda (a b) (gnc-numeric-sub a b 0 GNC-DENOM-LCD)))
(n+ (lambda (a b) (gnc-numeric-add a b 0 GNC-DENOM-LCD)))
@@ -910,17 +914,20 @@
;; this is the grind loop. Go over every unmarked transaction in
;; the candidate-xtns list.
(let xtn-loop ((xtns candidate-xtns))
- (if (and (not (qif-xtn:mark (car xtns)))
- (string=? (qif-xtn:from-acct (car xtns)) far-acct-name))
- (begin
- (set! how
- (qif-import:xtn-has-matches? (car xtns) near-acct-name
- date amount group-amount))
- (if how
- (begin
- (qif-import:merge-and-mark-xtns xtn same-acct-splits
- (car xtns) how)
- (set! done #t)))))
+ (if (not (and far-acct-name near-acct-name))
+ (if errorproc
+ (errorproc "Transaction with no or only one associated account."))
+ (if (and (not (qif-xtn:mark (car xtns))))
+ (string=? (qif-xtn:from-acct (car xtns)) far-acct-name)
+ (begin
+ (set! how
+ (qif-import:xtn-has-matches? (car xtns) near-acct-name
+ date amount group-amount))
+ (if how
+ (begin
+ (qif-import:merge-and-mark-xtns xtn same-acct-splits
+ (car xtns) how)
+ (set! done #t))))))
;; iterate with the next transaction
(if (and (not done)
(not (null? (cdr xtns))))
commit 1f8f681732bd52fb7d858b9eeb3269c973565712
Author: John Ralls <jralls at ceridwen.us>
Date: Fri Jun 15 11:32:37 2018 -0700
Remove abandoned C-Language QIF implementation.
diff --git a/gnucash/import-export/CMakeLists.txt b/gnucash/import-export/CMakeLists.txt
index 3b52ed8..cca8359 100644
--- a/gnucash/import-export/CMakeLists.txt
+++ b/gnucash/import-export/CMakeLists.txt
@@ -10,7 +10,6 @@ add_subdirectory(csv-imp)
add_subdirectory(customer-import)
add_subdirectory(log-replay)
add_subdirectory(ofx)
-add_subdirectory(qif)
add_subdirectory(qif-imp)
diff --git a/gnucash/import-export/qif/CMakeLists.txt b/gnucash/import-export/qif/CMakeLists.txt
deleted file mode 100644
index 608e14c..0000000
--- a/gnucash/import-export/qif/CMakeLists.txt
+++ /dev/null
@@ -1,43 +0,0 @@
-
-#Tests for this directory are not run.
-add_subdirectory(test)
-
-set(qif_SOURCES
- qif-context.c
- qif-defaults.c
- qif-file.c
- qif-objects.c
- qif-parse.c
-)
-
-# Add dependency on config.h
-set_source_files_properties (${qif_SOURCES} PROPERTIES OBJECT_DEPENDS ${CONFIG_H})
-
-set(qif_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
-)
-
-add_library(gncmod-qif ${qif_noinst_HEADERS} ${qif_SOURCES})
-
-target_link_libraries(gncmod-qif gncmod-generic-import gncmod-engine ${GLIB2_LDFLAGS})
-
-target_compile_definitions(gncmod-qif PRIVATE -DG_LOG_DOMAIN=\"gnc.import.qif\")
-
-if (APPLE)
- set_target_properties (gncmod-qif PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash")
-endif()
-
-install(TARGETS gncmod-qif
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
-# No headers to install.
-
-set_local_dist(qif_DIST_local CMakeLists.txt ${qif_SOURCES} ${qif_noinst_HEADERS})
-set(qif_DIST ${qif_DIST_local} ${test_qif_DIST} PARENT_SCOPE)
diff --git a/gnucash/import-export/qif/qif-context.c b/gnucash/import-export/qif/qif-context.c
deleted file mode 100644
index 17fa472..0000000
--- a/gnucash/import-export/qif/qif-context.c
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * qif-context.c -- create/destroy QIF Contexts
- *
- * Written By: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <glib.h>
-
-#include "qif-import-p.h"
-#include "qif-objects-p.h"
-
-static void qif_object_map_get_helper(gpointer key, gpointer value, gpointer listp);
-
-QifContext
-qif_context_new(void)
-{
- QifContext ctx = g_new0(struct _QifContext, 1);
-
- 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);
-
- return ctx;
-}
-
-void
-qif_context_destroy(QifContext ctx)
-{
- GList *node, *temp;
- QifContext fctx;
-
- if (!ctx) return;
-
- /* First, try to destroy all the children contexts */
- for (node = ctx->files; node; node = temp)
- {
- fctx = node->data;
- temp = node->next;
- qif_context_destroy(fctx);
- }
-
- /* ok, at this point we're actually destroying this context. */
-
- /* 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);
-
- /* Remove us from our parent context */
- if (ctx->parent)
- ctx->parent->files = g_list_remove(ctx->parent->files, ctx);
-
- g_free(ctx->filename);
-
- g_assert(ctx->files == NULL);
- g_free(ctx);
-}
-
-static GList *
-qif_context_get_foo_helper(QifContext ctx, GFunc get_helper)
-{
- GHashTable *ht;
- GList *node, *list = NULL;
- QifContext fctx;
-
- g_return_val_if_fail(ctx, NULL);
- g_return_val_if_fail(ctx->parsed, NULL);
- g_return_val_if_fail(get_helper, NULL);
-
- ht = g_hash_table_new(g_direct_hash, g_direct_equal);
-
- for (node = ctx->files; node; node = node->next)
- {
- fctx = node->data;
- qif_object_list_foreach(fctx, QIF_O_TXN, get_helper, ht);
- }
-
- g_hash_table_foreach(ht, qif_object_map_get_helper, &list);
- g_hash_table_destroy(ht);
-
- return list;
-}
-
-static void
-qif_get_accts_helper(gpointer obj, gpointer htp)
-{
- QifTxn txn = obj;
- QifSplit split;
- GHashTable *ht = htp;
- GList *node;
-
- if (txn->from_acct)
- g_hash_table_insert(ht, txn->from_acct, txn->from_acct);
-
- /* The default_split is using the from_acct, so we can ignore it */
-
- for (node = txn->splits; node; node = node->next)
- {
- split = node->data;
- if (split->cat.obj && split->cat_is_acct)
- g_hash_table_insert(ht, split->cat.acct, split->cat.acct);
- }
-}
-
-GList *
-qif_context_get_accounts(QifContext ctx)
-{
- return qif_context_get_foo_helper(ctx, qif_get_accts_helper);
-}
-
-static void
-qif_get_cats_helper(gpointer obj, gpointer htp)
-{
- QifTxn txn = obj;
- QifSplit split;
- GHashTable *ht = htp;
- GList *node;
-
- /* default_split uses from_acct, so no categories */
-
- for (node = txn->splits; node; node = node->next)
- {
- split = node->data;
- if (split->cat.obj && !split->cat_is_acct)
- g_hash_table_insert(ht, split->cat.cat, split->cat.cat);
- }
-}
-
-GList *
-qif_context_get_categories(QifContext ctx)
-{
- return qif_context_get_foo_helper(ctx, qif_get_cats_helper);
-}
-
-/*****************************************************************************/
-
-/*
- * Insert and remove a QifObject from the Object Maps in this Qif Context
- */
-
-gint
-qif_object_map_count(QifContext ctx, const char *type)
-{
- GHashTable *ht;
-
- g_return_val_if_fail(ctx, 0);
- g_return_val_if_fail(ctx->object_maps, 0);
- g_return_val_if_fail(type, 0);
-
- ht = g_hash_table_lookup(ctx->object_maps, type);
- if (!ht)
- return 0;
-
- return g_hash_table_size(ht);
-}
-
-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_maps, qif_object_map_remove_all, NULL);
- g_hash_table_destroy(ctx->object_maps);
-}
-
-/*****************************************************************************/
-
-/*
- * Insert and remove a QifObject from the Object Lists in this Qif Context
- */
-
-void
-qif_object_list_reverse(QifContext ctx, const char *type)
-{
- 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);
- list = g_list_reverse(list);
- g_hash_table_insert(ctx->object_lists, (gpointer)type, list);
-}
-
-gint
-qif_object_list_count(QifContext ctx, const char *type)
-{
- GList *list;
-
- g_return_val_if_fail(ctx, 0);
- g_return_val_if_fail(ctx->object_lists, 0);
- g_return_val_if_fail(type, 0);
-
- list = g_hash_table_lookup(ctx->object_lists, type);
- return g_list_length(list);
-}
-
-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);
-}
diff --git a/gnucash/import-export/qif/qif-defaults.c b/gnucash/import-export/qif/qif-defaults.c
deleted file mode 100644
index e29c141..0000000
--- a/gnucash/import-export/qif/qif-defaults.c
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * qif-defaults.c -- QIF Defaults -- default accounts...
- *
- * Created by: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <glib.h>
-#include <glib/gi18n.h>
-
-#include "gnc-helpers.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);
-}
-
diff --git a/gnucash/import-export/qif/qif-defaults.h b/gnucash/import-export/qif/qif-defaults.h
deleted file mode 100644
index 30c6562..0000000
--- a/gnucash/import-export/qif/qif-defaults.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * qif-defaults.h -- QIF Defaults -- default accounts...
- *
- * Created by: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#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 */
diff --git a/gnucash/import-export/qif/qif-file.c b/gnucash/import-export/qif/qif-file.c
deleted file mode 100644
index 775f0da..0000000
--- a/gnucash/import-export/qif/qif-file.c
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * qif-file.c -- parse a QIF File into its pieces
- *
- * Written by: Derek Atkins <derek@@ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <glib.h>
-#include <glib/gstdio.h>
-#include <string.h>
-
-#include "gnc-engine.h"
-
-#include "qif-import-p.h"
-#include "qif-objects-p.h"
-
-static QofLogModule log_module = GNC_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 QIF_E_OK on success or some other QIF Error.
- */
-static 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, QIF_E_BADARGS);
- g_return_val_if_fail(f, QIF_E_BADARGS);
-
- 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);
-
- if (err == QIF_E_OK)
- qif_object_list_reverse(ctx, QIF_O_TXN);
-
- return err;
-}
-
-static QifError
-qif_import_file(QifContext ctx, const char *filename)
-{
- QifError err;
- FILE *fp;
-
- g_return_val_if_fail(ctx, QIF_E_BADARGS);
- g_return_val_if_fail(filename, QIF_E_BADARGS);
- g_return_val_if_fail(*filename, QIF_E_BADARGS);
-
- /* Open the file */
- fp = g_fopen(filename, "r");
- if (fp == NULL)
- return QIF_E_NOFILE;
-
- ctx->filename = g_strdup(filename);
-
- /* read the file */
- err = qif_read_file(ctx, fp);
-
- /* close the file */
- fclose(fp);
-
- return err;
-}
-
-
-QifContext
-qif_file_new(QifContext ctx, const char *filename)
-{
- QifContext fctx;
-
- g_return_val_if_fail(ctx, NULL);
- g_return_val_if_fail(filename, NULL);
-
- fctx = qif_context_new();
-
- /* we should assume that we've got a bank account... just in case.. */
- qif_parse_bangtype(fctx, "!type:bank");
-
- /* Open the file */
- if (qif_import_file(fctx, filename) != QIF_E_OK)
- {
- qif_context_destroy(fctx);
- fctx = NULL;
- }
-
- /* Return the new context */
- if (fctx)
- {
- ctx->files = g_list_prepend(ctx->files, fctx);
- fctx->parent = ctx;
-
- /* Make sure the file gets merged into the parent */
- ctx->parsed = FALSE;
- }
-
- return fctx;
-}
-
-QifError
-qif_file_parse(QifContext ctx, gpointer ui_args)
-{
- g_return_val_if_fail(ctx, QIF_E_BADARGS);
- g_return_val_if_fail(!qif_file_needs_account(ctx), QIF_E_BADSTATE);
-
- qif_parse_all(ctx, ui_args);
- ctx->parsed = TRUE;
-
- return QIF_E_OK;
-}
-
-gboolean
-qif_file_needs_account(QifContext ctx)
-{
- g_return_val_if_fail(ctx, FALSE);
-
- return ((ctx->parse_flags & QIF_F_TXN_NEEDS_ACCT) ||
- (ctx->parse_flags & QIF_F_ITXN_NEEDS_ACCT));
-}
-
-const char *
-qif_file_filename(QifContext ctx)
-{
- g_return_val_if_fail(ctx, NULL);
- return ctx->filename;
-}
-
-static void
-set_txn_acct(gpointer obj, gpointer arg)
-{
- QifTxn txn = obj;
- QifAccount acct = arg;
-
- if (!txn->from_acct)
- txn->from_acct = acct;
-}
-
-void
-qif_file_set_default_account(QifContext ctx, const char *acct_name)
-{
- QifAccount acct;
-
- g_return_if_fail(ctx);
- g_return_if_fail(acct_name);
-
- if (! qif_file_needs_account(ctx)) return;
-
- acct = find_or_make_acct(ctx, g_strdup(acct_name),
- qif_parse_acct_type_guess(ctx->parse_type));
-
- qif_object_list_foreach(ctx, QIF_O_TXN, set_txn_acct, acct);
-
- qif_clear_flag(ctx->parse_flags, QIF_F_TXN_NEEDS_ACCT);
- qif_clear_flag(ctx->parse_flags, QIF_F_ITXN_NEEDS_ACCT);
-}
diff --git a/gnucash/import-export/qif/qif-file.h b/gnucash/import-export/qif/qif-file.h
deleted file mode 100644
index c4b85f1..0000000
--- a/gnucash/import-export/qif/qif-file.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/* qif-import-p.h -- a QIF Importer module (private headers)
- *
- * Written By: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#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 */
diff --git a/gnucash/import-export/qif/qif-import-p.h b/gnucash/import-export/qif/qif-import-p.h
deleted file mode 100644
index 96c6314..0000000
--- a/gnucash/import-export/qif/qif-import-p.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* qif-import-p.h -- a QIF Importer module (private headers)
- *
- * Written By: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#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 */
- char * filename;
- FILE * fp;
- gint lineno;
-
- /* This describes what we are parsing right now */
- QifType parse_type;
- QifHandler handler;
- gpointer parse_state;
-
- /* A bunch of flags for the current handler */
- gint parse_flags;
- gboolean parsed;
-
- /* The current and "opening balance" account */
- QifAccount current_acct;
- QifAccount opening_bal_acct;
-
- /* HashTable of Maps of data objects */
- GHashTable * object_maps;
-
- /* HashTable of Lists of data objects */
- GHashTable * object_lists;
-
- /* List of files */
- GList *files;
-};
-
-/* Object Maps */
-gint qif_object_map_count(QifContext ctx, const char *type);
-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_reverse(QifContext ctx, const char *type);
-gint qif_object_list_count(QifContext ctx, const char *type);
-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);
-
-/* Set and clear flags in bit-flags */
-#define qif_set_flag(i,f) (i |= f)
-#define qif_clear_flag(i,f) (i &= ~f)
-
-#endif /* QIF_IMPORT_P_H */
diff --git a/gnucash/import-export/qif/qif-import.h b/gnucash/import-export/qif/qif-import.h
deleted file mode 100644
index 9602029..0000000
--- a/gnucash/import-export/qif/qif-import.h
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * qif-import.h -- a QIF Import module
- *
- * Written By: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#ifndef QIF_IMPORT_H
-#define QIF_IMPORT_H
-
-#include <stdio.h>
-#include "qof.h"
-
-typedef enum
-{
- QIF_TYPE_BANK = 1,
- 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)
-#define QIF_F_ITXN_NEEDS_ACCT (1 << 2)
-
-/* 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,
- QIF_E_BADARGS,
- QIF_E_NOFILE,
-} 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 */
-
-/* Create a QIF Import Context */
-QifContext qif_context_new(void);
-void qif_context_destroy(QifContext ctx);
-
-/* Open and read a QIF File. You must pass in the parent
- * context; it will return the child (file) context
- */
-QifContext qif_file_new(QifContext ctx, const char* filename);
-
-/* Does a qif-file need a default QIF account? */
-gboolean qif_file_needs_account(QifContext ctx);
-
-/* Return the filename of the QIF file */
-const char * qif_file_filename(QifContext ctx);
-
-/* Provide a default QIF Account for the QIF File */
-void qif_file_set_default_account(QifContext ctx, const char *acct_name);
-
-/* Parse the QIF File */
-QifError qif_file_parse(QifContext ctx, gpointer ui_arg);
-
-/* Merge all the qif-files from the children and into the context */
-void qif_parse_merge_files(QifContext ctx);
-
-/* Obtain the list of USED QifAccounts and QifCategories. Finds all
- * references from the transactions in the QifContext. The returned
- * GList must be freed by the caller.
- */
-GList *qif_context_get_accounts(QifContext ctx);
-GList *qif_context_get_categories(QifContext ctx);
-
-#endif /* QIF_IMPORT_H */
diff --git a/gnucash/import-export/qif/qif-objects-p.h b/gnucash/import-export/qif/qif-objects-p.h
deleted file mode 100644
index dbd3aa4..0000000
--- a/gnucash/import-export/qif/qif-objects-p.h
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * qif-objects-p.h -- Private header: QIF objects for the QIF importer
- *
- * Written By: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#ifndef QIF_OBJECTS_P_H
-#define QIF_OBJECTS_P_H
-
-#include "qof.h"
-
-#include "qif-import.h"
-#include "qif-objects.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;
- char * catstr;
-
- union
- {
- QifObject obj;
- QifCategory cat;
- QifAccount acct;
- } far_cat;
- gboolean far_cat_is_acct;
-};
-
-/* to be run after parsing all the dates and amounts */
-void qif_txn_setup_splits(QifTxn txn);
-void qif_invst_txn_setup_splits(QifContext ctx, QifTxn txn);
-
-#endif /* QIF_OBJECTS_P_H */
diff --git a/gnucash/import-export/qif/qif-objects.c b/gnucash/import-export/qif/qif-objects.c
deleted file mode 100644
index 0fad928..0000000
--- a/gnucash/import-export/qif/qif-objects.c
+++ /dev/null
@@ -1,1468 +0,0 @@
-/*
- * qif-objects.c -- Objects for the QIF Importer
- *
- * Written by: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <glib.h>
-#include <string.h>
-#include "Account.h"
-
-#include "gnc-engine.h"
-
-#include "qif-import-p.h"
-#include "qif-objects-p.h"
-#include "qif-defaults.h"
-
-static QofLogModule log_module = GNC_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; \
-})
-
-/* 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.
- */
-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);
- 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.
- */
-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.
- */
-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.
- */
-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->amountstr) s->amountstr = g_strdup(s->amountstr);
- 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)
-{
- 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 transfer 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 = NULL; /* We know that ctx->current_acct is NULL */
-
- 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 we found an opening balance account then set up the context.
- * 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 (cur_acct)
- {
- ctx->opening_bal_acct = cur_acct;
- ctx->current_acct = cur_acct;
- }
- else
- qif_set_flag(ctx->parse_flags, QIF_F_TXN_NEEDS_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_HOW_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 we don't have an account, then deal with the opening balance */
- if (!ctx->current_acct)
- qif_process_opening_balance_txn(ctx, txn);
-
- /* Set the transaction's from account */
- txn->from_acct = ctx->current_acct;
-
- /* And add it to the process list */
- ctx->parse_state = g_list_prepend(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 splits */
-void
-qif_txn_setup_splits(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_HOW_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;
- gboolean txn_needs_acct;
-
- 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.
- */
-
- txn_needs_acct = (ctx->parse_flags & QIF_F_TXN_NEEDS_ACCT);
-
- /* Invert the list so we're working in the right order */
- ctx->parse_state = g_list_reverse(ctx->parse_state);
-
- for (node = ctx->parse_state; node; node = node->next)
- {
- txn = node->data;
-
- /* If we need a from account, then set it.. */
- if (txn_needs_acct && ctx->opening_bal_acct && !txn->from_acct)
- txn->from_acct = ctx->opening_bal_acct;
-
- /* merge the txn into the context (prepends to the list) */
- qif_object_list_insert(ctx, (QifObject)txn);
- }
-
- if (txn_needs_acct && ctx->opening_bal_acct)
- qif_clear_flag(ctx->parse_flags, QIF_F_TXN_NEEDS_ACCT);
-
- /* clean up our state */
- g_list_free(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->catstr);
-
- g_free(itxn);
-}
-
-static QifError
-qif_txn_invst_parse(QifContext ctx, GList *record)
-{
- QifTxn txn;
- QifInvstTxn itxn;
- QifLine line;
-
- 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 */
- qif_save_str(itxn->catstr);
- 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 security name */
- if (!itxn->security)
- itxn->security = g_strdup(""); /* XXX */
-
- /* if we don't have a from account, then mark the fact that
- * we'll need one later.
- */
- if (ctx->current_acct)
- txn->from_acct = ctx->current_acct;
- else
- qif_set_flag(ctx->parse_flags, QIF_F_ITXN_NEEDS_ACCT);
-
- /* Add this transaction to the parse state for later processing */
- ctx->parse_state = g_list_prepend(ctx->parse_state, txn);
-
- }
- else
- {
- /* no date? Just destroy it */
- qif_txn_destroy((QifObject)txn);
- }
-
- return QIF_E_OK;
-}
-
-
-void
-qif_invst_txn_setup_splits(QifContext ctx, QifTxn txn)
-{
- QifInvstTxn itxn;
- QifSplit near_split, far_split, comm_split;
- QifAccount from_acct;
-
- char *cat = NULL;
- char *cat_class = NULL;
- gboolean cat_is_acct = FALSE;
- char *miscx = NULL;
- char *miscx_class = NULL;
- gboolean miscx_is_acct = FALSE;
-
- /* Cached account-type lists */
- static GList *bank_list = NULL;
-
- 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_HOW_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();
- from_acct = txn->from_acct;
-
- /* Parse the category string */
- if (!qif_parse_split_category(itxn->catstr,
- &cat, &cat_is_acct, &cat_class,
- &miscx, &miscx_is_acct, &miscx_class))
- PERR("Failure parsing category: %s", itxn->catstr);
-
- /* Make sure we've got a cached list */
- if (bank_list == NULL)
- bank_list = qif_parse_acct_type("__any_bank__", -1);
-
- /* 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 = from_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);
- 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 = from_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 don't have a far acct (or far category) then reset the flag */
- if (!itxn->far_cat.obj)
- itxn->far_cat_is_acct = FALSE;
-
- /* 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);
-
- /* Free parsed strings.. */
- g_free(cat);
- g_free(cat_class);
- g_free(miscx);
- g_free(miscx_class);
-}
-
-
-/* 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)
-{
- int i;
- 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 } },
- { 0, {NULL, NULL, NULL} }
- };
-
- for (i = 0; handlers[i].type > 0; i++)
- {
- if (handlers[i].type <= 0)
- {
- PERR("Invalid type?!? (%d @ %d)", handlers[i].type, i);
- }
- else
- qif_register_handler(handlers[i].type, &(handlers[i].handler));
- }
-}
diff --git a/gnucash/import-export/qif/qif-objects.h b/gnucash/import-export/qif/qif-objects.h
deleted file mode 100644
index 2d28e59..0000000
--- a/gnucash/import-export/qif/qif-objects.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * qif-objects.h -- QIF objects for the QIF importer
- *
- * Written By: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#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);
-
-/* merge the object into the context. Returns the object that's in
- * the context, which is either the supplied object or the
- * already-existing object.
- */
-QifAccount qif_account_merge(QifContext ctx, QifAccount acct);
-QifCategory qif_cat_merge(QifContext ctx, QifCategory cat);
-QifClass qif_class_merge(QifContext ctx, QifClass qclass);
-QifSecurity qif_security_merge(QifContext ctx, QifSecurity security);
-
-#endif /* QIF_OBJECTS_H */
diff --git a/gnucash/import-export/qif/qif-parse.c b/gnucash/import-export/qif/qif-parse.c
deleted file mode 100644
index f291c86..0000000
--- a/gnucash/import-export/qif/qif-parse.c
+++ /dev/null
@@ -1,935 +0,0 @@
-/*
- * qif-parse.c -- parse QIF
- *
- * Written by: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <glib.h>
-#include <glib/gi18n.h>
-#include <string.h>
-
-/* For regex */
-#include <sys/types.h>
-#include <regex.h>
-
-#include <stdarg.h>
-
-#include "gnc-engine.h"
-#include "gnc-ui-util.h"
-
-#include "qif-import-p.h"
-#include "qif-objects-p.h"
-
-#include "import-parse.h"
-
-static QofLogModule log_module = GNC_MOD_IMPORT;
-
-/* An array of handlers for the various bang-types */
-static QifHandler qif_handlers[QIF_TYPE_MAX+1] = { NULL };
-
-/* Parser Regular Expressions */
-static gboolean qifp_regex_compiled = FALSE;
-static regex_t category_regex;
-
-/* 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)
-{
- if (type <= 0 || type > QIF_TYPE_MAX)
- {
- PERR("Invalid type: %d", type);
- return;
- }
- qif_handlers[type] = handler;
-}
-
-static void
-compile_regex()
-{
- regcomp(&category_regex,
- "^ *(\\[)?([^]/|]*)(]?)(/([^|]*))?(\\|(\\[)?([^]/]*)(]?)(/(.*))?)? *$",
- REG_EXTENDED);
-
- qifp_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);
-
- /* Translators FIXME: It is unclear whether these strings should
- really be translated, and if yes, into which translation. */
- 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("cvrshrt", QIF_A_BUY);
- QIF_ADD_ACT("kauf", QIF_A_BUY);
- QIF_ADD_ACT("buyx", QIF_A_BUYX);
- QIF_ADD_ACT("cvrshrtx", 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("cash", 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("shtsell", QIF_A_SELL);
- QIF_ADD_ACT("verkauf", QIF_A_SELL); /* verkaufen */
- QIF_ADD_ACT("sellx", QIF_A_SELLX);
- QIF_ADD_ACT("shtsellx", 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("contribx", QIF_A_XIN);
- QIF_ADD_ACT("xout", QIF_A_XOUT);
- QIF_ADD_ACT("withdrwx", 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_atype_map = g_hash_table_new(g_str_hash, g_str_equal);
- g_assert(qif_atype_map);
-
- QIF_ADD_ATYPE("bank", make_list(1, ACCT_TYPE_BANK));
- QIF_ADD_ATYPE("port", make_list(1, ACCT_TYPE_BANK));
- QIF_ADD_ATYPE("cash", make_list(1, ACCT_TYPE_CASH));
- QIF_ADD_ATYPE("ccard", make_list(1, ACCT_TYPE_CREDIT));
- QIF_ADD_ATYPE("invst", make_list(3, ACCT_TYPE_BANK, ACCT_TYPE_STOCK,
- ACCT_TYPE_MUTUAL));
- QIF_ADD_ATYPE("oth a", make_list(3, ACCT_TYPE_ASSET, ACCT_TYPE_BANK,
- ACCT_TYPE_CASH));
- QIF_ADD_ATYPE("oth l", make_list(2, ACCT_TYPE_LIABILITY, ACCT_TYPE_CREDIT));
- QIF_ADD_ATYPE("mutual", make_list(3, ACCT_TYPE_BANK, ACCT_TYPE_MUTUAL,
- ACCT_TYPE_STOCK));
-
- /* Internal types */
- QIF_ADD_ATYPE("__any_bank__", make_list(5, ACCT_TYPE_BANK, ACCT_TYPE_CREDIT,
- ACCT_TYPE_CASH, ACCT_TYPE_ASSET,
- ACCT_TYPE_LIABILITY));
- QIF_ADD_ATYPE("__all__", make_list(7, ACCT_TYPE_BANK, ACCT_TYPE_CREDIT,
- ACCT_TYPE_CASH, ACCT_TYPE_ASSET,
- ACCT_TYPE_LIABILITY, ACCT_TYPE_STOCK,
- ACCT_TYPE_MUTUAL));
- QIF_ADD_ATYPE("__stock__", make_list(2, ACCT_TYPE_STOCK, ACCT_TYPE_MUTUAL));
- QIF_ADD_ATYPE("__income__", make_list(1, ACCT_TYPE_INCOME));
- QIF_ADD_ATYPE("__expense__", make_list(1, ACCT_TYPE_EXPENSE));
- QIF_ADD_ATYPE("__equity__", make_list(1, ACCT_TYPE_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_utf8_strdown(line + 1, -1);
- g_strstrip(bangtype);
-
- /* In some cases we get "!Type Bank" -- change the space to a colon */
- if (!strncmp(bangtype, "type ", 5))
- 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 (!qifp_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[2].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_utf8_strdown(line->line, -1);
- g_strstrip(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_utf8_strdown(str, -1);
- g_strstrip(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_setup_splits(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);
-}
-
-typedef struct
-{
- QifContext ctx;
- GList * list;
- const char* type;
-} qif_merge_t;
-
-static void
-qif_merge_accts(gpointer key, gpointer value, gpointer data)
-{
- qif_merge_t *merge = data;
- QifAccount acct = value;
-
- /* Merge into the context. Remember items moved into the parent */
- if (qif_account_merge(merge->ctx, acct) == acct)
- merge->list = g_list_prepend(merge->list, acct->name);
-}
-
-static void
-qif_merge_cats(gpointer key, gpointer value, gpointer data)
-{
- qif_merge_t *merge = data;
- QifCategory cat = value;
-
- /* Merge into the context. Remember items moved into the parent */
- if (qif_cat_merge(merge->ctx, cat) == cat)
- merge->list = g_list_prepend(merge->list, cat->name);
-}
-
-static void
-qif_merge_classes(gpointer key, gpointer value, gpointer data)
-{
- qif_merge_t *merge = data;
- QifClass qclass = value;
-
- /* Merge into the context. Remember items moved into the parent */
- if (qif_class_merge(merge->ctx, qclass) == qclass)
- merge->list = g_list_prepend(merge->list, qclass->name);
-}
-
-static void
-qif_merge_secs(gpointer key, gpointer value, gpointer data)
-{
- qif_merge_t *merge = data;
- QifSecurity sec = value;
-
- /* Merge into the context. Remember items moved into the parent */
- if (qif_security_merge(merge->ctx, sec) == sec)
- merge->list = g_list_prepend(merge->list, sec->name);
-}
-
-static void
-qif_merge_del(gpointer obj, gpointer data)
-{
- qif_merge_t *merge = data;
- const char *name = obj;
-
- qif_object_map_remove(merge->ctx, merge->type, name);
-}
-
-static void
-qif_massage_split(QifSplit split, QifContext ctx)
-{
- const char *type = QIF_O_CATEGORY;
- char *name;
-
- if (split->cat.obj)
- {
- if (split->cat_is_acct)
- {
- type = QIF_O_ACCOUNT;
- name = split->cat.acct->name;
- }
- else
- name = split->cat.cat->name;
-
- split->cat.obj = qif_object_map_lookup(ctx, type, name);
- }
-
- if (split->cat_class)
- {
- split->cat_class = (QifClass) qif_object_map_lookup(ctx, QIF_O_CLASS,
- split->cat_class->name);
- }
-}
-
-static void
-qif_massage_itxn(QifInvstTxn itxn, QifContext ctx)
-{
- const char *type = QIF_O_CATEGORY;
- char *name;
-
- if (itxn->far_cat.obj)
- {
- if (itxn->far_cat_is_acct)
- {
- type = QIF_O_ACCOUNT;
- name = itxn->far_cat.acct->name;
- }
- else
- name = itxn->far_cat.cat->name;
-
- itxn->far_cat.obj = qif_object_map_lookup(ctx, type, name);
- }
-}
-
-static void
-qif_massage_txn(gpointer obj, gpointer data)
-{
- QifTxn txn = obj;
- QifContext ctx = data;
- QifSplit split;
- GList *node;
-
- if (txn->from_acct)
- txn->from_acct = (QifAccount) qif_object_map_lookup(ctx, QIF_O_ACCOUNT,
- txn->from_acct->name);
-
- if (txn->invst_info)
- qif_massage_itxn(txn->invst_info, ctx);
-
- if (txn->default_split)
- qif_massage_split(txn->default_split, ctx);
-
- for (node = txn->splits; node; node = node->next)
- {
- split = node->data;
- qif_massage_split(split, ctx);
- }
-}
-
-void
-qif_parse_merge_files(QifContext ctx)
-{
- GList *node;
- GList *accts = NULL;
- GList *cats = NULL;
- GList *classes = NULL;
- GList *securities = NULL;
- QifContext fctx;
-
- qif_merge_t merge;
-
- g_return_if_fail(ctx);
-
- /* Make sure each of the "file" contexts have been parsed.
- * note that we don't care about OUR context -- we can run this
- * process multiple times safely.
- */
- for (node = ctx->files; node; node = node->next)
- {
- fctx = node->data;
- g_return_if_fail(fctx->parsed);
- }
-
-
- /* Iterate over each file. Merge the Accounts, Categories, Classes,
- * Securities, and Transactions into the top-level context. Be sure
- * to re-point all Transaction/Split category/class/account pointers
- * to the new top-level item. Then be sure to remove the
- * "duplicated" items so we don't double-free (as we don't refcount,
- * either).
- */
- for (node = ctx->files; node; node = node->next)
- {
- fctx = node->data;
-
- /* Merge accts, categories, classes, and securities */
-
- merge.ctx = ctx;
- merge.list = NULL;
- qif_object_map_foreach(fctx, QIF_O_ACCOUNT, qif_merge_accts, &merge);
- accts = merge.list;
-
- merge.list = NULL;
- qif_object_map_foreach(fctx, QIF_O_CATEGORY, qif_merge_cats, &merge);
- cats = merge.list;
-
- merge.list = NULL;
- qif_object_map_foreach(fctx, QIF_O_CLASS, qif_merge_classes, &merge);
- classes = merge.list;
-
- merge.list = NULL;
- qif_object_map_foreach(fctx, QIF_O_SECURITY, qif_merge_secs, &merge);
- securities = merge.list;
-
-
- /* repoint the transactions to the merged context data */
- qif_object_list_foreach(fctx, QIF_O_TXN, qif_massage_txn, ctx);
-
-
- /* then remove from the file context objects referenced in the top context */
- merge.ctx = fctx;
- merge.type = QIF_O_ACCOUNT;
- g_list_foreach(accts, qif_merge_del, &merge);
- g_list_free(accts);
-
- merge.type = QIF_O_CATEGORY;
- g_list_foreach(cats, qif_merge_del, &merge);
- g_list_free(cats);
-
- merge.type = QIF_O_CLASS;
- g_list_foreach(classes, qif_merge_del, &merge);
- g_list_free(classes);
-
- merge.type = QIF_O_SECURITY;
- g_list_foreach(securities, qif_merge_del, &merge);
- g_list_free(securities);
-
- }
-
- /* We've been parsed */
- ctx->parsed = TRUE;
-}
diff --git a/gnucash/import-export/qif/qif-parse.h b/gnucash/import-export/qif/qif-parse.h
deleted file mode 100644
index 11d439c..0000000
--- a/gnucash/import-export/qif/qif-parse.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * qif-parse.h -- routines for parsing pieces of a QIF file
- *
- * Written By: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#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);
-
-/* Parse all objects */
-void qif_parse_all(QifContext ctx, gpointer ui_args);
-
-#endif /* QIF_PARSE_H */
diff --git a/gnucash/import-export/qif/test/CMakeLists.txt b/gnucash/import-export/qif/test/CMakeLists.txt
deleted file mode 100644
index d874ca3..0000000
--- a/gnucash/import-export/qif/test/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-
-set(QIF_TEST_INCLUDE_DIRS
- ${CMAKE_BINARY_DIR}/common
- ${CMAKE_SOURCE_DIR}/gnucash/import-export/qif
- ${CMAKE_SOURCE_DIR}/libgnucash/engine
- ${CMAKE_SOURCE_DIR}/common/test-core
- ${GLIB2_INCLUDE_DIRS}
-)
-set(QIF_TEST_LIBS gncmod-qif test-core)
-
-if (FALSE)
- # Tests for this directory are not run.
- gnc_add_test(test-link-qif test-link.c QIF_TEST_INCLUDE_DIRS QIF_TEST_LIBS)
- gnc_add_test(test-qif test-qif.c QIF_TEST_INCLUDE_DIRS QIF_TEST_LIBS
- GNC_TEST_FILES=${CMAKE_CURRENT_SOURCE_DIR}/test-files)
-endif()
-
-set_dist_list(test_qif_DIST CMakeLists.txt test-link.c test-qif.c test-files/test-1-bank-txn.qif)
diff --git a/gnucash/import-export/qif/test/test-files/test-1-bank-txn.qif b/gnucash/import-export/qif/test/test-files/test-1-bank-txn.qif
deleted file mode 100644
index 59592d0..0000000
--- a/gnucash/import-export/qif/test/test-files/test-1-bank-txn.qif
+++ /dev/null
@@ -1,6 +0,0 @@
-!Type:Bank
-D2003/01/27
-T123.45
-PTest Payee
-LTest Category
-^
diff --git a/gnucash/import-export/qif/test/test-link.c b/gnucash/import-export/qif/test/test-link.c
deleted file mode 100644
index fd55d42..0000000
--- a/gnucash/import-export/qif/test/test-link.c
+++ /dev/null
@@ -1,28 +0,0 @@
-/********************************************************************\
- * This program is free software; you can redistribute it and/or *
- * modify it under the terms of the GNU General Public License as *
- * published by the Free Software Foundation; either version 2 of *
- * the License, or (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License*
- * along with this program; if not, contact: *
- * *
- * Free Software Foundation Voice: +1-617-542-5942 *
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
- * Boston, MA 02110-1301, USA gnu at gnu.org *
- * *
-\********************************************************************/
-
-#include "qif-import.h"
-
-int
-main(int argc, char *argv[])
-{
- qif_context_new();
- return 0;
-}
diff --git a/gnucash/import-export/qif/test/test-qif.c b/gnucash/import-export/qif/test/test-qif.c
deleted file mode 100644
index c331bef..0000000
--- a/gnucash/import-export/qif/test/test-qif.c
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * test-qif.c -- Test the QIF Import routines.
- *
- * Created by: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#include <glib.h>
-#include <libguile.h>
-
-#include "gnc-module.h"
-#include "qif-import.h"
-#include "qif-import-p.h" /* Let's test some internal stuff, too */
-
-#include "test-stuff.h"
-
-/* XXX */
-extern void qif_object_init(void);
-
-static QifContext
-test_qif_load_file(QifContext ctx, const char *filename,
- gint txn_count, gint acct_count, gboolean needs_acct)
-{
- QifContext file;
-
- printf("qif loading \"%s\"...\n", filename);
- file = qif_file_new(ctx, filename);
- do_test(file != NULL, "failed to read file");
- if (!file) return NULL;
-
- do_test(qif_object_list_count(file, QIF_O_TXN) == txn_count,
- "Transaction count didn't match");
- do_test(qif_object_map_count(file, QIF_O_ACCOUNT) == acct_count,
- "Account count didn't match");
- do_test(qif_file_needs_account(file) == needs_acct,
- "Needs account flad didn't match");
-
- return file;
-}
-
-static void
-test_qif(void)
-{
- QifContext ctx, file;
- char *filename;
- const char *location = g_getenv("GNC_TEST_FILES");
- int i;
-
- ctx = qif_context_new();
- do_test(ctx != NULL, "failed to create the qif context");
- if (!ctx) return;
-
- if (!location)
- location = "test-files";
-
- for (i = 0; i < 1; i++)
- {
- filename = g_strdup_printf("%s/%s", location, "test-1-bank-txn.qif");
- file = test_qif_load_file(ctx, filename, 1, 0, TRUE);
- g_free(filename);
- if (!file) continue;
-
- if (qif_file_needs_account(file))
- qif_file_set_default_account(file, "test-1-bank-txn");
-
- do_test(qif_file_needs_account(file) == FALSE,
- "'Needs account' flag not cleared properly");
-
- do_test(qif_file_parse(file, NULL) == QIF_E_OK,
- "file failed to parse.");
- }
-
- qif_context_destroy(ctx);
-
- success("QIF test successful");
-}
-
-static void
-main_helper(void *closure, int argc, char **argv)
-{
- qif_object_init(); /* XXX:FIXME */
- test_qif();
- print_test_results();
- exit(get_rv());
-}
-
-int
-main(int argc, char **argv)
-{
- scm_boot_guile(argc, argv, main_helper, NULL);
- return 0;
-}
-
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 426b3f2..ede339f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -347,9 +347,7 @@ gnucash/import-export/ofx/gnc-ofx-import.c
gnucash/import-export/ofx/gnc-ofx-kvp.c
gnucash/import-export/ofx/gnc-plugin-ofx.c
gnucash/import-export/ofx/gschemas/org.gnucash.dialogs.import.ofx.gschema.xml.in
-gnucash/import-export/qif/qif-context.c
-gnucash/import-export/qif/qif-file.c
-gnucash/import-export/qif/qif-objects.c
+gnucash/import-export/qif-imp/.#assistant-qif-import.c
gnucash/import-export/qif-imp/assistant-qif-import.c
gnucash/import-export/qif-imp/dialog-account-picker.c
gnucash/import-export/qif-imp/gncmod-qif-import.c
@@ -486,8 +484,7 @@ gnucash/report/standard-reports/general-journal.scm
gnucash/report/standard-reports/general-ledger.scm
gnucash/report/standard-reports/income-gst-statement.scm
gnucash/report/standard-reports/income-statement.scm
-gnucash/report/standard-reports/net-barchart.scm
-gnucash/report/standard-reports/net-linechart.scm
+gnucash/report/standard-reports/net-charts.scm
gnucash/report/standard-reports/portfolio.scm
gnucash/report/standard-reports/price-scatter.scm
gnucash/report/standard-reports/register.scm
@@ -575,7 +572,6 @@ libgnucash/backend/sql/gnc-sql-result.cpp
libgnucash/backend/sql/gnc-tax-table-sql.cpp
libgnucash/backend/sql/gnc-transaction-sql.cpp
libgnucash/backend/sql/gnc-vendor-sql.cpp
-libgnucash/backend/xml/.#gnc-invoice-xml-v2.cpp
libgnucash/backend/xml/gnc-account-xml-v2.cpp
libgnucash/backend/xml/gnc-address-xml-v2.cpp
libgnucash/backend/xml/gnc-backend-xml.cpp
commit 9f1bfddc27965f50295f507b062d63b8612d1c9b
Author: John Ralls <jralls at ceridwen.us>
Date: Fri Jun 15 11:30:09 2018 -0700
Revert "Remove abandoned C-language QIF implementation."
Because it included some extraneous changes.
This reverts commit edd439a05e1468b1f55d0df7c05441b67aac8787.
diff --git a/gnucash/import-export/CMakeLists.txt b/gnucash/import-export/CMakeLists.txt
index cca8359..3b52ed8 100644
--- a/gnucash/import-export/CMakeLists.txt
+++ b/gnucash/import-export/CMakeLists.txt
@@ -10,6 +10,7 @@ add_subdirectory(csv-imp)
add_subdirectory(customer-import)
add_subdirectory(log-replay)
add_subdirectory(ofx)
+add_subdirectory(qif)
add_subdirectory(qif-imp)
diff --git a/gnucash/import-export/qif-imp/assistant-qif-import.c b/gnucash/import-export/qif-imp/assistant-qif-import.c
index e424182..21f3e74 100644
--- a/gnucash/import-export/qif-imp/assistant-qif-import.c
+++ b/gnucash/import-export/qif-imp/assistant-qif-import.c
@@ -59,7 +59,6 @@
#include "gnc-prefs.h"
#include "gnc-ui.h"
#include "guile-mappings.h"
-#include <gfec.h>
#include "swig-runtime.h"
@@ -1068,12 +1067,6 @@ gnc_ui_qif_import_commodity_update(QIFImportWindow * wind)
}
}
-static void
-_gfec_error_handler(const char *message)
-{
- PERR("qif-import:qif-to-gnc-undo encountered an error: %s", message);
-}
-
/****************************************************************
* gnc_ui_qif_import_convert_undo
@@ -1089,7 +1082,7 @@ gnc_ui_qif_import_convert_undo(QIFImportWindow * wind)
gnc_set_busy_cursor(NULL, TRUE);
/* Undo the conversion. */
- gfec_apply(undo, wind->imported_account_tree, _gfec_error_handler);
+ scm_call_1(undo, wind->imported_account_tree);
/* There's no imported account tree any more. */
scm_gc_unprotect_object(wind->imported_account_tree);
diff --git a/gnucash/import-export/qif-imp/qif-to-gnc.scm b/gnucash/import-export/qif-imp/qif-to-gnc.scm
index 442cb47..5f7e3f1 100644
--- a/gnucash/import-export/qif-imp/qif-to-gnc.scm
+++ b/gnucash/import-export/qif-imp/qif-to-gnc.scm
@@ -910,9 +910,7 @@
;; this is the grind loop. Go over every unmarked transaction in
;; the candidate-xtns list.
(let xtn-loop ((xtns candidate-xtns))
- (if (and (and
- (and far-acct-name near acct-name)
- (not (qif-xtn:mark (car xtns))))
+ (if (and (not (qif-xtn:mark (car xtns)))
(string=? (qif-xtn:from-acct (car xtns)) far-acct-name))
(begin
(set! how
diff --git a/gnucash/import-export/qif/CMakeLists.txt b/gnucash/import-export/qif/CMakeLists.txt
new file mode 100644
index 0000000..608e14c
--- /dev/null
+++ b/gnucash/import-export/qif/CMakeLists.txt
@@ -0,0 +1,43 @@
+
+#Tests for this directory are not run.
+add_subdirectory(test)
+
+set(qif_SOURCES
+ qif-context.c
+ qif-defaults.c
+ qif-file.c
+ qif-objects.c
+ qif-parse.c
+)
+
+# Add dependency on config.h
+set_source_files_properties (${qif_SOURCES} PROPERTIES OBJECT_DEPENDS ${CONFIG_H})
+
+set(qif_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
+)
+
+add_library(gncmod-qif ${qif_noinst_HEADERS} ${qif_SOURCES})
+
+target_link_libraries(gncmod-qif gncmod-generic-import gncmod-engine ${GLIB2_LDFLAGS})
+
+target_compile_definitions(gncmod-qif PRIVATE -DG_LOG_DOMAIN=\"gnc.import.qif\")
+
+if (APPLE)
+ set_target_properties (gncmod-qif PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/gnucash")
+endif()
+
+install(TARGETS gncmod-qif
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/gnucash
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+# No headers to install.
+
+set_local_dist(qif_DIST_local CMakeLists.txt ${qif_SOURCES} ${qif_noinst_HEADERS})
+set(qif_DIST ${qif_DIST_local} ${test_qif_DIST} PARENT_SCOPE)
diff --git a/gnucash/import-export/qif/qif-context.c b/gnucash/import-export/qif/qif-context.c
new file mode 100644
index 0000000..17fa472
--- /dev/null
+++ b/gnucash/import-export/qif/qif-context.c
@@ -0,0 +1,417 @@
+/*
+ * qif-context.c -- create/destroy QIF Contexts
+ *
+ * Written By: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+
+#include "qif-import-p.h"
+#include "qif-objects-p.h"
+
+static void qif_object_map_get_helper(gpointer key, gpointer value, gpointer listp);
+
+QifContext
+qif_context_new(void)
+{
+ QifContext ctx = g_new0(struct _QifContext, 1);
+
+ 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);
+
+ return ctx;
+}
+
+void
+qif_context_destroy(QifContext ctx)
+{
+ GList *node, *temp;
+ QifContext fctx;
+
+ if (!ctx) return;
+
+ /* First, try to destroy all the children contexts */
+ for (node = ctx->files; node; node = temp)
+ {
+ fctx = node->data;
+ temp = node->next;
+ qif_context_destroy(fctx);
+ }
+
+ /* ok, at this point we're actually destroying this context. */
+
+ /* 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);
+
+ /* Remove us from our parent context */
+ if (ctx->parent)
+ ctx->parent->files = g_list_remove(ctx->parent->files, ctx);
+
+ g_free(ctx->filename);
+
+ g_assert(ctx->files == NULL);
+ g_free(ctx);
+}
+
+static GList *
+qif_context_get_foo_helper(QifContext ctx, GFunc get_helper)
+{
+ GHashTable *ht;
+ GList *node, *list = NULL;
+ QifContext fctx;
+
+ g_return_val_if_fail(ctx, NULL);
+ g_return_val_if_fail(ctx->parsed, NULL);
+ g_return_val_if_fail(get_helper, NULL);
+
+ ht = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+ for (node = ctx->files; node; node = node->next)
+ {
+ fctx = node->data;
+ qif_object_list_foreach(fctx, QIF_O_TXN, get_helper, ht);
+ }
+
+ g_hash_table_foreach(ht, qif_object_map_get_helper, &list);
+ g_hash_table_destroy(ht);
+
+ return list;
+}
+
+static void
+qif_get_accts_helper(gpointer obj, gpointer htp)
+{
+ QifTxn txn = obj;
+ QifSplit split;
+ GHashTable *ht = htp;
+ GList *node;
+
+ if (txn->from_acct)
+ g_hash_table_insert(ht, txn->from_acct, txn->from_acct);
+
+ /* The default_split is using the from_acct, so we can ignore it */
+
+ for (node = txn->splits; node; node = node->next)
+ {
+ split = node->data;
+ if (split->cat.obj && split->cat_is_acct)
+ g_hash_table_insert(ht, split->cat.acct, split->cat.acct);
+ }
+}
+
+GList *
+qif_context_get_accounts(QifContext ctx)
+{
+ return qif_context_get_foo_helper(ctx, qif_get_accts_helper);
+}
+
+static void
+qif_get_cats_helper(gpointer obj, gpointer htp)
+{
+ QifTxn txn = obj;
+ QifSplit split;
+ GHashTable *ht = htp;
+ GList *node;
+
+ /* default_split uses from_acct, so no categories */
+
+ for (node = txn->splits; node; node = node->next)
+ {
+ split = node->data;
+ if (split->cat.obj && !split->cat_is_acct)
+ g_hash_table_insert(ht, split->cat.cat, split->cat.cat);
+ }
+}
+
+GList *
+qif_context_get_categories(QifContext ctx)
+{
+ return qif_context_get_foo_helper(ctx, qif_get_cats_helper);
+}
+
+/*****************************************************************************/
+
+/*
+ * Insert and remove a QifObject from the Object Maps in this Qif Context
+ */
+
+gint
+qif_object_map_count(QifContext ctx, const char *type)
+{
+ GHashTable *ht;
+
+ g_return_val_if_fail(ctx, 0);
+ g_return_val_if_fail(ctx->object_maps, 0);
+ g_return_val_if_fail(type, 0);
+
+ ht = g_hash_table_lookup(ctx->object_maps, type);
+ if (!ht)
+ return 0;
+
+ return g_hash_table_size(ht);
+}
+
+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_maps, qif_object_map_remove_all, NULL);
+ g_hash_table_destroy(ctx->object_maps);
+}
+
+/*****************************************************************************/
+
+/*
+ * Insert and remove a QifObject from the Object Lists in this Qif Context
+ */
+
+void
+qif_object_list_reverse(QifContext ctx, const char *type)
+{
+ 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);
+ list = g_list_reverse(list);
+ g_hash_table_insert(ctx->object_lists, (gpointer)type, list);
+}
+
+gint
+qif_object_list_count(QifContext ctx, const char *type)
+{
+ GList *list;
+
+ g_return_val_if_fail(ctx, 0);
+ g_return_val_if_fail(ctx->object_lists, 0);
+ g_return_val_if_fail(type, 0);
+
+ list = g_hash_table_lookup(ctx->object_lists, type);
+ return g_list_length(list);
+}
+
+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);
+}
diff --git a/gnucash/import-export/qif/qif-defaults.c b/gnucash/import-export/qif/qif-defaults.c
new file mode 100644
index 0000000..e29c141
--- /dev/null
+++ b/gnucash/import-export/qif/qif-defaults.c
@@ -0,0 +1,152 @@
+/*
+ * qif-defaults.c -- QIF Defaults -- default accounts...
+ *
+ * Created by: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "gnc-helpers.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);
+}
+
diff --git a/gnucash/import-export/qif/qif-defaults.h b/gnucash/import-export/qif/qif-defaults.h
new file mode 100644
index 0000000..30c6562
--- /dev/null
+++ b/gnucash/import-export/qif/qif-defaults.h
@@ -0,0 +1,44 @@
+/*
+ * qif-defaults.h -- QIF Defaults -- default accounts...
+ *
+ * Created by: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#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 */
diff --git a/gnucash/import-export/qif/qif-file.c b/gnucash/import-export/qif/qif-file.c
new file mode 100644
index 0000000..775f0da
--- /dev/null
+++ b/gnucash/import-export/qif/qif-file.c
@@ -0,0 +1,326 @@
+/*
+ * qif-file.c -- parse a QIF File into its pieces
+ *
+ * Written by: Derek Atkins <derek@@ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <string.h>
+
+#include "gnc-engine.h"
+
+#include "qif-import-p.h"
+#include "qif-objects-p.h"
+
+static QofLogModule log_module = GNC_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 QIF_E_OK on success or some other QIF Error.
+ */
+static 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, QIF_E_BADARGS);
+ g_return_val_if_fail(f, QIF_E_BADARGS);
+
+ 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);
+
+ if (err == QIF_E_OK)
+ qif_object_list_reverse(ctx, QIF_O_TXN);
+
+ return err;
+}
+
+static QifError
+qif_import_file(QifContext ctx, const char *filename)
+{
+ QifError err;
+ FILE *fp;
+
+ g_return_val_if_fail(ctx, QIF_E_BADARGS);
+ g_return_val_if_fail(filename, QIF_E_BADARGS);
+ g_return_val_if_fail(*filename, QIF_E_BADARGS);
+
+ /* Open the file */
+ fp = g_fopen(filename, "r");
+ if (fp == NULL)
+ return QIF_E_NOFILE;
+
+ ctx->filename = g_strdup(filename);
+
+ /* read the file */
+ err = qif_read_file(ctx, fp);
+
+ /* close the file */
+ fclose(fp);
+
+ return err;
+}
+
+
+QifContext
+qif_file_new(QifContext ctx, const char *filename)
+{
+ QifContext fctx;
+
+ g_return_val_if_fail(ctx, NULL);
+ g_return_val_if_fail(filename, NULL);
+
+ fctx = qif_context_new();
+
+ /* we should assume that we've got a bank account... just in case.. */
+ qif_parse_bangtype(fctx, "!type:bank");
+
+ /* Open the file */
+ if (qif_import_file(fctx, filename) != QIF_E_OK)
+ {
+ qif_context_destroy(fctx);
+ fctx = NULL;
+ }
+
+ /* Return the new context */
+ if (fctx)
+ {
+ ctx->files = g_list_prepend(ctx->files, fctx);
+ fctx->parent = ctx;
+
+ /* Make sure the file gets merged into the parent */
+ ctx->parsed = FALSE;
+ }
+
+ return fctx;
+}
+
+QifError
+qif_file_parse(QifContext ctx, gpointer ui_args)
+{
+ g_return_val_if_fail(ctx, QIF_E_BADARGS);
+ g_return_val_if_fail(!qif_file_needs_account(ctx), QIF_E_BADSTATE);
+
+ qif_parse_all(ctx, ui_args);
+ ctx->parsed = TRUE;
+
+ return QIF_E_OK;
+}
+
+gboolean
+qif_file_needs_account(QifContext ctx)
+{
+ g_return_val_if_fail(ctx, FALSE);
+
+ return ((ctx->parse_flags & QIF_F_TXN_NEEDS_ACCT) ||
+ (ctx->parse_flags & QIF_F_ITXN_NEEDS_ACCT));
+}
+
+const char *
+qif_file_filename(QifContext ctx)
+{
+ g_return_val_if_fail(ctx, NULL);
+ return ctx->filename;
+}
+
+static void
+set_txn_acct(gpointer obj, gpointer arg)
+{
+ QifTxn txn = obj;
+ QifAccount acct = arg;
+
+ if (!txn->from_acct)
+ txn->from_acct = acct;
+}
+
+void
+qif_file_set_default_account(QifContext ctx, const char *acct_name)
+{
+ QifAccount acct;
+
+ g_return_if_fail(ctx);
+ g_return_if_fail(acct_name);
+
+ if (! qif_file_needs_account(ctx)) return;
+
+ acct = find_or_make_acct(ctx, g_strdup(acct_name),
+ qif_parse_acct_type_guess(ctx->parse_type));
+
+ qif_object_list_foreach(ctx, QIF_O_TXN, set_txn_acct, acct);
+
+ qif_clear_flag(ctx->parse_flags, QIF_F_TXN_NEEDS_ACCT);
+ qif_clear_flag(ctx->parse_flags, QIF_F_ITXN_NEEDS_ACCT);
+}
diff --git a/gnucash/import-export/qif/qif-file.h b/gnucash/import-export/qif/qif-file.h
new file mode 100644
index 0000000..c4b85f1
--- /dev/null
+++ b/gnucash/import-export/qif/qif-file.h
@@ -0,0 +1,36 @@
+/* qif-import-p.h -- a QIF Importer module (private headers)
+ *
+ * Written By: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#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 */
diff --git a/gnucash/import-export/qif/qif-import-p.h b/gnucash/import-export/qif/qif-import-p.h
new file mode 100644
index 0000000..96c6314
--- /dev/null
+++ b/gnucash/import-export/qif/qif-import-p.h
@@ -0,0 +1,100 @@
+/* qif-import-p.h -- a QIF Importer module (private headers)
+ *
+ * Written By: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#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 */
+ char * filename;
+ FILE * fp;
+ gint lineno;
+
+ /* This describes what we are parsing right now */
+ QifType parse_type;
+ QifHandler handler;
+ gpointer parse_state;
+
+ /* A bunch of flags for the current handler */
+ gint parse_flags;
+ gboolean parsed;
+
+ /* The current and "opening balance" account */
+ QifAccount current_acct;
+ QifAccount opening_bal_acct;
+
+ /* HashTable of Maps of data objects */
+ GHashTable * object_maps;
+
+ /* HashTable of Lists of data objects */
+ GHashTable * object_lists;
+
+ /* List of files */
+ GList *files;
+};
+
+/* Object Maps */
+gint qif_object_map_count(QifContext ctx, const char *type);
+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_reverse(QifContext ctx, const char *type);
+gint qif_object_list_count(QifContext ctx, const char *type);
+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);
+
+/* Set and clear flags in bit-flags */
+#define qif_set_flag(i,f) (i |= f)
+#define qif_clear_flag(i,f) (i &= ~f)
+
+#endif /* QIF_IMPORT_P_H */
diff --git a/gnucash/import-export/qif/qif-import.h b/gnucash/import-export/qif/qif-import.h
new file mode 100644
index 0000000..9602029
--- /dev/null
+++ b/gnucash/import-export/qif/qif-import.h
@@ -0,0 +1,159 @@
+/*
+ * qif-import.h -- a QIF Import module
+ *
+ * Written By: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#ifndef QIF_IMPORT_H
+#define QIF_IMPORT_H
+
+#include <stdio.h>
+#include "qof.h"
+
+typedef enum
+{
+ QIF_TYPE_BANK = 1,
+ 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)
+#define QIF_F_ITXN_NEEDS_ACCT (1 << 2)
+
+/* 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,
+ QIF_E_BADARGS,
+ QIF_E_NOFILE,
+} 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 */
+
+/* Create a QIF Import Context */
+QifContext qif_context_new(void);
+void qif_context_destroy(QifContext ctx);
+
+/* Open and read a QIF File. You must pass in the parent
+ * context; it will return the child (file) context
+ */
+QifContext qif_file_new(QifContext ctx, const char* filename);
+
+/* Does a qif-file need a default QIF account? */
+gboolean qif_file_needs_account(QifContext ctx);
+
+/* Return the filename of the QIF file */
+const char * qif_file_filename(QifContext ctx);
+
+/* Provide a default QIF Account for the QIF File */
+void qif_file_set_default_account(QifContext ctx, const char *acct_name);
+
+/* Parse the QIF File */
+QifError qif_file_parse(QifContext ctx, gpointer ui_arg);
+
+/* Merge all the qif-files from the children and into the context */
+void qif_parse_merge_files(QifContext ctx);
+
+/* Obtain the list of USED QifAccounts and QifCategories. Finds all
+ * references from the transactions in the QifContext. The returned
+ * GList must be freed by the caller.
+ */
+GList *qif_context_get_accounts(QifContext ctx);
+GList *qif_context_get_categories(QifContext ctx);
+
+#endif /* QIF_IMPORT_H */
diff --git a/gnucash/import-export/qif/qif-objects-p.h b/gnucash/import-export/qif/qif-objects-p.h
new file mode 100644
index 0000000..dbd3aa4
--- /dev/null
+++ b/gnucash/import-export/qif/qif-objects-p.h
@@ -0,0 +1,174 @@
+/*
+ * qif-objects-p.h -- Private header: QIF objects for the QIF importer
+ *
+ * Written By: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#ifndef QIF_OBJECTS_P_H
+#define QIF_OBJECTS_P_H
+
+#include "qof.h"
+
+#include "qif-import.h"
+#include "qif-objects.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;
+ char * catstr;
+
+ union
+ {
+ QifObject obj;
+ QifCategory cat;
+ QifAccount acct;
+ } far_cat;
+ gboolean far_cat_is_acct;
+};
+
+/* to be run after parsing all the dates and amounts */
+void qif_txn_setup_splits(QifTxn txn);
+void qif_invst_txn_setup_splits(QifContext ctx, QifTxn txn);
+
+#endif /* QIF_OBJECTS_P_H */
diff --git a/gnucash/import-export/qif/qif-objects.c b/gnucash/import-export/qif/qif-objects.c
new file mode 100644
index 0000000..0fad928
--- /dev/null
+++ b/gnucash/import-export/qif/qif-objects.c
@@ -0,0 +1,1468 @@
+/*
+ * qif-objects.c -- Objects for the QIF Importer
+ *
+ * Written by: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <string.h>
+#include "Account.h"
+
+#include "gnc-engine.h"
+
+#include "qif-import-p.h"
+#include "qif-objects-p.h"
+#include "qif-defaults.h"
+
+static QofLogModule log_module = GNC_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; \
+})
+
+/* 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.
+ */
+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);
+ 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.
+ */
+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.
+ */
+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.
+ */
+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->amountstr) s->amountstr = g_strdup(s->amountstr);
+ 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)
+{
+ 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 transfer 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 = NULL; /* We know that ctx->current_acct is NULL */
+
+ 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 we found an opening balance account then set up the context.
+ * 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 (cur_acct)
+ {
+ ctx->opening_bal_acct = cur_acct;
+ ctx->current_acct = cur_acct;
+ }
+ else
+ qif_set_flag(ctx->parse_flags, QIF_F_TXN_NEEDS_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_HOW_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 we don't have an account, then deal with the opening balance */
+ if (!ctx->current_acct)
+ qif_process_opening_balance_txn(ctx, txn);
+
+ /* Set the transaction's from account */
+ txn->from_acct = ctx->current_acct;
+
+ /* And add it to the process list */
+ ctx->parse_state = g_list_prepend(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 splits */
+void
+qif_txn_setup_splits(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_HOW_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;
+ gboolean txn_needs_acct;
+
+ 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.
+ */
+
+ txn_needs_acct = (ctx->parse_flags & QIF_F_TXN_NEEDS_ACCT);
+
+ /* Invert the list so we're working in the right order */
+ ctx->parse_state = g_list_reverse(ctx->parse_state);
+
+ for (node = ctx->parse_state; node; node = node->next)
+ {
+ txn = node->data;
+
+ /* If we need a from account, then set it.. */
+ if (txn_needs_acct && ctx->opening_bal_acct && !txn->from_acct)
+ txn->from_acct = ctx->opening_bal_acct;
+
+ /* merge the txn into the context (prepends to the list) */
+ qif_object_list_insert(ctx, (QifObject)txn);
+ }
+
+ if (txn_needs_acct && ctx->opening_bal_acct)
+ qif_clear_flag(ctx->parse_flags, QIF_F_TXN_NEEDS_ACCT);
+
+ /* clean up our state */
+ g_list_free(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->catstr);
+
+ g_free(itxn);
+}
+
+static QifError
+qif_txn_invst_parse(QifContext ctx, GList *record)
+{
+ QifTxn txn;
+ QifInvstTxn itxn;
+ QifLine line;
+
+ 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 */
+ qif_save_str(itxn->catstr);
+ 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 security name */
+ if (!itxn->security)
+ itxn->security = g_strdup(""); /* XXX */
+
+ /* if we don't have a from account, then mark the fact that
+ * we'll need one later.
+ */
+ if (ctx->current_acct)
+ txn->from_acct = ctx->current_acct;
+ else
+ qif_set_flag(ctx->parse_flags, QIF_F_ITXN_NEEDS_ACCT);
+
+ /* Add this transaction to the parse state for later processing */
+ ctx->parse_state = g_list_prepend(ctx->parse_state, txn);
+
+ }
+ else
+ {
+ /* no date? Just destroy it */
+ qif_txn_destroy((QifObject)txn);
+ }
+
+ return QIF_E_OK;
+}
+
+
+void
+qif_invst_txn_setup_splits(QifContext ctx, QifTxn txn)
+{
+ QifInvstTxn itxn;
+ QifSplit near_split, far_split, comm_split;
+ QifAccount from_acct;
+
+ char *cat = NULL;
+ char *cat_class = NULL;
+ gboolean cat_is_acct = FALSE;
+ char *miscx = NULL;
+ char *miscx_class = NULL;
+ gboolean miscx_is_acct = FALSE;
+
+ /* Cached account-type lists */
+ static GList *bank_list = NULL;
+
+ 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_HOW_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();
+ from_acct = txn->from_acct;
+
+ /* Parse the category string */
+ if (!qif_parse_split_category(itxn->catstr,
+ &cat, &cat_is_acct, &cat_class,
+ &miscx, &miscx_is_acct, &miscx_class))
+ PERR("Failure parsing category: %s", itxn->catstr);
+
+ /* Make sure we've got a cached list */
+ if (bank_list == NULL)
+ bank_list = qif_parse_acct_type("__any_bank__", -1);
+
+ /* 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 = from_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);
+ 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 = from_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 don't have a far acct (or far category) then reset the flag */
+ if (!itxn->far_cat.obj)
+ itxn->far_cat_is_acct = FALSE;
+
+ /* 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);
+
+ /* Free parsed strings.. */
+ g_free(cat);
+ g_free(cat_class);
+ g_free(miscx);
+ g_free(miscx_class);
+}
+
+
+/* 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)
+{
+ int i;
+ 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 } },
+ { 0, {NULL, NULL, NULL} }
+ };
+
+ for (i = 0; handlers[i].type > 0; i++)
+ {
+ if (handlers[i].type <= 0)
+ {
+ PERR("Invalid type?!? (%d @ %d)", handlers[i].type, i);
+ }
+ else
+ qif_register_handler(handlers[i].type, &(handlers[i].handler));
+ }
+}
diff --git a/gnucash/import-export/qif/qif-objects.h b/gnucash/import-export/qif/qif-objects.h
new file mode 100644
index 0000000..2d28e59
--- /dev/null
+++ b/gnucash/import-export/qif/qif-objects.h
@@ -0,0 +1,71 @@
+/*
+ * qif-objects.h -- QIF objects for the QIF importer
+ *
+ * Written By: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#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);
+
+/* merge the object into the context. Returns the object that's in
+ * the context, which is either the supplied object or the
+ * already-existing object.
+ */
+QifAccount qif_account_merge(QifContext ctx, QifAccount acct);
+QifCategory qif_cat_merge(QifContext ctx, QifCategory cat);
+QifClass qif_class_merge(QifContext ctx, QifClass qclass);
+QifSecurity qif_security_merge(QifContext ctx, QifSecurity security);
+
+#endif /* QIF_OBJECTS_H */
diff --git a/gnucash/import-export/qif/qif-parse.c b/gnucash/import-export/qif/qif-parse.c
new file mode 100644
index 0000000..f291c86
--- /dev/null
+++ b/gnucash/import-export/qif/qif-parse.c
@@ -0,0 +1,935 @@
+/*
+ * qif-parse.c -- parse QIF
+ *
+ * Written by: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <string.h>
+
+/* For regex */
+#include <sys/types.h>
+#include <regex.h>
+
+#include <stdarg.h>
+
+#include "gnc-engine.h"
+#include "gnc-ui-util.h"
+
+#include "qif-import-p.h"
+#include "qif-objects-p.h"
+
+#include "import-parse.h"
+
+static QofLogModule log_module = GNC_MOD_IMPORT;
+
+/* An array of handlers for the various bang-types */
+static QifHandler qif_handlers[QIF_TYPE_MAX+1] = { NULL };
+
+/* Parser Regular Expressions */
+static gboolean qifp_regex_compiled = FALSE;
+static regex_t category_regex;
+
+/* 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)
+{
+ if (type <= 0 || type > QIF_TYPE_MAX)
+ {
+ PERR("Invalid type: %d", type);
+ return;
+ }
+ qif_handlers[type] = handler;
+}
+
+static void
+compile_regex()
+{
+ regcomp(&category_regex,
+ "^ *(\\[)?([^]/|]*)(]?)(/([^|]*))?(\\|(\\[)?([^]/]*)(]?)(/(.*))?)? *$",
+ REG_EXTENDED);
+
+ qifp_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);
+
+ /* Translators FIXME: It is unclear whether these strings should
+ really be translated, and if yes, into which translation. */
+ 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("cvrshrt", QIF_A_BUY);
+ QIF_ADD_ACT("kauf", QIF_A_BUY);
+ QIF_ADD_ACT("buyx", QIF_A_BUYX);
+ QIF_ADD_ACT("cvrshrtx", 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("cash", 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("shtsell", QIF_A_SELL);
+ QIF_ADD_ACT("verkauf", QIF_A_SELL); /* verkaufen */
+ QIF_ADD_ACT("sellx", QIF_A_SELLX);
+ QIF_ADD_ACT("shtsellx", 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("contribx", QIF_A_XIN);
+ QIF_ADD_ACT("xout", QIF_A_XOUT);
+ QIF_ADD_ACT("withdrwx", 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_atype_map = g_hash_table_new(g_str_hash, g_str_equal);
+ g_assert(qif_atype_map);
+
+ QIF_ADD_ATYPE("bank", make_list(1, ACCT_TYPE_BANK));
+ QIF_ADD_ATYPE("port", make_list(1, ACCT_TYPE_BANK));
+ QIF_ADD_ATYPE("cash", make_list(1, ACCT_TYPE_CASH));
+ QIF_ADD_ATYPE("ccard", make_list(1, ACCT_TYPE_CREDIT));
+ QIF_ADD_ATYPE("invst", make_list(3, ACCT_TYPE_BANK, ACCT_TYPE_STOCK,
+ ACCT_TYPE_MUTUAL));
+ QIF_ADD_ATYPE("oth a", make_list(3, ACCT_TYPE_ASSET, ACCT_TYPE_BANK,
+ ACCT_TYPE_CASH));
+ QIF_ADD_ATYPE("oth l", make_list(2, ACCT_TYPE_LIABILITY, ACCT_TYPE_CREDIT));
+ QIF_ADD_ATYPE("mutual", make_list(3, ACCT_TYPE_BANK, ACCT_TYPE_MUTUAL,
+ ACCT_TYPE_STOCK));
+
+ /* Internal types */
+ QIF_ADD_ATYPE("__any_bank__", make_list(5, ACCT_TYPE_BANK, ACCT_TYPE_CREDIT,
+ ACCT_TYPE_CASH, ACCT_TYPE_ASSET,
+ ACCT_TYPE_LIABILITY));
+ QIF_ADD_ATYPE("__all__", make_list(7, ACCT_TYPE_BANK, ACCT_TYPE_CREDIT,
+ ACCT_TYPE_CASH, ACCT_TYPE_ASSET,
+ ACCT_TYPE_LIABILITY, ACCT_TYPE_STOCK,
+ ACCT_TYPE_MUTUAL));
+ QIF_ADD_ATYPE("__stock__", make_list(2, ACCT_TYPE_STOCK, ACCT_TYPE_MUTUAL));
+ QIF_ADD_ATYPE("__income__", make_list(1, ACCT_TYPE_INCOME));
+ QIF_ADD_ATYPE("__expense__", make_list(1, ACCT_TYPE_EXPENSE));
+ QIF_ADD_ATYPE("__equity__", make_list(1, ACCT_TYPE_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_utf8_strdown(line + 1, -1);
+ g_strstrip(bangtype);
+
+ /* In some cases we get "!Type Bank" -- change the space to a colon */
+ if (!strncmp(bangtype, "type ", 5))
+ 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 (!qifp_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[2].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_utf8_strdown(line->line, -1);
+ g_strstrip(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_utf8_strdown(str, -1);
+ g_strstrip(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_setup_splits(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);
+}
+
+typedef struct
+{
+ QifContext ctx;
+ GList * list;
+ const char* type;
+} qif_merge_t;
+
+static void
+qif_merge_accts(gpointer key, gpointer value, gpointer data)
+{
+ qif_merge_t *merge = data;
+ QifAccount acct = value;
+
+ /* Merge into the context. Remember items moved into the parent */
+ if (qif_account_merge(merge->ctx, acct) == acct)
+ merge->list = g_list_prepend(merge->list, acct->name);
+}
+
+static void
+qif_merge_cats(gpointer key, gpointer value, gpointer data)
+{
+ qif_merge_t *merge = data;
+ QifCategory cat = value;
+
+ /* Merge into the context. Remember items moved into the parent */
+ if (qif_cat_merge(merge->ctx, cat) == cat)
+ merge->list = g_list_prepend(merge->list, cat->name);
+}
+
+static void
+qif_merge_classes(gpointer key, gpointer value, gpointer data)
+{
+ qif_merge_t *merge = data;
+ QifClass qclass = value;
+
+ /* Merge into the context. Remember items moved into the parent */
+ if (qif_class_merge(merge->ctx, qclass) == qclass)
+ merge->list = g_list_prepend(merge->list, qclass->name);
+}
+
+static void
+qif_merge_secs(gpointer key, gpointer value, gpointer data)
+{
+ qif_merge_t *merge = data;
+ QifSecurity sec = value;
+
+ /* Merge into the context. Remember items moved into the parent */
+ if (qif_security_merge(merge->ctx, sec) == sec)
+ merge->list = g_list_prepend(merge->list, sec->name);
+}
+
+static void
+qif_merge_del(gpointer obj, gpointer data)
+{
+ qif_merge_t *merge = data;
+ const char *name = obj;
+
+ qif_object_map_remove(merge->ctx, merge->type, name);
+}
+
+static void
+qif_massage_split(QifSplit split, QifContext ctx)
+{
+ const char *type = QIF_O_CATEGORY;
+ char *name;
+
+ if (split->cat.obj)
+ {
+ if (split->cat_is_acct)
+ {
+ type = QIF_O_ACCOUNT;
+ name = split->cat.acct->name;
+ }
+ else
+ name = split->cat.cat->name;
+
+ split->cat.obj = qif_object_map_lookup(ctx, type, name);
+ }
+
+ if (split->cat_class)
+ {
+ split->cat_class = (QifClass) qif_object_map_lookup(ctx, QIF_O_CLASS,
+ split->cat_class->name);
+ }
+}
+
+static void
+qif_massage_itxn(QifInvstTxn itxn, QifContext ctx)
+{
+ const char *type = QIF_O_CATEGORY;
+ char *name;
+
+ if (itxn->far_cat.obj)
+ {
+ if (itxn->far_cat_is_acct)
+ {
+ type = QIF_O_ACCOUNT;
+ name = itxn->far_cat.acct->name;
+ }
+ else
+ name = itxn->far_cat.cat->name;
+
+ itxn->far_cat.obj = qif_object_map_lookup(ctx, type, name);
+ }
+}
+
+static void
+qif_massage_txn(gpointer obj, gpointer data)
+{
+ QifTxn txn = obj;
+ QifContext ctx = data;
+ QifSplit split;
+ GList *node;
+
+ if (txn->from_acct)
+ txn->from_acct = (QifAccount) qif_object_map_lookup(ctx, QIF_O_ACCOUNT,
+ txn->from_acct->name);
+
+ if (txn->invst_info)
+ qif_massage_itxn(txn->invst_info, ctx);
+
+ if (txn->default_split)
+ qif_massage_split(txn->default_split, ctx);
+
+ for (node = txn->splits; node; node = node->next)
+ {
+ split = node->data;
+ qif_massage_split(split, ctx);
+ }
+}
+
+void
+qif_parse_merge_files(QifContext ctx)
+{
+ GList *node;
+ GList *accts = NULL;
+ GList *cats = NULL;
+ GList *classes = NULL;
+ GList *securities = NULL;
+ QifContext fctx;
+
+ qif_merge_t merge;
+
+ g_return_if_fail(ctx);
+
+ /* Make sure each of the "file" contexts have been parsed.
+ * note that we don't care about OUR context -- we can run this
+ * process multiple times safely.
+ */
+ for (node = ctx->files; node; node = node->next)
+ {
+ fctx = node->data;
+ g_return_if_fail(fctx->parsed);
+ }
+
+
+ /* Iterate over each file. Merge the Accounts, Categories, Classes,
+ * Securities, and Transactions into the top-level context. Be sure
+ * to re-point all Transaction/Split category/class/account pointers
+ * to the new top-level item. Then be sure to remove the
+ * "duplicated" items so we don't double-free (as we don't refcount,
+ * either).
+ */
+ for (node = ctx->files; node; node = node->next)
+ {
+ fctx = node->data;
+
+ /* Merge accts, categories, classes, and securities */
+
+ merge.ctx = ctx;
+ merge.list = NULL;
+ qif_object_map_foreach(fctx, QIF_O_ACCOUNT, qif_merge_accts, &merge);
+ accts = merge.list;
+
+ merge.list = NULL;
+ qif_object_map_foreach(fctx, QIF_O_CATEGORY, qif_merge_cats, &merge);
+ cats = merge.list;
+
+ merge.list = NULL;
+ qif_object_map_foreach(fctx, QIF_O_CLASS, qif_merge_classes, &merge);
+ classes = merge.list;
+
+ merge.list = NULL;
+ qif_object_map_foreach(fctx, QIF_O_SECURITY, qif_merge_secs, &merge);
+ securities = merge.list;
+
+
+ /* repoint the transactions to the merged context data */
+ qif_object_list_foreach(fctx, QIF_O_TXN, qif_massage_txn, ctx);
+
+
+ /* then remove from the file context objects referenced in the top context */
+ merge.ctx = fctx;
+ merge.type = QIF_O_ACCOUNT;
+ g_list_foreach(accts, qif_merge_del, &merge);
+ g_list_free(accts);
+
+ merge.type = QIF_O_CATEGORY;
+ g_list_foreach(cats, qif_merge_del, &merge);
+ g_list_free(cats);
+
+ merge.type = QIF_O_CLASS;
+ g_list_foreach(classes, qif_merge_del, &merge);
+ g_list_free(classes);
+
+ merge.type = QIF_O_SECURITY;
+ g_list_foreach(securities, qif_merge_del, &merge);
+ g_list_free(securities);
+
+ }
+
+ /* We've been parsed */
+ ctx->parsed = TRUE;
+}
diff --git a/gnucash/import-export/qif/qif-parse.h b/gnucash/import-export/qif/qif-parse.h
new file mode 100644
index 0000000..11d439c
--- /dev/null
+++ b/gnucash/import-export/qif/qif-parse.h
@@ -0,0 +1,50 @@
+/*
+ * qif-parse.h -- routines for parsing pieces of a QIF file
+ *
+ * Written By: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#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);
+
+/* Parse all objects */
+void qif_parse_all(QifContext ctx, gpointer ui_args);
+
+#endif /* QIF_PARSE_H */
diff --git a/gnucash/import-export/qif/test/CMakeLists.txt b/gnucash/import-export/qif/test/CMakeLists.txt
new file mode 100644
index 0000000..d874ca3
--- /dev/null
+++ b/gnucash/import-export/qif/test/CMakeLists.txt
@@ -0,0 +1,18 @@
+
+set(QIF_TEST_INCLUDE_DIRS
+ ${CMAKE_BINARY_DIR}/common
+ ${CMAKE_SOURCE_DIR}/gnucash/import-export/qif
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine
+ ${CMAKE_SOURCE_DIR}/common/test-core
+ ${GLIB2_INCLUDE_DIRS}
+)
+set(QIF_TEST_LIBS gncmod-qif test-core)
+
+if (FALSE)
+ # Tests for this directory are not run.
+ gnc_add_test(test-link-qif test-link.c QIF_TEST_INCLUDE_DIRS QIF_TEST_LIBS)
+ gnc_add_test(test-qif test-qif.c QIF_TEST_INCLUDE_DIRS QIF_TEST_LIBS
+ GNC_TEST_FILES=${CMAKE_CURRENT_SOURCE_DIR}/test-files)
+endif()
+
+set_dist_list(test_qif_DIST CMakeLists.txt test-link.c test-qif.c test-files/test-1-bank-txn.qif)
diff --git a/gnucash/import-export/qif/test/test-files/test-1-bank-txn.qif b/gnucash/import-export/qif/test/test-files/test-1-bank-txn.qif
new file mode 100644
index 0000000..59592d0
--- /dev/null
+++ b/gnucash/import-export/qif/test/test-files/test-1-bank-txn.qif
@@ -0,0 +1,6 @@
+!Type:Bank
+D2003/01/27
+T123.45
+PTest Payee
+LTest Category
+^
diff --git a/gnucash/import-export/qif/test/test-link.c b/gnucash/import-export/qif/test/test-link.c
new file mode 100644
index 0000000..fd55d42
--- /dev/null
+++ b/gnucash/import-export/qif/test/test-link.c
@@ -0,0 +1,28 @@
+/********************************************************************\
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#include "qif-import.h"
+
+int
+main(int argc, char *argv[])
+{
+ qif_context_new();
+ return 0;
+}
diff --git a/gnucash/import-export/qif/test/test-qif.c b/gnucash/import-export/qif/test/test-qif.c
new file mode 100644
index 0000000..c331bef
--- /dev/null
+++ b/gnucash/import-export/qif/test/test-qif.c
@@ -0,0 +1,110 @@
+/*
+ * test-qif.c -- Test the QIF Import routines.
+ *
+ * Created by: Derek Atkins <derek at ihtfp.com>
+ * Copyright (c) 2003 Derek Atkins <warlord at MIT.EDU>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#include <glib.h>
+#include <libguile.h>
+
+#include "gnc-module.h"
+#include "qif-import.h"
+#include "qif-import-p.h" /* Let's test some internal stuff, too */
+
+#include "test-stuff.h"
+
+/* XXX */
+extern void qif_object_init(void);
+
+static QifContext
+test_qif_load_file(QifContext ctx, const char *filename,
+ gint txn_count, gint acct_count, gboolean needs_acct)
+{
+ QifContext file;
+
+ printf("qif loading \"%s\"...\n", filename);
+ file = qif_file_new(ctx, filename);
+ do_test(file != NULL, "failed to read file");
+ if (!file) return NULL;
+
+ do_test(qif_object_list_count(file, QIF_O_TXN) == txn_count,
+ "Transaction count didn't match");
+ do_test(qif_object_map_count(file, QIF_O_ACCOUNT) == acct_count,
+ "Account count didn't match");
+ do_test(qif_file_needs_account(file) == needs_acct,
+ "Needs account flad didn't match");
+
+ return file;
+}
+
+static void
+test_qif(void)
+{
+ QifContext ctx, file;
+ char *filename;
+ const char *location = g_getenv("GNC_TEST_FILES");
+ int i;
+
+ ctx = qif_context_new();
+ do_test(ctx != NULL, "failed to create the qif context");
+ if (!ctx) return;
+
+ if (!location)
+ location = "test-files";
+
+ for (i = 0; i < 1; i++)
+ {
+ filename = g_strdup_printf("%s/%s", location, "test-1-bank-txn.qif");
+ file = test_qif_load_file(ctx, filename, 1, 0, TRUE);
+ g_free(filename);
+ if (!file) continue;
+
+ if (qif_file_needs_account(file))
+ qif_file_set_default_account(file, "test-1-bank-txn");
+
+ do_test(qif_file_needs_account(file) == FALSE,
+ "'Needs account' flag not cleared properly");
+
+ do_test(qif_file_parse(file, NULL) == QIF_E_OK,
+ "file failed to parse.");
+ }
+
+ qif_context_destroy(ctx);
+
+ success("QIF test successful");
+}
+
+static void
+main_helper(void *closure, int argc, char **argv)
+{
+ qif_object_init(); /* XXX:FIXME */
+ test_qif();
+ print_test_results();
+ exit(get_rv());
+}
+
+int
+main(int argc, char **argv)
+{
+ scm_boot_guile(argc, argv, main_helper, NULL);
+ return 0;
+}
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b8cc820..426b3f2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -347,6 +347,9 @@ gnucash/import-export/ofx/gnc-ofx-import.c
gnucash/import-export/ofx/gnc-ofx-kvp.c
gnucash/import-export/ofx/gnc-plugin-ofx.c
gnucash/import-export/ofx/gschemas/org.gnucash.dialogs.import.ofx.gschema.xml.in
+gnucash/import-export/qif/qif-context.c
+gnucash/import-export/qif/qif-file.c
+gnucash/import-export/qif/qif-objects.c
gnucash/import-export/qif-imp/assistant-qif-import.c
gnucash/import-export/qif-imp/dialog-account-picker.c
gnucash/import-export/qif-imp/gncmod-qif-import.c
@@ -483,7 +486,8 @@ gnucash/report/standard-reports/general-journal.scm
gnucash/report/standard-reports/general-ledger.scm
gnucash/report/standard-reports/income-gst-statement.scm
gnucash/report/standard-reports/income-statement.scm
-gnucash/report/standard-reports/net-charts.scm
+gnucash/report/standard-reports/net-barchart.scm
+gnucash/report/standard-reports/net-linechart.scm
gnucash/report/standard-reports/portfolio.scm
gnucash/report/standard-reports/price-scatter.scm
gnucash/report/standard-reports/register.scm
@@ -571,6 +575,7 @@ libgnucash/backend/sql/gnc-sql-result.cpp
libgnucash/backend/sql/gnc-tax-table-sql.cpp
libgnucash/backend/sql/gnc-transaction-sql.cpp
libgnucash/backend/sql/gnc-vendor-sql.cpp
+libgnucash/backend/xml/.#gnc-invoice-xml-v2.cpp
libgnucash/backend/xml/gnc-account-xml-v2.cpp
libgnucash/backend/xml/gnc-address-xml-v2.cpp
libgnucash/backend/xml/gnc-backend-xml.cpp
Summary of changes:
gnucash/gtkbuilder/dialog-account-picker.glade | 2 +-
.../import-export/qif-imp/assistant-qif-import.c | 116 +++++++++++----------
gnucash/import-export/qif-imp/qif-to-gnc.scm | 43 ++++----
po/POTFILES.in | 1 +
4 files changed, 87 insertions(+), 75 deletions(-)
More information about the gnucash-changes
mailing list