[Gnucash-changes] Neil Williams' QOF book merge patch #1.

Derek Atkins warlord at cvs.gnucash.org
Sat Oct 16 11:51:30 EDT 2004


Log Message:
-----------
Neil Williams' QOF book merge patch #1.

	  Includes
	  qof_book_merge.c
	  qof_book_merge.h
	  test-book-merge.c test routine
	  New Account Hierarchy druid 
	  Sundry adjustments to QOF support.
	  Tweaks to several Makefile.am files to support new files.
	  Tweaks to window-main.c to support new menu item
	  Changes to druid-hierarchy.c to support the merge druid.

Modified Files:
--------------
    gnucash:
        ChangeLog
    gnucash/src/business/business-core:
        gncAddress.c
        gncAddress.h
        gncBillTerm.c
        gncCustomer.c
        gncCustomer.h
        gncEntry.h
        gncJob.h
        gncOrder.c
        gncTaxTable.c
        gncTaxTable.h
    gnucash/src/engine:
        Makefile.am
        gnc-pricedb.c
    gnucash/src/engine/test:
        Makefile.am
    gnucash/src/gnome:
        Makefile.am
        druid-hierarchy.c
        druid-hierarchy.h
        window-main.c
    gnucash/src/gnome/glade:
        Makefile.am

Added Files:
-----------
    gnucash/src/engine:
        qof_book_merge.c
        qof_book_merge.h
    gnucash/src/engine/test:
        test-book-merge.c
    gnucash/src/gnome:
        druid-merge.c
        druid-merge.h
    gnucash/src/gnome/glade:
        merge.glade

Revision Data
-------------
Index: ChangeLog
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/ChangeLog,v
retrieving revision 1.1847
retrieving revision 1.1848
diff -LChangeLog -LChangeLog -u -r1.1847 -r1.1848
--- ChangeLog
+++ ChangeLog
@@ -1,3 +1,18 @@
+2004-10-16  Derek Atkins  <derek at ihtfp.com>
+
+	* Neil Williams' QOF book merge patch #1:
+	  qof_book_merge - release 1.
+
+	  Includes
+	  qof_book_merge.c
+	  qof_book_merge.h
+	  test-book-merge.c test routine
+	  New Account Hierarchy druid 
+	  Sundry adjustments to QOF support.
+	  Tweaks to several Makefile.am files to support new files.
+	  Tweaks to window-main.c to support new menu item
+	  Changes to druid-hierarchy.c to support the merge druid.
+
 2004-10-15  Derek Atkins  <derek at ihtfp.com>
 
 	* src/business/business-core/gncInvoice.[ch]
Index: gncCustomer.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncCustomer.h,v
retrieving revision 1.22
retrieving revision 1.23
diff -Lsrc/business/business-core/gncCustomer.h -Lsrc/business/business-core/gncCustomer.h -u -r1.22 -r1.23
--- src/business/business-core/gncCustomer.h
+++ src/business/business-core/gncCustomer.h
@@ -31,6 +31,29 @@
 #ifndef GNC_CUSTOMER_H_
 #define GNC_CUSTOMER_H_
 
+/** @struct GncCustomer
+
+credit, discount and shipaddr are unique to GncCustomer\n
+id, name, notes, terms, addr, currency, taxtable, taxtable_override
+taxincluded, active and jobs are identical to ::GncVendor.
+
+ at param	QofInstance     inst;
+ at param	char *          id;
+ at param	char *          name;
+ at param	char *          notes;
+ at param	GncBillTerm *   terms;
+ at param	GncAddress *    addr;
+ at param	gnc_commodity * currency;
+ at param	GncTaxTable*    taxtable;
+ at param	gboolean        taxtable_override;
+ at param	GncTaxIncluded  taxincluded;
+ at param	gboolean        active;
+ at param	GList *         jobs;
+ at param	gnc_numeric     credit;
+ at param	gnc_numeric     discount;
+ at param	GncAddress *    shipaddr;
+
+*/
 typedef struct _gncCustomer GncCustomer;
 
 #include "qofbook.h"
@@ -75,10 +98,6 @@
 void gncCustomerAddJob (GncCustomer *customer, GncJob *job);
 void gncCustomerRemoveJob (GncCustomer *customer, GncJob *job);
 
-/** added for QOF standardisation */
-void gncCustomerSetTaxIncluded_q (GncCustomer *customer, gint taxincl);
-gint gncCustomerGetTaxIncluded_q (GncCustomer *cust);
-
 /** @} */
 
 /** @name Get Functions */
Index: gncJob.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncJob.h,v
retrieving revision 1.15
retrieving revision 1.16
diff -Lsrc/business/business-core/gncJob.h -Lsrc/business/business-core/gncJob.h -u -r1.15 -r1.16
--- src/business/business-core/gncJob.h
+++ src/business/business-core/gncJob.h
@@ -68,7 +68,7 @@
 gboolean gncJobIsDirty (GncJob *job);
 
 /** Return a pointer to the instance gncJob that is identified
- *  by the guid, and is residing in the book. Returns NULL if the 
+ *  by the guid, and is residing in the book. Returns NULL if the
  *  instance can't be found.
  *  Equivalent function prototype is
  *  GncJob * gncJobLookup (QofBook *book, const GUID *guid);
Index: gncEntry.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncEntry.h,v
retrieving revision 1.30
retrieving revision 1.31
diff -Lsrc/business/business-core/gncEntry.h -Lsrc/business/business-core/gncEntry.h -u -r1.30 -r1.31
--- src/business/business-core/gncEntry.h
+++ src/business/business-core/gncEntry.h
@@ -100,6 +100,7 @@
 void gncEntrySetInvDiscount (GncEntry *entry, gnc_numeric discount);
 void gncEntrySetInvDiscountType (GncEntry *entry, GncAmountType type);
 void gncEntrySetInvDiscountHow (GncEntry *entry, GncDiscountHow how);
+
 /** @} */
 
 /** @name Vendor Bills (and Employee Expenses) */
@@ -208,21 +209,32 @@
 void gncEntryCommitEdit (GncEntry *entry);
 int gncEntryCompare (GncEntry *a, GncEntry *b);
 
-#define ENTRY_DATE	"date"
-#define ENTRY_DATE_ENTERED "date-entered"
-#define ENTRY_DESC	"desc"
-#define ENTRY_ACTION	"action"
-#define ENTRY_NOTES	"notes"
-#define ENTRY_QTY	"qty"
-
-#define ENTRY_IPRICE	"iprice"
-#define ENTRY_BPRICE	"bprice"
-#define ENTRY_BILLABLE	"billable?"
-#define ENTRY_BILLTO	"bill-to"
-
-#define ENTRY_ORDER	"order"
-#define ENTRY_INVOICE	"invoice"
-#define ENTRY_BILL	"bill"
+#define ENTRY_DATE			"date"
+#define ENTRY_DATE_ENTERED 	"date-entered"
+#define ENTRY_DESC			"desc"
+#define ENTRY_ACTION		"action"
+#define ENTRY_NOTES			"notes"
+#define ENTRY_QTY			"qty"
+
+#define ENTRY_IPRICE		"iprice"
+#define ENTRY_BPRICE		"bprice"
+#define ENTRY_BILLABLE		"billable?"
+#define ENTRY_BILLTO		"bill-to"
+
+#define ENTRY_ORDER			"order"
+#define ENTRY_INVOICE		"invoice"
+#define ENTRY_BILL			"bill"
+
+#define ENTRY_INV_DISC_TYPE		"discount-type"
+#define ENTRY_INV_DISC_HOW		"discount-method"
+
+#define ENTRY_INV_TAXABLE	"invoice-taxable"
+#define ENTRY_BILL_TAXABLE	"bill-taxable"
+#define ENTRY_INV_TAX_INC	"invoice-tax-included"
+#define ENTRY_BILL_TAX_INC	"bill-tax-included"
+#define ENTRY_INV_DISCOUNT	"invoice-discount"
+#define ENTRY_BILL_PAY_TYPE "bill-payment-type"
+
 
 /* deprecated functions, should be removed */
 #define gncEntryGetGUID(x) qof_instance_get_guid(QOF_INSTANCE(x))
Index: gncCustomer.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncCustomer.c,v
retrieving revision 1.61
retrieving revision 1.62
diff -Lsrc/business/business-core/gncCustomer.c -Lsrc/business/business-core/gncCustomer.c -u -r1.61 -r1.62
--- src/business/business-core/gncCustomer.c
+++ src/business/business-core/gncCustomer.c
@@ -56,7 +56,7 @@
 #include "gncJobP.h"
 #include "gncTaxTableP.h"
 
-struct _gncCustomer 
+struct _gncCustomer
 {
   QofInstance     inst;
 
@@ -270,13 +270,6 @@
   gncCustomerCommitEdit (cust);
 }
 
-void gncCustomerSetTaxIncluded_q (GncCustomer *cust, gint taxincl)
-{
-	GncTaxIncluded g = taxincl;
-	if(!g) return;
-	gncCustomerSetTaxIncluded(cust, g);
-}
-
 void gncCustomerSetTaxIncluded (GncCustomer *cust, GncTaxIncluded taxincl)
 {
   if (!cust) return;
@@ -451,11 +444,6 @@
   return cust->terms;
 }
 
-gint gncCustomerGetTaxIncluded_q (GncCustomer *cust)
-{
-	return (GncTaxIncluded)gncCustomerGetTaxIncluded(cust);
-}
-
 GncTaxIncluded gncCustomerGetTaxIncluded (GncCustomer *cust)
 {
   if (!cust) return GNC_TAXINCLUDED_USEGLOBAL;
@@ -543,7 +531,7 @@
   return c->name;
 }
 
-static QofObject gncCustomerDesc = 
+static QofObject gncCustomerDesc =
 {
   interface_version:  QOF_OBJECT_VERSION,
   e_type:             _GNC_MOD_NAME,
Index: gncBillTerm.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncBillTerm.c,v
retrieving revision 1.40
retrieving revision 1.41
diff -Lsrc/business/business-core/gncBillTerm.c -Lsrc/business/business-core/gncBillTerm.c -u -r1.40 -r1.41
--- src/business/business-core/gncBillTerm.c
+++ src/business/business-core/gncBillTerm.c
@@ -51,7 +51,10 @@
 #include "gncBusiness.h"
 #include "gncBillTermP.h"
 
-
+/* doxygen: this definition has been copied and pasted
+into the .h file to make it accessible to doxygen.
+Please keep the two in sync if it is not possible to
+move the struct definition itself into the .h */
 struct _gncBillTerm 
 {
   QofInstance     inst;
@@ -79,7 +82,6 @@
 {
   GList *         terms;        /* visible terms */
 };
-
 static short        module = MOD_BUSINESS;
 
 #define _GNC_MOD_NAME        GNC_ID_BILLTERM
@@ -678,7 +680,7 @@
   interface_version:   QOF_OBJECT_VERSION,
   e_type:              _GNC_MOD_NAME,
   type_label:          "Billing Term",
-  create:              NULL,
+  create:              (gpointer)gncBillTermCreate,
   book_begin:          _gncBillTermCreate,
   book_end:            _gncBillTermDestroy,
   is_dirty:            qof_collection_is_dirty,
@@ -691,24 +693,16 @@
 gboolean gncBillTermRegister (void)
 {
   static QofParam params[] = {
-    { GNC_BILLTERM_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncBillTermGetName,
-      (QofSetterFunc)gncBillTermSetName },
-    { GNC_BILLTERM_DESC, QOF_TYPE_STRING, (QofAccessFunc)gncBillTermGetDescription,
-      (QofSetterFunc)gncBillTermSetDescription },
-    { GNC_BILLTERM_TYPE, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetType, 
-      (QofSetterFunc)gncBillTermSetType },
-    { GNC_BILLTERM_DUEDAYS, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetDueDays, 
-      (QofSetterFunc)gncBillTermSetDueDays },
-    { GNC_BILLTERM_DISCDAYS, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetDiscountDays,
-      (QofSetterFunc)gncBillTermSetDiscountDays },
-    { GNC_BILLTERM_DISCOUNT, QOF_TYPE_NUMERIC, (QofAccessFunc)gncBillTermGetDiscount,
-      (QofSetterFunc)gncBillTermSetDiscount },
-    { GNC_BILLTERM_CUTOFF, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetCutoff, 
-      (QofSetterFunc)gncBillTermSetCutoff },
-    { GNC_BILLTERM_REFCOUNT, QOF_TYPE_INT64, (QofAccessFunc)gncBillTermGetRefcount, NULL },
-    { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
-    { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
-    { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
+	{ GNC_BILLTERM_NAME, 		QOF_TYPE_STRING,  (QofAccessFunc)gncBillTermGetName,			(QofSetterFunc)gncBillTermSetName },
+	{ GNC_BILLTERM_DESC, 		QOF_TYPE_STRING,  (QofAccessFunc)gncBillTermGetDescription,		(QofSetterFunc)gncBillTermSetDescription },
+	{ GNC_BILLTERM_TYPE, 		QOF_TYPE_INT32,   (QofAccessFunc)gncBillTermGetType, 			(QofSetterFunc)gncBillTermSetType },
+	{ GNC_BILLTERM_DUEDAYS, 	QOF_TYPE_INT32,   (QofAccessFunc)gncBillTermGetDueDays, 		(QofSetterFunc)gncBillTermSetDueDays },
+	{ GNC_BILLTERM_DISCDAYS, 	QOF_TYPE_INT32,   (QofAccessFunc)gncBillTermGetDiscountDays,	(QofSetterFunc)gncBillTermSetDiscountDays },
+	{ GNC_BILLTERM_DISCOUNT, 	QOF_TYPE_NUMERIC, (QofAccessFunc)gncBillTermGetDiscount,		(QofSetterFunc)gncBillTermSetDiscount },
+	{ GNC_BILLTERM_CUTOFF, 		QOF_TYPE_INT32,   (QofAccessFunc)gncBillTermGetCutoff, 			(QofSetterFunc)gncBillTermSetCutoff },
+	{ GNC_BILLTERM_REFCOUNT, 	QOF_TYPE_INT64,   (QofAccessFunc)gncBillTermGetRefcount, 		NULL },
+	{ QOF_PARAM_BOOK, 			QOF_ID_BOOK, 	  (QofAccessFunc)qof_instance_get_book, 		NULL },
+    { QOF_PARAM_GUID, 			QOF_TYPE_GUID, 	  (QofAccessFunc)qof_instance_get_guid, 		NULL },
     { NULL },
   };
 
Index: gncAddress.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncAddress.c,v
retrieving revision 1.16
retrieving revision 1.17
diff -Lsrc/business/business-core/gncAddress.c -Lsrc/business/business-core/gncAddress.c -u -r1.16 -r1.17
--- src/business/business-core/gncAddress.c
+++ src/business/business-core/gncAddress.c
@@ -285,10 +285,14 @@
 {
   static QofParam params[] = {
 
-    { ADDRESS_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetName, (QofSetterFunc)gncAddressSetName },
-    { ADDRESS_PHONE, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetPhone, (QofSetterFunc)gncAddressSetPhone },
-    { ADDRESS_FAX, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetFax, (QofSetterFunc)gncAddressSetFax },
-    { ADDRESS_EMAIL, QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetEmail, (QofSetterFunc)gncAddressSetEmail },
+    { ADDRESS_NAME, 	QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetName, 	(QofSetterFunc)gncAddressSetName },
+    { ADDRESS_ONE, 		QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetAddr1, (QofSetterFunc)gncAddressSetAddr1 },
+    { ADDRESS_TWO, 		QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetAddr2, (QofSetterFunc)gncAddressSetAddr2 },
+    { ADDRESS_THREE, 	QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetAddr3, (QofSetterFunc)gncAddressSetAddr3 },
+    { ADDRESS_FOUR, 	QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetAddr4, (QofSetterFunc)gncAddressSetAddr4 },
+    { ADDRESS_PHONE, 	QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetPhone, (QofSetterFunc)gncAddressSetPhone },
+    { ADDRESS_FAX, 		QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetFax, 	(QofSetterFunc)gncAddressSetFax },
+    { ADDRESS_EMAIL, 	QOF_TYPE_STRING, (QofAccessFunc)gncAddressGetEmail, (QofSetterFunc)gncAddressSetEmail },
     { NULL },
   };
 
Index: gncTaxTable.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncTaxTable.c,v
retrieving revision 1.39
retrieving revision 1.40
diff -Lsrc/business/business-core/gncTaxTable.c -Lsrc/business/business-core/gncTaxTable.c -u -r1.39 -r1.40
--- src/business/business-core/gncTaxTable.c
+++ src/business/business-core/gncTaxTable.c
@@ -464,12 +464,6 @@
     mod_table (entry->table);
   }
 }
-void gncTaxTableEntrySetType_q (GncTaxTableEntry *entry, gint type)
-{
-	GncAmountType q = type;
-	if(!q) return;
-	gncTaxTableEntrySetType(entry,q);
-}
 
 void gncTaxTableEntrySetType (GncTaxTableEntry *entry, GncAmountType type)
 {
@@ -669,11 +663,6 @@
   return entry->account;
 }
 
-gint gncTaxTableEntryGetType_q (GncTaxTableEntry *entry)
-{
-	return (GncAmountType)gncTaxTableEntryGetType(entry);
-}
-
 GncAmountType gncTaxTableEntryGetType (GncTaxTableEntry *entry)
 {
   if (!entry) return 0;
@@ -823,10 +812,10 @@
 gboolean gncTaxTableRegister (void)
 {
   static QofParam params[] = {
-	{ GNC_TT_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncTaxTableGetName, (QofSetterFunc)gncTaxTableSetName },
-	{ GNC_TT_REFCOUNT, QOF_TYPE_INT64, (QofAccessFunc)gncTaxTableGetRefcount, NULL },
-    { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
-    { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
+	{ GNC_TT_NAME, 		QOF_TYPE_STRING, 	(QofAccessFunc)gncTaxTableGetName, 		(QofSetterFunc)gncTaxTableSetName },
+	{ GNC_TT_REFCOUNT, 	QOF_TYPE_INT64, 	(QofAccessFunc)gncTaxTableGetRefcount, 	(QofSetterFunc)gncTaxTableSetRefcount },
+    { QOF_PARAM_BOOK, 	QOF_ID_BOOK, 		(QofAccessFunc)qof_instance_get_book, 	NULL },
+    { QOF_PARAM_GUID, 	QOF_TYPE_GUID, 		(QofAccessFunc)qof_instance_get_guid, 	NULL },
     { NULL },
   };
 
Index: gncAddress.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncAddress.h,v
retrieving revision 1.11
retrieving revision 1.12
diff -Lsrc/business/business-core/gncAddress.h -Lsrc/business/business-core/gncAddress.h -u -r1.11 -r1.12
--- src/business/business-core/gncAddress.h
+++ src/business/business-core/gncAddress.h
@@ -33,15 +33,35 @@
 
 #include "qofbook.h"
 #include "qofid.h"
+#include "qofobject.h"
+#include "qofinstance.h"
+#include "qofid-p.h"
 
 #define GNC_ADDRESS_MODULE_NAME        "gncAddress"
 
+/** \struct GncAddress
+
+ at param	QofBook *	book;
+ at param	QofEntity * parent;
+ at param	gboolean	dirty;
+ at param	char *	name;
+ at param	char *	addr1;
+ at param	char *	addr2;
+ at param	char *	addr3;
+ at param	char *	addr4;
+ at param	char *	phone;
+ at param	char *	fax;
+ at param	char *	email;
+*/
 typedef struct _gncAddress GncAddress;
 
 /** @name Create/Destroy functions */
 /** @{ */
+/** create a new address */
 GncAddress * gncAddressCreate (QofBook *book, QofEntity *parent);
+/** destroy an address */
 void gncAddressDestroy (GncAddress *addr);
+
 /** @} */
 
 /** @name Set functions */
@@ -73,9 +93,18 @@
 
 gboolean gncAddressIsDirty (const GncAddress *addr);
 
+/** \brief compare two addresses 
+
+\return 0 if identical, -1 if a is empty or less than b 
+and +1 if a is more than b or if b is empty. 
+*/
 int gncAddressCompare (const GncAddress *a, const GncAddress *b);
 
 #define ADDRESS_NAME    "name"
+#define ADDRESS_ONE		"number"
+#define ADDRESS_TWO		"street"
+#define ADDRESS_THREE   "locality"
+#define ADDRESS_FOUR	"city"
 #define ADDRESS_PHONE   "phone"
 #define ADDRESS_FAX     "fax"
 #define ADDRESS_EMAIL   "email"
Index: gncOrder.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncOrder.c,v
retrieving revision 1.45
retrieving revision 1.46
diff -Lsrc/business/business-core/gncOrder.c -Lsrc/business/business-core/gncOrder.c -u -r1.45 -r1.46
--- src/business/business-core/gncOrder.c
+++ src/business/business-core/gncOrder.c
@@ -425,7 +425,7 @@
   interface_version:  QOF_OBJECT_VERSION,
   e_type:             _GNC_MOD_NAME,
   type_label:         "Order",
-  create:             NULL,
+  create:             (gpointer)gncOrderCreate,
   book_begin:         NULL,
   book_end:           NULL,
   is_dirty:           qof_collection_is_dirty,
Index: gncTaxTable.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/business/business-core/gncTaxTable.h,v
retrieving revision 1.17
retrieving revision 1.18
diff -Lsrc/business/business-core/gncTaxTable.h -Lsrc/business/business-core/gncTaxTable.h -u -r1.17 -r1.18
--- src/business/business-core/gncTaxTable.h
+++ src/business/business-core/gncTaxTable.h
@@ -31,8 +31,36 @@
 #ifndef GNC_TAXTABLE_H_
 #define GNC_TAXTABLE_H_
 
+/** @struct GncTaxTable
+
+modtime is the internal date of the last modtime\n
+See src/doc/business.txt for an explanation of the following\n
+Code that handles refcount, parent, child, invisible and children 
+is *identical* to that in ::GncBillTerm
+
+ at param	QofInstance     inst;
+ at param 	char *          name;
+ at param 	GList *         entries;
+ at param 	Timespec        modtime;	
+ at param 	gint64          refcount;
+ at param 	GncTaxTable *   parent; if non-null, we are an immutable child 
+ at param 	GncTaxTable *   child;  if non-null, we have not changed 
+ at param 	gboolean        invisible;
+ at param 	GList *         children; list of children for disconnection 
+*/
 typedef struct _gncTaxTable GncTaxTable;
+
+/** @struct GncTaxTableEntry
+
+ at param	GncTaxTable *   table;
+ at param  Account *       account;
+ at param	GncAmountType   type;
+ at param	gnc_numeric     amount;
+};
+
+*/
 typedef struct _gncTaxTableEntry GncTaxTableEntry;
+
 typedef struct _gncAccountValue GncAccountValue;
 
 #include "Account.h"
@@ -92,10 +120,6 @@
 void gncTaxTableBeginEdit (GncTaxTable *table);
 void gncTaxTableCommitEdit (GncTaxTable *table);
 
-/** added for later QOF standardisation */
-gint gncTaxTableEntryGetType_q (GncTaxTableEntry *entry);
-void gncTaxTableEntrySetType_q (GncTaxTableEntry *entry, gint type);
-
 /** @} */
 
 /** @name Get Functions */
--- /dev/null
+++ src/engine/qof_book_merge.h
@@ -0,0 +1,652 @@
+/*********************************************************************
+ * qof_book_merge.h -- api for QofBook merge with collision handling *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk>           *
+ *                                                                   *
+ * 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        *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                    *
+ *                                                                   *
+ ********************************************************************/
+
+#define _GNU_SOURCE
+#ifndef QOFBOOKMERGE_H
+#define QOFBOOKMERGE_H
+
+/** @addtogroup QOF
+	@{ */
+/** @addtogroup BookMerge Merging QofBook structures.
+
+<b>Collision handling principles.</b>\n
+\n
+	-# Always check for a ::GUID first and compare. qof_book_merge only accepts valid ::QofBook
+	data  and therefore ALL objects in the import book will	include valid GUID's.
+	-# If the original import data did not contain a GUID (e.g. an external non-GnuCash source)
+	the GUID values will have been created during the import and will not match any existing
+	GUID's in the target book so objects that do not have a GUID match cannot be assumed to
+	be ::MERGE_NEW - parameter values must be checked.
+	-# If import contains data from closed books, store the data from the closed
+	books in the current book as active. i.e. re-open the books. 
+
+- If a GUID match exists, set qof_book_mergeRule::mergeAbsolute to \a TRUE.
+	-# If ALL parameters in the import object match the target object with the same \a GUID, 
+	set ::qof_book_mergeResult to \a MERGE_ABSOLUTE.
+	-# If any parameters differ, set ::MERGE_UPDATE.
+- If the import object \a GUID does not match an existing object,
+mergeAbsolute is unchanged from the default \a FALSE
+The parameter values of the object are compared to other objects of the same
+type in the target book.
+	-# If the same data exists in the target book with a different GUID, the object 
+	is tagged as DUPLICATE.
+	-# If the data has changed, the object is tagged as REPORT. 
+	-# If the data does not match, the object is tagged as NEW
+
+More information is at http://code.neil.williamsleesmill.me.uk/
+
+Each foreach function uses g_return_if_fail checks to protect the target book. If
+any essential data is missing, the loop returns without changing the target book.
+Note that this will not set or return an error value. However, g_return is only 
+used for critical errors that arise from programming errors, not for invalid import data 
+which should be cleaned up before creating the import QofBook.
+
+Only ::qof_book_mergeInit, ::qof_book_mergeUpdateResult and ::qof_book_mergeCommit return 
+any error values to the calling process. 
+
+	@{ */
+/**@file  qof_book_merge.h
+	@brief API for merging two \c QofBook* structures with collision handling
+	@author Copyright (c) 2004 Neil Williams <linux at codehelp.co.uk>
+*/
+
+
+#include <glib.h>
+#include "qof.h"
+#include "qofinstance-p.h"
+#include "gnc-trace.h"
+
+/** \brief Results of collisions and user resolution.
+
+All rules are initialised as ::MERGE_UNDEF.
+Once the comparison is complete, each object within the import will be
+updated.
+
+::MERGE_ABSOLUTE, ::MERGE_NEW, ::MERGE_DUPLICATE and ::MERGE_UPDATE can be reported
+to the user along with all ::MERGE_REPORT objects for confirmation.
+It may be useful later to allow \a MERGE_ABSOLUTE, \a MERGE_NEW, \a MERGE_DUPLICATE and
+\a MERGE_UPDATE to not be reported, if the user sets a preferences option
+for each result. (Always accept new items: Y/N default NO, ignores all
+MERGE_NEW if set to Y etc.) This option would not require any changes
+to qof_book_merge.
+
+\a MERGE_NEW, \a MERGE_DUPLICATE and \a MERGE_UPDATE are only actioned after 
+conflicts are resolved by the user using a dialog and all \a MERGE_REPORT objects are
+re-assigned to one of MERGE_NEW, MERGE_DUPLICATE or MERGE_UPDATE. There is no automatic
+merge, even if no entities are tagged as MERGE_REPORT, the calling process must still
+check for REPORT items using ::qof_book_mergeRuleForeach and call ::qof_book_mergeCommit.
+
+\a MERGE_INVALID data should be rare and allows for user-abort - the imported file/source
+ may be corrupted and the prescence of invalid data should raise concerns that
+ the rest of the data may be corrupted, damaged or otherwise altered. If any entity is 
+ tagged as MERGE_INVALID, the merge operation will abort and leave the target book
+ completely unchanged.
+
+\a MERGE_ABSOLUTE is only used for a complete match. The import object contains 
+the same data in the same parameters with no omissions or amendments. If any data is missing, 
+amended or added, the data is labelled \a MERGE_UPDATE. 
+
+ Every piece of data has a corresponding result. Only when the count of items labelled
+ \a MERGE_REPORT is equal to zero are \a MERGE_NEW and \a MERGE_UPDATE 
+ items added to the existing book.\n \a MERGE_DUPLICATE items are silently ignored.
+ Aborting the dialog/process (by the user or in a program crash) at any point before the
+ final commit leaves the existing book completely untouched.
+*/
+typedef enum { 
+	MERGE_UNDEF, 		/**< default value before comparison is made. */
+	MERGE_ABSOLUTE, 	/**< GUID exact match, no new data - \b ignore */
+	MERGE_NEW, 			/**< import object does \b not exist in the
+							target book - \b add */
+	MERGE_REPORT, 		/**< import object needs user intervention - \b report */
+	MERGE_DUPLICATE, 	/**< import object with different GUID exactly
+							matches existing GUID - \b ignore */
+	MERGE_UPDATE, 		/**< import object matches an existing entity but 
+							includes new or modified parameter data - \b update */
+	MERGE_INVALID 		/**< import object didn't match registered object
+							or parameter types or user decided to abort - \b abort */
+}qof_book_mergeResult;
+
+
+/** \brief 	mergeData contains the essential data for any merge.
+
+Used to dictate what to merge, how to merge it, where to get the new data and
+where to put the amended data. 
+
+Combines lists of \a ::QofParam, \a ::QofEntity and \a ::qof_book_mergeRule into one struct that
+can be easily passed between callbacks. Also holds the pointers to the import and target ::QofBook 
+structures.
+	
+- targetList and mergeObjectParams change each time a new object type is set for compare. 
+- mergeList is the complete list of rules for all objects in the import book.
+
+*/
+typedef struct
+{
+	GSList 	*mergeObjectParams;	/**< GSList of ::QofParam details for each parameter in the current object. */
+	GList 	*mergeList;			/**< GSList of ::qof_book_mergeRule rules for the import data. */
+	GSList 	*targetList;		/**< GSList of ::QofEntity * for each object of this type in the target book */
+	QofBook *mergeBook;			/**< pointer to the import book for this merge operation. */
+	QofBook *targetBook;		/**< pointer to the target book for this merge operation. */
+	gboolean abort;				/**< set to TRUE if MERGE_INVALID is set. */
+}qof_book_mergeData;
+
+
+/** \brief One rule per entity, built into a single GSList for the entire merge 
+
+All rules are stored in the GSList qof_book_mergeData::mergeList.
+
+If the ::GUID matches it's the always same semantic object,
+regardless of whether other data fields are changed.
+\n	
+The boolean value mergeAbsolute defaults to \c FALSE\n
+
+NOTE 1: if mergeAbsolute == \c TRUE, ::qof_book_mergeResult will still be set to 
+::MERGE_UPDATE if parameters within this entity have been modified.
+
+NOTE 2: ::qof_book_merge_param_as_string returns \b string representations of the parameter
+data that is causing a collision. These values must \b NOT be used to set the target
+parameter - the function is provided for display purposes only, to make it simple to
+explain the collision to the user using MERGE_REPORT and the dialog.
+
+*/
+typedef struct 
+{
+	/* internal counters and reference variables */
+	gboolean mergeAbsolute;			/**< Only set if the GUID of the import matches the target */
+	gint difference;				/**< used to find best match in a book where no GUID matches */
+	gboolean updated;				/**< prevent the mergeResult from being overwritten. */
+	/* rule objects set from or by external calls */
+	QofIdType mergeType;			/**< type of comparison required for check for collision */
+	const char* mergeLabel;			/**< Descriptive label for the object type, useful for the
+											user intervention dialog. */
+	GSList *mergeParam;				/**< list of usable parameters for the object type */
+	GSList *linkedEntList;			/**< list of complex data types included in this object. 
+
+	linkedEntList contains an ::QofEntity reference to any parameter that is not
+	one of the core QOF_TYPE data types. This entity must be already registered with QOF
+	and the results of the comparison for the linked entity will modulate the mergeResult
+	of this object. e.g. if an invoice is the same value but for a different customer,
+	the invoice will be set to MERGE_REPORT and the customer as MERGE_NEW.
+	*/
+	qof_book_mergeResult mergeResult; /**< result of comparison with main ::QofBook */
+	QofEntity *importEnt;			/**< pointer to the current entity in the import book. */
+	QofEntity *targetEnt;			/**< pointer to the corresponding entity in the target book, if any. */
+}qof_book_mergeRule;
+
+
+/* ======================================================================== */
+/** @name qof_book_merge API */
+/** @{
+*/
+/** \brief Initialise the qof_book_merge process
+
+	First function of the qof_book_merge API. Every merge must begin with Init.
+
+	Requires the book to import (::QofBook *) and the book to receive the import, the target book
+	(::QofBook *). \n
+Process:
+
+ 	-# Invoke the callback ::qof_book_mergeForeachType on every registered object class definition. 
+	-# Callback obtains the registered parameter list for each object type. This provides run time 
+	access to all registered objects and all object parameters without any changes to
+	qof_book_merge - no registered object or parameter is omitted from any merge operation.
+	-# Use ::qof_object_foreach to invoke the callback ::qof_book_mergeForeach, one object at a time 
+	 on every instance stored in mergeBook. This is the first point where real data from the import 
+	 book is accessed.
+	-# qof_book_mergeForeach obtains the ::GUID for the object from the import book and runs the first
+	check on the original book, checking for any exact GUID match. With the full parameter list, 
+	the rules for this object can be created. If there is a GUID match, the data in each parameter 
+	of the import object is compared with the same semantic object in the original book. If there is
+	no GUID in the import object or no GUID match with the original book, the original book is 
+	searched to find a parameter match - checking for a ::MERGE_DUPLICATE result.
+	-# ::qof_book_mergeCompare sets the ::qof_book_mergeResult of the comparison.
+	-# Inserts the completed rule into qof_book_mergeData::mergeList GSList.
+
+\return -1 in case of error, otherwise 0.
+
+*/
+int
+qof_book_mergeInit( QofBook *importBook, QofBook *targetBook);
+
+
+/** \brief Definition of the dialog control callback routine
+
+All ::MERGE_REPORT rules must be offered for user intervention using this template.\n
+Commit will fail if any rules are still tagged as \a MERGE_REPORT.
+
+Calling processes are free to also offer MERGE_NEW, MERGE_UPDATE, MERGE_DUPLICATE and 
+MERGE_ABSOLUTE for user intervention. Attempting to query MERGE_INVALID rules
+will cause an error.
+
+For an example, consider test_rule_loop, declared as:
+
+<tt>void test_rule_loop(qof_book_mergeRule *rule, guint remainder);\n
+void test_rule_loop(qof_book_mergeRule *rule, guint remainder) \n
+{\n
+	g_return_if_fail(rule != NULL);\n
+	printf("Rule Result %s", rule->mergeType);\n
+	qof_book_mergeUpdateResult(rule,MERGE_UPDATE);\n
+}</tt>
+
+The dialog is free to call ::qof_book_mergeUpdateResult in the loop or at the end
+as long as the link between the rule and the result is maintained, e.g. by using a
+GHashTable. 
+\n
+The parameters are:
+	- rule : pointer to the ::qof_book_mergeRule that generated the collision report
+	- remainder : guint value returned from g_slist_length for the number of other
+		rules remaining with the same result. This might be useful for a progress dialog, it might not.
+		When updating MERGE_REPORT, remainder must equal zero before calling 
+		::qof_book_mergeCommit or the import will abort.
+\n
+
+If the dialog sets \b any rule result to ::MERGE_INVALID, the import will abort when
+::qof_book_mergeCommit is called. It is the responsibility of the calling 
+function to handle the error code from ::qof_book_mergeCommit, close the dialog
+and return to GnuCash. The merge routines in these files will already have halted the merge 
+operation and freed any memory allocated to merge structures before returning the error code.
+There is no need for the dialog process to report back to qof_book_merge in this situation.
+
+*/
+typedef void (* qof_book_mergeRuleForeachCB)(qof_book_mergeRule*, guint);
+
+/** \brief Dialog Control Callback
+
+This function is designed to be used to iterate over all rules tagged with a specific
+::qof_book_mergeResult value.
+
+Uses ::qof_book_get_collection with the qof_book_mergeRule::mergeType object type to
+return a collection of ::QofEntity entities from either the qof_book_mergeData::mergeBook or
+qof_book_mergeData::targetBook. Then uses ::qof_collection_lookup_entity to lookup 
+the qof_book_mergeRule::importEnt and again the qof_book_mergeRule::targetEnt to 
+return the two specific entities.
+
+*/
+void qof_book_mergeRuleForeach( qof_book_mergeRuleForeachCB, qof_book_mergeResult);
+
+
+/** \brief Holds details of each rule as the callbacks iterate over the list.
+
+*/
+struct qof_book_mergeRuleIterate {
+	qof_book_mergeRuleForeachCB   fcn;
+	qof_book_mergeRule *data;
+	GList *ruleList;
+	guint remainder;
+};
+
+/** \brief provides easy string access to parameter data for dialog use
+
+<b>Must only be used for display purposes!</b>
+
+Uses the param_getfcn to retrieve the parameter value as a string, suitable for
+display in dialogs and user intervention output. Only the parameters used in the merge
+are available, i.e. parameters where both param_getfcn and param_setfcn are not NULL.
+
+Note that the object type description (a full text version of the object name) is
+also available to the dialog as qof_book_mergeRule::mergeLabel.
+
+This allows the dialog to display the description of the object and all parameter data.
+
+*/
+char* qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt);
+
+/** \brief called by dialog callback to set the result of user intervention
+
+Set \b any rule result to ::MERGE_INVALID to abort the import when
+::qof_book_mergeCommit is called, without changing the target book.
+
+The calling process should make it absolutely clear that a merge operation 
+\b cannot be undone and that a backup copy should always be available 
+\b before a merge is initialised.
+
+Recommended method: Only offer three options to the user per rule:
+
+-# Allow import data to be merged into target data
+	- change MERGE_REPORT to MERGE_UPDATE
+-# Allow import data without an exact match to be
+	added as new
+	- change MERGE_REPORT to MERGE_NEW \b IF mergeAbsolute = FALSE
+-# Ignore import data and leave target data unchanged
+	- change MERGE_REPORT to MERGE_ABSOLUTE or MERGE_DUPLICATE
+
+Handle the required result changes in code: Check the value of
+qof_book_mergeRule::mergeAbsolute and use these principles:
+
+To ignore entities tagged as:
+- MERGE_REPORT, you must check the value of mergeAbsolute.
+	- if mergeAbsolute is TRUE, change MERGE_REPORT to MERGE_ABSOLUTE
+	- if mergeAbsolute is FALSE, change MERGE_REPORT to MERGE_DUPLICATE
+- MERGE_NEW, set MERGE_DUPLICATE.
+- MERGE_UPDATE, you must check the value of mergeAbsolute.
+	- if mergeAbsolute is TRUE, change MERGE_UPDATE to MERGE_ABSOLUTE
+	- if mergeAbsolute is FALSE, change MERGE_UPDATE to MERGE_DUPLICATE
+
+To merge entities that are not pre-set to MERGE_NEW, set MERGE_UPDATE.\n
+Attempting to merge an entity when the pre-set value was MERGE_NEW will
+force a change back to MERGE_NEW.
+
+To add entities, check mergeAbsolute is FALSE and set MERGE_NEW.\n
+An entity \b only be added if mergeAbsolute is FALSE. Attempting to
+add an entity when mergeAbsolute is TRUE will always force a MERGE_UPDATE.
+
+It is not possible to update the same rule more than once.
+
+-# \b MERGE_NEW is reserved for new objects and is only pre-set if
+all parameters, including GUID, have already failed to match any 
+relevant object. ::qof_book_mergeCommit will create new 
+entities for all rules tagged as MERGE_NEW. 
+	- if mergeAbsolute is TRUE and the user wants to import the 
+		data, requests to set MERGE_NEW will be forced to MERGE_UPDATE 
+		because an entity with that GUID already exists in the target book.
+	- if MERGE_NEW is pre-set, requests to change to MERGE_UPDATE will be 
+		ignored because a new entity is needed.
+-# \b MERGE_UPDATE is reserved for existing objects - ::qof_book_mergeCommit 
+will require a matching entity to update and will force a change to back to 
+MERGE_NEW if none is known to exist, using the principle above.
+-# \b MERGE_INVALID will cause an abort of the merge process.
+-# \b MERGE_UNDEF and \b MERGE_REPORT cannot be set - the entity result will be unchanged.
+-# \b MERGE_DUPLICATE and \b MERGE_ABSOLUTE are handled identically but are semantically
+	different - qof_book_mergeRule::mergeAbsolute is used to dictate which to set:
+	- if mergeAbsolute is TRUE but MERGE_DUPLICATE is requested,
+		force a change to MERGE_ABSOLUTE.
+	- if mergeAbsolute is FALSE but MERGE_ABSOLUTE is requested,
+		force a change to MERGE_DUPLICATE.
+
+::qof_book_mergeCommit only commits entities tagged 
+with MERGE_NEW and MERGE_UPDATE results.
+\n
+Entities tagged with MERGE_ABSOLUTE and MERGE_DUPLICATE results are ignored.
+
+\return -1 if supplied parameters are invalid or NULL, 0 on success.
+		
+*/
+int qof_book_mergeUpdateResult(qof_book_mergeRule *resolved, qof_book_mergeResult tag);
+
+
+/** \brief Commits the import data to the target book
+
+	The last function in the API and the final part of any qof_book_merge operation.
+
+qof_book_mergeCommit will abort the \b entire merge operation if any rule is set to
+::MERGE_INVALID. It is the responsibility of the calling 
+function to handle the error code from ::qof_book_mergeCommit, close the dialog
+and return to GnuCash. qof_book_mergeCommit will already have halted the merge 
+operation and freed any memory allocated to all merge structures before returning the error
+code. There is no way for the dialog process to report back to qof_book_merge in this situation.
+
+qof_book_mergeCommit checks for any entities still tagged as ::MERGE_REPORT and then proceeds
+to import all entities tagged as ::MERGE_UPDATE or ::MERGE_NEW into the target book.
+\n
+<b>This final process cannot be UNDONE!</b>\n
+\n
+
+\return 
+	- -1 if no merge has been initialised with ::qof_book_mergeInit or if any rules
+	are tagged as ::MERGE_INVALID,
+	- +1 if some entities are still tagged as \a MERGE_REPORT
+	- 0 on success.
+*/
+int
+qof_book_mergeCommit( void );
+
+/** \brief Abort the merge and free all memory allocated by the merge
+
+Sometimes, setting ::MERGE_INVALID is insufficient: e.g. if the user aborts the
+merge from outside the functions dealing with the merge ruleset. This function
+causes an immediate abort - the calling process must start again at Init if 
+a new merge is required.
+*/
+void
+qof_book_merge_abort(void);
+
+/** @} */
+
+/* ======================================================================== */
+/* Internal callback routines */
+
+/** @name Phase 1: Import book */
+/** @{
+*/
+
+/** \brief Looks up all import objects and calls ::qof_book_mergeCompare for each. 
+
+	This callback is used to obtain a list of all objects and their parameters
+	in the book to be imported.\n
+
+	Called by ::qof_book_mergeForeachType.\n
+	Receives all instances of only those objects that exist in the import book,
+	from ::qof_object_foreach. ::qof_book_mergeData contains a full list of all registered 
+	parameters for each object in the mergeObjectParams GSList. \n
+	Looks up the live parameter data (via ::QofEntity and ::QofParam), creates the rule, 
+	compares the data and stores the result of the comparison.
+
+Process:
+
+	-# Sets default ::qof_book_mergeResult as MERGE_UNDEF - undefined.\n
+	-# Obtains GUID, parameter data, type and rule.
+	-# Compares GUID with original book, sets ::qof_book_mergeData .mergeAbsolute
+	to TRUE if exact match.
+	-# Inserts rule into ::qof_book_mergeData rule list.
+	-# Runs the comparison for that data type using ::qof_book_mergeCompare.
+	
+*/
+void qof_book_mergeForeach (QofEntity* mergeEnt, gpointer mergeData);
+
+/** \brief Registered Object Callback.
+
+	Receives one object at a time from ::qof_object_foreach_type.\n
+	Note: generic type data only, no live data accesses.\n
+	::qof_object_foreach_type called directly by ::qof_book_mergeInit.
+
+	This callback is used to obtain a list of all registered
+	objects, whether or not the objects exist in either the import or
+	original books.\n
+	
+	Invokes the callback ::qof_book_mergeForeach on every instance of a particular object type.
+	The callback will be invoked only for those instances stored in the import book and therefore
+	qof_book_mergeForeach gains the first access to any live data.
+*/
+void qof_book_mergeForeachType (QofObject* merge_obj, gpointer mergeData);
+
+/** \brief Iterates over each parameter name within the selected QofObject.
+
+	 Receives the list of parameter names from ::QofParam and fills the GSList in
+	 ::qof_book_mergeData.\n
+	 No live data access - object typing and parameter listing only.\n
+	 \b Note: This function is called by ::qof_book_mergeForeachType in the comparison
+	 stage and ::qof_book_mergeCommitRuleLoop in the commit stage. Change with care!
+*/
+void qof_book_mergeForeachParam(QofParam* param_name, gpointer user_data);
+
+/** @} */
+/** @name Phase 2: Target book */
+/** @{
+*/
+
+/** \brief Registered Object Callback for the \b target book.
+
+	Receives one object at a time from ::qof_object_foreach_type.\n
+\n
+	This callback is used to iterate through all the registered
+	objects, in the \b Target book. When the target object type
+	matches the object type of the current import object, calls
+	::qof_book_mergeForeachTarget to store details of the possible target
+	matches in the GSList *targetList in ::qof_book_mergeData .
+	\n
+*/
+void qof_book_mergeForeachTypeTarget ( QofObject* merge_obj, gpointer mergeData);
+
+
+/** \brief Looks up all \b target objects of a specific type.
+
+	This callback is used to obtain a list of all suitable objects and their parameters
+	in the \b target book.\n
+\n
+	Called by ::qof_book_mergeForeachTypeTarget.\n
+	Receives all instances of only those objects that exist in the target book,
+	that match the object type of the current \b import object.
+	This is done when there is no GUID match and there is no way to know if
+	a corresponding object exists in the target book that would conflict with the
+	data in the import object.
+\n
+	Stores details of the QofEntity* of each suitable object in the target book
+	for later comparison by ::qof_book_mergeCompare .
+	
+*/
+void qof_book_mergeForeachTarget (QofEntity* mergeEnt, gpointer mergeData);
+
+/** \brief Omits target entities that have already been matched.
+
+	It is possible for two entities in the import book to match a single entity in
+	the target book, resulting in a loss of data during commit.
+	
+	qof_book_merge_target_check simply checks the GUID of all existing
+	target entities against the full list of all entities of a suitable type
+	in the ::qof_book_mergeForeachTarget iteration. Possible target entity
+	matches are only added to the qof_book_mergeData::targetList if the GUID
+	does not match.
+*/
+void qof_book_merge_target_check (QofEntity* targetEnt);
+
+/** @} */
+/** @name Phase 3: User Intervention
+*/
+/** @{
+*/
+
+/** \brief Iterates over the rules and declares the number of rules left to match
+
+	The second argument is a guint holding the remainder which might be
+	useful for progress feedback in the GUI. 
+*/
+void qof_book_mergeRuleCB(gpointer, gpointer);
+
+/** @} */
+/** @name Phase 4: Commit import to target book
+*/
+/** @{
+*/
+
+/** \brief Separates the rules according to the comparison results.
+
+	Used to create a list of all rules that match a particular ::qof_book_mergeResult.
+	Intended for ::MERGE_NEW and ::MERGE_UPDATE, it can be used for ::MERGE_ABSOLUTE or
+	::MERGE_DUPLICATE if you want to maybe report on what will be ignored in the import.
+	
+	It can \b NOT be used for ::MERGE_UNDEF, ::MERGE_INVALID or ::MERGE_REPORT.
+*/
+void qof_book_mergeCommitForeach (qof_book_mergeRuleForeachCB cb, qof_book_mergeResult mergeResult );
+
+/** \brief Iterates over the rules and declares the number of rules left to commit
+
+	The second argument is a guint holding the remainder which might be
+	useful for progress feedback in the GUI. 
+
+*/
+void qof_book_mergeCommitForeachCB(gpointer, gpointer);
+
+/** \brief Commit the data from the import to the target QofBook.
+
+	Called by ::qof_book_mergeCommit to commit data from each rule in turn.
+	Uses QofParam->param_getfcn - ::QofAccessFunc to query the import book
+	and param_setfcn - ::QofSetterFunc to update the target book.
+\n	
+	Note: Not all param_getfcn can have a matching param_setfcn.
+	Getting the balance of an account is obviously necessary to other routines
+	but is pointless in a comparison for a merge - the balance is calculated from
+	transactions, it cannot be set by the account. A discrepancy in the calculated
+	figures for an account object should not cause a MERGE_REPORT.
+\n
+ 	Limits the comparison routines to only calling param_getfcn if 
+	param_setfcn is not NULL. 
+	
+*/
+
+void qof_book_mergeCommitRuleLoop(qof_book_mergeRule *rule, guint remainder);
+
+/** @} */
+
+
+/** @name Comparison and Rule routines
+*/
+/** @{
+*/
+
+/** \brief Set the ::qof_book_mergeResult for each QofIdType parameter, using ::GUID if any.
+
+\n
+GUID's used in comparisons of entities and instances.
+
+If there is no GUID match, \a mergeData->mergeAbsolute will be set to FALSE.
+::qof_book_mergeCompare will receive a GSList of ::QofEntity * targets instead of one
+unique match and will iterate through the parameter comparisons for each member of 
+the GSList *targetList.
+
+Sets function pointers to the parameter_getter and parameter_setter routines from 
+::QofParam *mergeObjectParams and matches the comparison to the incoming data type:\n
+
+ at param mergeRule - contains the GUID, the import book reference, ::QofIdType of the object 
+that contains the parameter and mergeResult.
+
+\return -1 in case of error, otherwise 0.
+
+*/
+int 
+qof_book_mergeCompare( void );
+
+/** \brief Makes the decisions about how matches and conflicts are tagged.
+
+New rules start at:
+	- ::MERGE_ABSOLUTE\n 
+		(GUID's match; first parameter matches) OR
+	- ::MERGE_DUPLICATE\n
+		(GUID's do NOT match; first parameter DOES match) OR
+	- ::MERGE_NEW\n
+	(GUID's do NOT match; first parameters does NOT match).
+	
+If subsequent parameters in the same object FAIL a match:
+	- \a MERGE_ABSOLUTE fallsback to ::MERGE_UPDATE \n
+		(GUID matches but some parameters differ)\n
+		(guidTarget will be updated with mergeEnt)
+	- \a MERGE_DUPLICATE fallsback to ::MERGE_REPORT\n
+		(GUID does not match and some parameters do NOT match)
+	- \a MERGE_NEW fallsback to \a MERGE_REPORT
+		(GUID does not match and some parameters now DO match)
+
+<b>Comparisons without a GUID match.</b>
+	Only sets a failed match if ALL objects fail to match.
+	when absolute is FALSE, all suitable target objects are compared.
+	mergeResult is not set until all targets checked.
+	Identifies the closest match using a difference rank. This avoids 
+	using non-generic tests for object similarities. difference has a 
+	maximum value of the total number of comparable parameters and the
+	value closest to zero is used. In the case of a tie, it is
+	currently first-come-first-served. FIXME!
+
+*/
+void qof_book_mergeUpdateRule( gboolean match);
+
+/** @} */
+/** @} */
+#endif // QOFBOOKMERGE_H
--- /dev/null
+++ src/engine/qof_book_merge.c
@@ -0,0 +1,757 @@
+/*********************************************************************
+ * qof_book_merge.c -- api for QoFBook merge with collision handling *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk>           *
+ *                                                                   *
+ * 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        *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                    *
+ *                                                                   *
+ ********************************************************************/
+
+#include "qof_book_merge.h"
+static short module = MOD_IMPORT; 
+
+/* all qof_book_merge data is held in mergeData. */
+static qof_book_mergeData* mergeData = NULL;
+
+/*
+currentRule is only used when a qof_book_mergeRule is being inspected or
+tested, not when it is created. This is to avoid the need for g_new()
+every time a single rule is checked.
+
+Rules are created and freed separately, via the mergeData GList, mergeList.
+*/
+static qof_book_mergeRule* currentRule = NULL;
+
+/* ================================================================ */
+/* API functions. */
+int
+qof_book_mergeInit( QofBook *importBook, QofBook *targetBook) 
+{
+	GList *check;
+	g_return_val_if_fail((importBook != NULL)&&(targetBook != NULL), -1);
+	mergeData = g_new(qof_book_mergeData, 1);
+	mergeData->abort = FALSE;
+	mergeData->mergeList = NULL;
+	mergeData->targetList = NULL;
+	mergeData->mergeBook = importBook;
+	mergeData->targetBook = targetBook;
+	mergeData->mergeObjectParams = NULL;
+	currentRule = g_new(qof_book_mergeRule, 1);
+	qof_object_foreach_type(qof_book_mergeForeachType, NULL);
+	check = g_list_copy(mergeData->mergeList);
+	while(check != NULL) {
+		currentRule = check->data;
+		if(currentRule->mergeResult == MERGE_INVALID) {
+			qof_book_merge_abort();
+			return(-1);
+		}
+		check = g_list_next(check);
+	}
+	g_list_free(check);
+	return 0;
+}
+
+void
+qof_book_merge_abort (void)
+{
+	if(currentRule) {
+		g_slist_free(currentRule->linkedEntList);
+		g_slist_free(currentRule->mergeParam);
+		g_free(currentRule);
+	}
+	while(mergeData->mergeList != NULL) {
+		g_free(mergeData->mergeList->data);
+		mergeData->mergeList = g_list_next(mergeData->mergeList);
+	}
+	while(mergeData->targetList != NULL) {
+		g_free(mergeData->targetList->data);
+		mergeData->targetList = g_slist_next(mergeData->targetList);
+	}
+	g_list_free(mergeData->mergeList);
+	g_slist_free(mergeData->mergeObjectParams);
+	g_slist_free(mergeData->targetList);
+	g_free(mergeData);
+}
+
+char*
+qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt)
+{
+	gchar 		*stringImport;
+	char 		sa[GUID_ENCODING_LENGTH + 1];
+	KvpFrame 	*kvpImport;
+	QofType 	mergeType;
+	const GUID *guidImport;
+	gnc_numeric numericImport, 	(*numeric_getter)	(QofEntity*, QofParam*);
+	Timespec 	tsImport, 		(*date_getter)		(QofEntity*, QofParam*);
+	double 		doubleImport, 	(*double_getter)	(QofEntity*, QofParam*);
+	gboolean 	booleanImport, 	(*boolean_getter)	(QofEntity*, QofParam*);
+	gint32 		i32Import, 		(*int32_getter)		(QofEntity*, QofParam*);
+	gint64 		i64Import, 		(*int64_getter)		(QofEntity*, QofParam*);
+	
+	stringImport = NULL;
+	mergeType = qtparam->param_type;
+	if(safe_strcmp(mergeType, QOF_TYPE_STRING) == 0)  { 
+			stringImport = g_strdup(qtparam->param_getfcn(qtEnt,qtparam));
+			if(stringImport == NULL) { stringImport = ""; }
+			return stringImport;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_DATE) == 0) { 
+			date_getter = (Timespec (*)(QofEntity*, QofParam*))qtparam->param_getfcn;
+			tsImport = date_getter(qtEnt, qtparam);
+			stringImport = g_strdup_printf("%llu", tsImport.tv_sec);
+			return stringImport;
+		}
+		if((safe_strcmp(mergeType, QOF_TYPE_NUMERIC) == 0)  ||
+		(safe_strcmp(mergeType, QOF_TYPE_DEBCRED) == 0)) { 
+			numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			numericImport = numeric_getter(qtEnt,qtparam);
+			stringImport = g_strdup(gnc_numeric_to_string(numericImport));
+			return stringImport;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_GUID) == 0) { 
+			guidImport = qtparam->param_getfcn(qtEnt,qtparam);
+			guid_to_string_buff(guidImport, sa);
+			stringImport = g_strdup(sa);
+			return stringImport;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_INT32) == 0) { 
+			int32_getter = (gint32 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			i32Import = int32_getter(qtEnt, qtparam);
+			stringImport = g_strdup_printf("%u", i32Import);
+			return stringImport;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_INT64) == 0) { 
+			int64_getter = (gint64 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			i64Import = int64_getter(qtEnt, qtparam);
+			stringImport = g_strdup_printf("%llu", i64Import);
+			return stringImport;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_DOUBLE) == 0) { 
+			double_getter = (double (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			doubleImport = double_getter(qtEnt, qtparam);
+			stringImport = g_strdup_printf("%f", doubleImport);
+			return stringImport;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_BOOLEAN) == 0){ 
+			boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			booleanImport = boolean_getter(qtEnt, qtparam);
+			if(booleanImport == TRUE) { stringImport = g_strdup("TRUE"); }
+			else { stringImport = g_strdup("FALSE"); }
+			return stringImport;
+		}
+		/* "kvp" */
+		/* FIXME: how can this be a string??? */
+		if(safe_strcmp(mergeType, QOF_TYPE_KVP) == 0) { 
+			kvpImport = kvp_frame_copy(qtparam->param_getfcn(qtEnt,qtparam));
+
+			return stringImport;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_CHAR) == 0) { 
+			stringImport = g_strdup(qtparam->param_getfcn(qtEnt,qtparam));
+			return stringImport;
+		}
+	return NULL;
+}
+
+int
+qof_book_mergeUpdateResult(qof_book_mergeRule *resolved, qof_book_mergeResult tag)
+{
+	g_return_val_if_fail((resolved != NULL), -1);
+	g_return_val_if_fail((tag > 0), -1);
+	g_return_val_if_fail((tag != MERGE_REPORT), -1);
+	currentRule = resolved;
+	if((currentRule->mergeAbsolute == TRUE)&&	(tag == MERGE_DUPLICATE)) 	{ tag = MERGE_ABSOLUTE; }
+	if((currentRule->mergeAbsolute == TRUE)&&	(tag == MERGE_NEW)) 		{ tag = MERGE_UPDATE; }
+	if((currentRule->mergeAbsolute == FALSE)&&	(tag == MERGE_ABSOLUTE)) 	{ tag = MERGE_DUPLICATE; }
+	if((currentRule->mergeResult == MERGE_NEW)&&(tag == MERGE_UPDATE)) { tag = MERGE_NEW; }
+	if(currentRule->updated == FALSE) { currentRule->mergeResult = tag;	}
+	currentRule->updated = TRUE;
+	if(tag == MERGE_INVALID) {
+		mergeData->abort = TRUE;
+		qof_book_merge_abort();
+	}
+	return 0;
+}
+
+int
+qof_book_mergeCommit( void )
+{
+	GList *check;
+	
+	if(mergeData->abort == TRUE) return -1;
+	g_return_val_if_fail(mergeData != NULL, -1);
+	g_return_val_if_fail(mergeData->mergeList != NULL, -1);
+	currentRule = mergeData->mergeList->data;
+	check = g_list_copy(mergeData->mergeList);
+	while(check != NULL) {
+		currentRule = check->data;
+		if(currentRule->mergeResult == MERGE_INVALID) {
+			qof_book_merge_abort();
+			return(-1);
+		}
+		if(currentRule->mergeResult == MERGE_REPORT) {
+			g_list_free(check);
+			return 1;
+		}
+		check = g_list_next(check);
+	}
+	g_list_free(check);
+	qof_book_mergeCommitForeach( qof_book_mergeCommitRuleLoop, MERGE_NEW);
+	qof_book_mergeCommitForeach( qof_book_mergeCommitRuleLoop, MERGE_UPDATE);
+	while(mergeData->mergeList != NULL) {
+		g_free(mergeData->mergeList->data);
+		mergeData->mergeList = g_list_next(mergeData->mergeList);
+	}
+	g_list_free(mergeData->mergeList);
+	g_slist_free(mergeData->mergeObjectParams);
+	g_slist_free(mergeData->targetList);
+	g_free(mergeData);
+	return 0;
+}
+
+/* End of API functions. Internal code follows. */
+/* ==================================================================== */
+
+void qof_book_mergeRuleForeach( qof_book_mergeRuleForeachCB cb, qof_book_mergeResult mergeResult)
+{
+	struct qof_book_mergeRuleIterate iter;
+	GList *matching_rules;
+
+	g_return_if_fail(cb != NULL);
+	g_return_if_fail(mergeData != NULL);
+	g_return_if_fail(mergeResult > 0);
+	g_return_if_fail(mergeResult != MERGE_INVALID);
+	g_return_if_fail(mergeData->abort == FALSE);
+	iter.fcn = cb;
+	matching_rules = NULL;
+	iter.ruleList = g_list_copy(mergeData->mergeList);
+	while(iter.ruleList!=NULL) {
+		currentRule = iter.ruleList->data;
+		if(currentRule->mergeResult == mergeResult) {
+			matching_rules = g_list_prepend(matching_rules, currentRule);
+		}
+		iter.ruleList = g_list_next(iter.ruleList);
+	}
+	iter.remainder = g_list_length(matching_rules);
+	g_list_free(iter.ruleList);
+	g_list_foreach (matching_rules, qof_book_mergeRuleCB, &iter);
+	g_list_free(matching_rules);
+}
+
+
+void
+qof_book_mergeUpdateRule(gboolean match)
+{
+	gboolean absolute;
+
+	absolute = currentRule->mergeAbsolute;
+	if(absolute && match && currentRule->mergeResult == MERGE_UNDEF) {
+			currentRule->mergeResult = MERGE_ABSOLUTE;
+	}
+	if(absolute && !match) { currentRule->mergeResult = MERGE_UPDATE; }
+	if(!absolute && match &&currentRule->mergeResult == MERGE_UNDEF) {
+			currentRule->mergeResult = MERGE_DUPLICATE;
+	}
+	if(!absolute && !match) {
+		currentRule->difference++;
+		if(currentRule->mergeResult == MERGE_DUPLICATE) {
+			currentRule->mergeResult = MERGE_REPORT;
+		}
+	}
+}
+
+int 
+qof_book_mergeCompare( void ) 
+{
+	gchar 		*stringImport, *stringTarget, 
+				*charImport, *charTarget;
+	char 		sa[GUID_ENCODING_LENGTH + 1];
+	const GUID 	*guidImport, *guidTarget;
+	QofInstance *inst;
+	gpointer 	unknown_obj;
+	QofParam 	*qtparam;
+	KvpFrame 	*kvpImport, *kvpTarget;
+	QofIdType 	mergeParamName;
+	QofType 	mergeType;
+	GSList 		*paramList;
+	QofEntity 	*mergeEnt, *targetEnt, *childEnt;
+	Timespec 	tsImport, tsTarget, 			(*date_getter)		(QofEntity*, QofParam*);
+	gnc_numeric numericImport, numericTarget, 	(*numeric_getter)	(QofEntity*, QofParam*);
+	double 		doubleImport, doubleTarget, 	(*double_getter)	(QofEntity*, QofParam*);
+	gboolean 	absolute, mergeError, 
+				knowntype, mergeMatch, 
+				booleanImport, booleanTarget,	(*boolean_getter)	(QofEntity*, QofParam*);
+	gint32 		i32Import, i32Target, 			(*int32_getter)		(QofEntity*, QofParam*);
+	gint64 		i64Import, i64Target, 			(*int64_getter)		(QofEntity*, QofParam*);
+
+	g_return_val_if_fail((mergeData != NULL)||(currentRule != NULL), -1);
+	absolute = currentRule->mergeAbsolute;
+	mergeEnt = currentRule->importEnt;
+	targetEnt = currentRule->targetEnt;
+	paramList = currentRule->mergeParam;
+	currentRule->difference = 0;
+	currentRule->mergeResult = MERGE_UNDEF;
+	g_return_val_if_fail((targetEnt)||(mergeEnt)||(paramList), -1);
+	
+	kvpImport = kvp_frame_new();
+	kvpTarget = kvp_frame_new();
+	mergeError = FALSE;
+	while(paramList != NULL) {
+		mergeMatch = FALSE;
+		knowntype = FALSE;
+		qtparam = paramList->data;
+		mergeParamName = qtparam->param_name;
+
+		g_return_val_if_fail(mergeParamName != NULL, -1);
+		mergeType = qtparam->param_type;
+		if(safe_strcmp(mergeType, QOF_TYPE_STRING) == 0)  { 
+			stringImport = qtparam->param_getfcn(mergeEnt,qtparam);
+			stringTarget = qtparam->param_getfcn(targetEnt,qtparam);
+			/* very strict string matches may need to be relaxed. */
+			if(stringImport == NULL) { stringImport = ""; }
+			if(stringTarget == NULL) { stringTarget = ""; }
+			if(safe_strcmp(stringImport,stringTarget) == 0) { mergeMatch = TRUE; }
+			qof_book_mergeUpdateRule(mergeMatch);
+			stringImport = stringTarget = NULL;
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_DATE) == 0) {
+			date_getter = (Timespec (*)(QofEntity*, QofParam*))qtparam->param_getfcn;
+			tsImport = date_getter(mergeEnt, qtparam);
+			tsTarget = date_getter(targetEnt, qtparam);
+			if(timespec_cmp(&tsImport, &tsTarget) == 0) { mergeMatch = TRUE; }
+			qof_book_mergeUpdateRule(mergeMatch);
+			knowntype= TRUE;
+		}
+		if((safe_strcmp(mergeType, QOF_TYPE_NUMERIC) == 0)  ||
+		(safe_strcmp(mergeType, QOF_TYPE_DEBCRED) == 0)) { 
+			numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			numericImport = numeric_getter(mergeEnt,qtparam);
+			numericTarget = numeric_getter(targetEnt,qtparam);
+			if(gnc_numeric_compare (numericImport, numericTarget) == 0) { mergeMatch = TRUE; }
+			qof_book_mergeUpdateRule(mergeMatch);
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_GUID) == 0) { 
+			guidImport = qtparam->param_getfcn(mergeEnt,qtparam);
+			guidTarget = qtparam->param_getfcn(targetEnt,qtparam);
+			if(guid_compare(guidImport, guidTarget) == 0) { mergeMatch = TRUE; }
+			qof_book_mergeUpdateRule(mergeMatch);
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_INT32) == 0) { 
+			int32_getter = (gint32 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			i32Import = int32_getter(mergeEnt, qtparam);
+			i32Target = int32_getter(targetEnt, qtparam);
+			if(i32Target == i32Import) { mergeMatch = TRUE; }
+			qof_book_mergeUpdateRule(mergeMatch);
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_INT64) == 0) { 
+			int64_getter = (gint64 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			i64Import = int64_getter(mergeEnt, qtparam);
+			i64Target = int64_getter(targetEnt, qtparam);
+			if(i64Target == i64Import) { mergeMatch = TRUE; }
+			qof_book_mergeUpdateRule(mergeMatch); 
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_DOUBLE) == 0) { 
+			double_getter = (double (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			doubleImport = double_getter(mergeEnt, qtparam);
+			doubleTarget = double_getter(mergeEnt, qtparam);
+			if(doubleImport == doubleTarget) { mergeMatch = TRUE; }
+			qof_book_mergeUpdateRule(mergeMatch); 
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_BOOLEAN) == 0){ 
+			boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			booleanImport = boolean_getter(mergeEnt, qtparam);
+			booleanTarget = boolean_getter(targetEnt, qtparam);
+			if(booleanImport != FALSE && booleanImport != TRUE) { booleanImport = FALSE; }
+			if(booleanTarget != FALSE && booleanTarget != TRUE) { booleanTarget = FALSE; }
+			if(booleanImport == booleanTarget) { mergeMatch = TRUE; }
+			qof_book_mergeUpdateRule(mergeMatch);
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_KVP) == 0) { 
+			kvpImport = kvp_frame_copy(qtparam->param_getfcn(mergeEnt,qtparam));
+			kvpTarget = kvp_frame_copy(qtparam->param_getfcn(targetEnt,qtparam));
+			if(kvp_frame_compare(kvpImport, kvpTarget) == 0) { mergeMatch = TRUE; }
+			qof_book_mergeUpdateRule(mergeMatch); 
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_CHAR) == 0) { 
+			charImport = qtparam->param_getfcn(mergeEnt,qtparam);
+			charTarget = qtparam->param_getfcn(targetEnt,qtparam);
+			if(charImport == charTarget) { mergeMatch = TRUE; }
+			qof_book_mergeUpdateRule(mergeMatch); 
+			knowntype= TRUE;
+		}
+		/* no need to verify the book */
+		if(safe_strcmp(mergeType, QOF_ID_BOOK) == 0) { knowntype= TRUE;	}
+		/* deal with non-QOF type parameters : */
+		/* references to other registered QOF objects */
+		if(knowntype == FALSE) {
+			if(qof_class_is_registered(currentRule->mergeLabel)) {
+				childEnt = g_new(QofEntity,1);
+				unknown_obj = qtparam->param_getfcn(mergeEnt, qtparam);
+				inst = ((QofInstance*)(unknown_obj));
+				childEnt = &inst->entity;
+				currentRule->linkedEntList = g_slist_prepend(currentRule->linkedEntList, childEnt);
+				guidImport = qof_entity_get_guid(childEnt);
+				if(guidImport != NULL) {
+					guid_to_string_buff(guidImport, sa);
+					stringImport = g_strdup(sa);
+					printf("Test routine GUID: %s\n", stringImport);
+				}
+			}
+		}
+	g_return_val_if_fail(knowntype == TRUE, -1);
+	paramList = g_slist_next(paramList);
+	}
+	g_free(kvpImport);
+	g_free(kvpTarget);
+	return 0;
+}
+
+void
+qof_book_mergeCommitForeach (qof_book_mergeRuleForeachCB cb, qof_book_mergeResult mergeResult )
+{
+	struct qof_book_mergeRuleIterate iter;
+	GList *subList;
+
+	g_return_if_fail(cb != NULL);
+	g_return_if_fail(mergeData != NULL);
+	g_return_if_fail(mergeResult > 0);
+	g_return_if_fail((mergeResult != MERGE_INVALID)||(mergeResult != MERGE_UNDEF)||(mergeResult != MERGE_REPORT));
+
+	iter.fcn = cb;
+	subList = NULL;
+	iter.ruleList = g_list_copy(mergeData->mergeList);
+	while(iter.ruleList!=NULL) {
+		currentRule = iter.ruleList->data;
+		if(currentRule->mergeResult == mergeResult) {
+			subList = g_list_prepend(subList, currentRule);
+		}
+		iter.ruleList = g_list_next(iter.ruleList);
+	}
+	iter.remainder = g_list_length(subList);
+	g_list_foreach (subList, qof_book_mergeCommitForeachCB, &iter);
+}
+
+void qof_book_mergeCommitForeachCB(gpointer lister, gpointer arg)
+{
+	struct qof_book_mergeRuleIterate *iter;
+	
+	iter = arg;
+	iter->fcn ((qof_book_mergeRule*)lister, iter->remainder);
+	iter->remainder--;
+}
+
+
+void 
+qof_book_mergeForeach ( QofEntity* mergeEnt, gpointer user_data) 
+{
+	qof_book_mergeRule* mergeRule;
+	QofEntity *targetEnt, *best_matchEnt;
+	GUID *g;
+	gint difference;
+	GSList *c;
+	
+	g_return_if_fail(mergeEnt != NULL);
+	g = guid_malloc();
+	*g = mergeEnt->guid;
+	mergeRule = g_new(qof_book_mergeRule,1);
+	mergeRule->importEnt = 		mergeEnt;
+	mergeRule->difference = 	difference = 0;
+	mergeRule->mergeAbsolute = 	FALSE;
+	mergeRule->mergeResult = 	MERGE_UNDEF;
+	mergeRule->updated = 		FALSE;
+	mergeRule->mergeType = 		mergeEnt->e_type;
+	mergeRule->mergeLabel = 	qof_object_get_type_label(mergeEnt->e_type);
+	mergeRule->mergeParam = 	g_slist_copy(mergeData->mergeObjectParams);
+	mergeRule->linkedEntList =	NULL;
+	currentRule = mergeRule;
+	targetEnt = best_matchEnt = NULL;
+	targetEnt = qof_collection_lookup_entity (
+		qof_book_get_collection (mergeData->targetBook, mergeEnt->e_type), g);
+	if( targetEnt != NULL) { 
+		mergeRule->mergeAbsolute = TRUE;
+		mergeRule->targetEnt = targetEnt;
+		g_return_if_fail(qof_book_mergeCompare() != -1);
+		mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
+		return;
+	}
+	g_slist_free(mergeData->targetList);
+	mergeData->targetList = NULL;
+	qof_object_foreach_type(qof_book_mergeForeachTypeTarget, NULL);
+	if(g_slist_length(mergeData->targetList) == 0) {
+		mergeRule->mergeResult = MERGE_NEW;
+	}
+	difference = g_slist_length(mergeRule->mergeParam);
+	c = g_slist_copy(mergeData->targetList);
+	gnc_set_log_level(MOD_IMPORT, GNC_LOG_DEBUG);
+	while(c != NULL) {
+		mergeRule->targetEnt = c->data;
+		currentRule = mergeRule;
+		g_return_if_fail(qof_book_mergeCompare() != -1);
+		if(mergeRule->difference == 0) {
+			best_matchEnt = mergeRule->targetEnt;
+			mergeRule->mergeResult = MERGE_DUPLICATE;
+			g_slist_free(c);
+			guid_free(g);
+			return;
+		}
+		if(difference > mergeRule->difference) {
+			best_matchEnt = mergeRule->targetEnt;
+			difference = mergeRule->difference;
+		}
+		c = g_slist_next(c);
+	}
+	g_slist_free(c);
+	if(best_matchEnt != NULL ) {
+		mergeRule->targetEnt = best_matchEnt;
+		mergeRule->difference = difference;
+	}
+	else {
+		mergeRule->targetEnt = NULL;
+		mergeRule->difference = 0;
+		mergeRule->mergeResult = MERGE_NEW;
+	}
+	if(best_matchEnt != NULL ) {
+		g_return_if_fail(qof_book_mergeCompare() != -1);
+	}
+	mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
+	guid_free(g);
+	/* return to qof_book_mergeInit */
+}
+
+void qof_book_mergeForeachTarget (QofEntity* targetEnt, gpointer user_data)
+{
+	g_return_if_fail(targetEnt != NULL);
+
+	qof_book_merge_target_check(targetEnt);
+}
+
+
+void qof_book_merge_target_check (QofEntity* targetEnt)
+{
+	GList *checklist;
+	qof_book_mergeRule *destination;
+	const GUID *guid_ent, *guid_dest;
+	gboolean exists;
+	
+	exists = FALSE;
+	checklist = NULL;
+	if(mergeData->mergeList == NULL) { return; }
+	guid_ent = qof_entity_get_guid(targetEnt);
+	checklist = g_list_copy(mergeData->mergeList);
+	while(checklist != NULL) {
+		destination = checklist->data;
+		guid_dest = qof_entity_get_guid(destination->targetEnt);
+		if(guid_compare(guid_ent,guid_dest) == 0) { exists = TRUE; }
+		checklist = g_list_next(checklist);
+	}
+	if(exists == FALSE ) {
+		mergeData->targetList = g_slist_prepend(mergeData->targetList,targetEnt);
+	}
+}
+
+void 
+qof_book_mergeForeachTypeTarget ( QofObject* merge_obj, gpointer user_data) 
+{
+	g_return_if_fail(merge_obj != NULL);
+	if(safe_strcmp(merge_obj->e_type, currentRule->importEnt->e_type) == 0) {
+		qof_object_foreach(currentRule->importEnt->e_type, mergeData->targetBook, 
+			qof_book_mergeForeachTarget, NULL);
+	}
+}
+
+void 
+qof_book_mergeForeachType ( QofObject* merge_obj, gpointer user_data) 
+{
+	g_return_if_fail((merge_obj != NULL));
+	/* Skip unsupported objects */
+	if((merge_obj->create == NULL)||(merge_obj->foreach == NULL)){
+		DEBUG (" merge_obj QOF support failed %s", merge_obj->e_type);
+		return;
+	}
+
+	if(mergeData->mergeObjectParams != NULL) g_slist_free(mergeData->mergeObjectParams);
+	mergeData->mergeObjectParams = NULL;
+	qof_class_param_foreach(merge_obj->e_type, qof_book_mergeForeachParam , NULL);
+	qof_object_foreach(merge_obj->e_type, mergeData->mergeBook, qof_book_mergeForeach, NULL);
+}
+
+void 
+qof_book_mergeForeachParam( QofParam* param, gpointer user_data) 
+{
+	g_return_if_fail(param != NULL);
+	if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
+		mergeData->mergeObjectParams = g_slist_append(mergeData->mergeObjectParams, param);
+	}
+}
+
+void
+qof_book_mergeRuleCB(gpointer lister, gpointer arg)
+{
+	struct qof_book_mergeRuleIterate *iter;
+
+	g_return_if_fail(mergeData->abort == FALSE);
+	iter = arg;
+	iter->fcn ((qof_book_mergeRule*)lister, iter->remainder);
+	iter->remainder--;
+}
+
+void qof_book_mergeCommitRuleLoop(qof_book_mergeRule *rule, guint remainder) 
+{ 
+	QofInstance 	*inst;
+	gboolean		registered_type;
+	/* cm_ prefix used for variables that hold the data to commit */
+	QofParam 		*cm_param;
+	char 			*cm_string, *cm_char;
+	const GUID 		*cm_guid;
+	KvpFrame 		*cm_kvp;
+	/* function pointers and variables for parameter getters that don't use pointers normally */
+	gnc_numeric 	cm_numeric, (*numeric_getter)	(QofEntity*, QofParam*);
+	double 			cm_double, 	(*double_getter)	(QofEntity*, QofParam*);
+	gboolean 		cm_boolean, (*boolean_getter)	(QofEntity*, QofParam*);
+	gint32 			cm_i32, 	(*int32_getter)		(QofEntity*, QofParam*);
+	gint64 			cm_i64, 	(*int64_getter)		(QofEntity*, QofParam*);
+	Timespec 		cm_date, 	(*date_getter)		(QofEntity*, QofParam*);
+	/* function pointers to the parameter setters */
+	void	(*string_setter)	(QofEntity*, const char*);
+	void	(*date_setter)		(QofEntity*, Timespec);
+	void	(*numeric_setter)	(QofEntity*, gnc_numeric);
+	void	(*guid_setter)		(QofEntity*, const GUID*);
+	void	(*double_setter)	(QofEntity*, double);
+	void	(*boolean_setter)	(QofEntity*, gboolean);
+	void	(*i32_setter)		(QofEntity*, gint32);
+	void	(*i64_setter)		(QofEntity*, gint64);
+	void	(*char_setter)		(QofEntity*, char*);
+	void	(*kvp_frame_setter)	(QofEntity*, KvpFrame*);
+
+	g_return_if_fail(rule != NULL);
+	g_return_if_fail((rule->mergeResult != MERGE_NEW)||(rule->mergeResult != MERGE_UPDATE));
+
+	/* create a new object for MERGE_NEW */ 
+	if(rule->mergeResult == MERGE_NEW) {
+		inst = (QofInstance*)qof_object_new_instance(rule->importEnt->e_type, mergeData->targetBook);
+		g_return_if_fail(inst != NULL);
+		rule->targetEnt = &inst->entity;
+	}
+	/* currentRule->targetEnt is now set,
+		1. by an absolute GUID match or
+		2. by best_matchEnt and difference or
+		3. by MERGE_NEW.
+	*/
+	registered_type = FALSE;
+	while(rule->mergeParam != NULL) {
+		g_return_if_fail(rule->mergeParam->data);		
+		cm_param = rule->mergeParam->data;
+		rule->mergeType = cm_param->param_type;
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_STRING) == 0)  { 
+			cm_string = cm_param->param_getfcn(rule->importEnt, cm_param);
+			string_setter = (void(*)(QofEntity*, const char*))cm_param->param_setfcn;
+			if(string_setter != NULL) {	string_setter(rule->targetEnt, cm_string); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_DATE) == 0) { 
+			date_getter = (Timespec (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+			cm_date = date_getter(rule->importEnt, cm_param);
+			date_setter = (void(*)(QofEntity*, Timespec))cm_param->param_setfcn;
+			if(date_setter != NULL) { date_setter(rule->targetEnt, cm_date); }
+			registered_type = TRUE;
+		}
+		if((safe_strcmp(rule->mergeType, QOF_TYPE_NUMERIC) == 0)  ||
+		(safe_strcmp(rule->mergeType, QOF_TYPE_DEBCRED) == 0)) { 
+			numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+			cm_numeric = numeric_getter(rule->importEnt, cm_param);
+			numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_param->param_setfcn;
+			if(numeric_setter != NULL) { numeric_setter(rule->targetEnt, cm_numeric); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_GUID) == 0) { 
+			cm_guid = cm_param->param_getfcn(rule->importEnt, cm_param);
+			guid_setter = (void(*)(QofEntity*, const GUID*))cm_param->param_setfcn;
+			if(guid_setter != NULL) { guid_setter(rule->targetEnt, cm_guid); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_INT32) == 0) { 
+			int32_getter = (gint32 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+			cm_i32 = int32_getter(rule->importEnt, cm_param);
+			i32_setter = (void(*)(QofEntity*, gint32))cm_param->param_setfcn;
+			if(i32_setter != NULL) { i32_setter(rule->targetEnt, cm_i32); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_INT64) == 0) { 
+			int64_getter = (gint64 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+			cm_i64 = int64_getter(rule->importEnt, cm_param);
+			i64_setter = (void(*)(QofEntity*, gint64))cm_param->param_setfcn;
+			if(i64_setter != NULL) { i64_setter(rule->targetEnt, cm_i64); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_DOUBLE) == 0) { 
+			double_getter = (double (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+			cm_double = double_getter(rule->importEnt, cm_param);
+			double_setter = (void(*)(QofEntity*, double))cm_param->param_setfcn;
+			if(double_setter != NULL) { double_setter(rule->targetEnt, cm_double); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_BOOLEAN) == 0){ 
+			boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+			cm_boolean = boolean_getter(rule->importEnt, cm_param);
+			boolean_setter = (void(*)(QofEntity*, gboolean))cm_param->param_setfcn;
+			if(boolean_setter != NULL) { boolean_setter(rule->targetEnt, cm_boolean); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_KVP) == 0) { 
+			cm_kvp = kvp_frame_copy(cm_param->param_getfcn(rule->importEnt,cm_param));
+			kvp_frame_setter = (void(*)(QofEntity*, KvpFrame*))cm_param->param_setfcn;
+			if(kvp_frame_setter != NULL) { kvp_frame_setter(rule->targetEnt, cm_kvp); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_CHAR) == 0) { 
+			cm_char = cm_param->param_getfcn(rule->importEnt,cm_param);
+			char_setter = (void(*)(QofEntity*, char*))cm_param->param_setfcn;
+			if(char_setter != NULL) { char_setter(rule->targetEnt, cm_char); }
+			registered_type = TRUE;
+		}
+		if(registered_type == FALSE) {
+			if(qof_class_is_registered(rule->mergeLabel)) {
+			/* need to lookup childEnt in the target book to
+				ensure it has the right QofCollection */
+				GSList *linkage = g_slist_copy(rule->linkedEntList);
+				while(linkage != NULL) {
+					QofEntity *childEnt = linkage->data;
+					/* there may be more than one linked QofEntity for this rule */
+					if(safe_strcmp(childEnt->e_type, rule->mergeType) == 0) {
+						cm_guid = qof_entity_get_guid(childEnt);
+						QofCollection *col;
+    					col = qof_book_get_collection (mergeData->targetBook, rule->mergeType);
+					    childEnt = qof_collection_lookup_entity (col, cm_guid);
+						/* childEnt isn't used here yet. It may be too early to set */
+						/* intention is to set the parameter if childEnt is not null.
+						might have to store the param and set later, after Commit. */
+					}
+					linkage = g_slist_next(linkage);
+				}
+			}
+		}
+		rule->mergeParam = g_slist_next(rule->mergeParam);
+	}
+}
Index: gnc-pricedb.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/gnc-pricedb.c,v
retrieving revision 1.80
retrieving revision 1.81
diff -Lsrc/engine/gnc-pricedb.c -Lsrc/engine/gnc-pricedb.c -u -r1.80 -r1.81
--- src/engine/gnc-pricedb.c
+++ src/engine/gnc-pricedb.c
@@ -76,7 +76,7 @@
   return p;
 }
 
-static void 
+static void
 gnc_price_destroy (GNCPrice *p)
 {
   ENTER(" ");
@@ -151,20 +151,20 @@
 
 /* ==================================================================== */
 
-void 
+void
 gnc_price_begin_edit (GNCPrice *p)
 {
   QOF_BEGIN_EDIT (&p->inst);
 }
 
-static inline void commit_err (QofInstance *inst, QofBackendError errcode) 
+static inline void commit_err (QofInstance *inst, QofBackendError errcode)
 {
   PERR ("Failed to commit: %d", errcode);
 }
 
 static inline void noop (QofInstance *inst) {}
 
-void 
+void
 gnc_price_commit_edit (GNCPrice *p)
 {
   QOF_COMMIT_EDIT_PART1 (&p->inst);
@@ -173,13 +173,13 @@
 
 /* ==================================================================== */
 
-void 
+void
 gnc_pricedb_begin_edit (GNCPriceDB *pdb)
 {
   QOF_BEGIN_EDIT (&pdb->inst);
 }
 
-void 
+void
 gnc_pricedb_commit_edit (GNCPriceDB *pdb)
 {
   QOF_COMMIT_EDIT_PART1 (&pdb->inst);
@@ -194,10 +194,10 @@
 {
   if(!p) return;
 
-  if(!gnc_commodity_equiv(p->commodity, c)) 
+  if(!gnc_commodity_equiv(p->commodity, c))
   {
-    /* Changing the commodity requires the hash table 
-     * position to be modified. The easiest way of doing 
+    /* Changing the commodity requires the hash table
+     * position to be modified. The easiest way of doing
      * this is to remove and reinsert. */
     gnc_price_ref (p);
     remove_price (p->db, p, TRUE);
@@ -216,10 +216,10 @@
 {
   if(!p) return;
 
-  if(!gnc_commodity_equiv(p->currency, c)) 
+  if(!gnc_commodity_equiv(p->currency, c))
   {
-    /* Changing the currency requires the hash table 
-     * position to be modified. The easiest way of doing 
+    /* Changing the currency requires the hash table
+     * position to be modified. The easiest way of doing
      * this is to remove and reinsert. */
     gnc_price_ref (p);
     remove_price (p->db, p, TRUE);
@@ -236,10 +236,10 @@
 gnc_price_set_time(GNCPrice *p, Timespec t)
 {
   if(!p) return;
-  if(!timespec_equal(&(p->tmspec), &t)) 
+  if(!timespec_equal(&(p->tmspec), &t))
   {
-    /* Changing the datestamp requires the hash table 
-     * position to be modified. The easiest way of doing 
+    /* Changing the datestamp requires the hash table
+     * position to be modified. The easiest way of doing
      * this is to remove and reinsert. */
     gnc_price_ref (p);
     remove_price (p->db, p, FALSE);
@@ -256,7 +256,7 @@
 gnc_price_set_source(GNCPrice *p, const char *s)
 {
   if(!p) return;
-  if(safe_strcmp(p->source, s) != 0) 
+  if(safe_strcmp(p->source, s) != 0)
   {
     GCache *cache;
     char *tmp;
@@ -275,7 +275,7 @@
 gnc_price_set_type(GNCPrice *p, const char* type)
 {
   if(!p) return;
-  if(safe_strcmp(p->type, type) != 0) 
+  if(safe_strcmp(p->type, type) != 0)
   {
     GCache *cache;
     gchar *tmp;
@@ -294,7 +294,7 @@
 gnc_price_set_value(GNCPrice *p, gnc_numeric value)
 {
   if(!p) return;
-  if(!gnc_numeric_eq(p->value, value)) 
+  if(!gnc_numeric_eq(p->value, value))
   {
     gnc_price_begin_edit (p);
     p->value = value;
@@ -319,7 +319,7 @@
 gnc_price_lookup (const GUID *guid, QofBook *book)
 {
   QofCollection *col;
-  
+
   if (!guid || !book) return NULL;
   col = qof_book_get_collection (book, GNC_ID_PRICE);
   return (GNCPrice *) qof_collection_lookup_entity (col, guid);
@@ -465,7 +465,7 @@
   GList *found_element;
 
   if(!prices || !p) return FALSE;
-  
+
   found_element = g_list_find(*prices, p);
   if(!found_element) return TRUE;
 
@@ -558,11 +558,11 @@
   g_return_val_if_fail (book, NULL);
 
   /* There can only be one pricedb per book.  So if one exits already,
-   * then use that.  Warn user, they shouldn't be creating two ... 
+   * then use that.  Warn user, they shouldn't be creating two ...
    */
   col = qof_book_get_collection (book, GNC_ID_PRICEDB);
   result = qof_collection_get_data (col);
-  if (result) 
+  if (result)
   {
     PWARN ("A price database already exists for this book!");
     return result;
@@ -736,7 +736,7 @@
 }
 
 /* ==================================================================== */
-/* The add_price() function is a utility that only manages the 
+/* The add_price() function is a utility that only manages the
  * dual hash table instertion */
 
 static gboolean
@@ -804,7 +804,7 @@
   if (FALSE == add_price(db, p)) return FALSE;
 
   /* If we haven't been able to call the backend before, call it now */
-  if (TRUE == p->inst.dirty) 
+  if (TRUE == p->inst.dirty)
   {
     gnc_price_begin_edit(p);
     db->inst.dirty = TRUE;
@@ -986,8 +986,8 @@
 }
 
 
-static void 
-hash_values_helper(gpointer key, gpointer value, gpointer data) 
+static void
+hash_values_helper(gpointer key, gpointer value, gpointer data)
 {
   GList ** l = data;
   *l = g_list_concat(*l, g_list_copy (value));
@@ -1313,7 +1313,7 @@
       Timespec diff_next = timespec_diff(&next_t, &t);
       Timespec abs_current = timespec_abs(&diff_current);
       Timespec abs_next = timespec_abs(&diff_next);
-      
+
       if (timespec_cmp(&abs_current, &abs_next) <= 0) {
         result = current_price;
       } else {
@@ -1368,7 +1368,7 @@
       Timespec diff_next = timespec_diff(&next_t, &t);
       Timespec abs_current = timespec_abs(&diff_current);
       Timespec abs_next = timespec_abs(&diff_next);
-      
+
       if (timespec_cmp(&abs_current, &abs_next) <= 0) {
         result = current_price;
       } else {
@@ -1488,10 +1488,10 @@
 
   balance = gnc_numeric_mul (balance, currency_price_value,
                              gnc_commodity_get_fraction (new_currency),
-                             GNC_HOW_RND_ROUND);      
+                             GNC_HOW_RND_ROUND);
   balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
                              gnc_commodity_get_fraction (new_currency),
-                             GNC_HOW_RND_ROUND);      
+                             GNC_HOW_RND_ROUND);
 
   gnc_price_list_destroy(price_list);
   return balance;
@@ -1564,10 +1564,10 @@
 
   balance = gnc_numeric_mul (balance, currency_price_value,
                              gnc_commodity_get_fraction (new_currency),
-                             GNC_HOW_RND_ROUND);      
+                             GNC_HOW_RND_ROUND);
   balance = gnc_numeric_mul (balance, gnc_price_get_value (price),
                              gnc_commodity_get_fraction (new_currency),
-                             GNC_HOW_RND_ROUND);      
+                             GNC_HOW_RND_ROUND);
 
   gnc_price_list_destroy(price_list);
   return balance;
@@ -1612,7 +1612,7 @@
                        gpointer user_data)
 {
   GNCPriceDBForeachData foreach_data;
-  
+
   if(!db || !f) return FALSE;
   foreach_data.ok = TRUE;
   foreach_data.func = f;
@@ -1638,13 +1638,13 @@
   if(!a && !b) return 0;
   if(!a) return -1;
   if(!b) return 1;
-  
+
   ca = (gnc_commodity *) kvpa->key;
   cb = (gnc_commodity *) kvpb->key;
 
   cmp_result = safe_strcmp(gnc_commodity_get_namespace(ca),
                            gnc_commodity_get_namespace(cb));
-  
+
   if(cmp_result != 0) return cmp_result;
 
   return safe_strcmp(gnc_commodity_get_mnemonic(ca),
@@ -1659,7 +1659,7 @@
   GSList *currency_hashes = NULL;
   gboolean ok = TRUE;
   GSList *i = NULL;
-  
+
   if(!db || !f) return FALSE;
 
   currency_hashes = g_hash_table_key_value_pairs(db->commodity_hash);
@@ -1843,13 +1843,13 @@
 /* ==================================================================== */
 /* gncObject function implementation and registration */
 
-static void 
+static void
 pricedb_book_begin (QofBook *book)
 {
   gnc_pricedb_create(book);
 }
 
-static void 
+static void
 pricedb_book_end (QofBook *book)
 {
   /* ????? */
@@ -1870,11 +1870,11 @@
 /* ==================================================================== */
 /* a non-boolean foreach. Ugh */
 
-typedef struct 
+typedef struct
 {
   void (*func)(GNCPrice *p, gpointer user_data);
   gpointer user_data;
-} 
+}
 VoidGNCPriceDBForeachData;
 
 static void
@@ -1884,7 +1884,7 @@
   GList *node = price_list;
   VoidGNCPriceDBForeachData *foreach_data = (VoidGNCPriceDBForeachData *) user_data;
 
-  while(node) 
+  while(node)
   {
     GNCPrice *p = (GNCPrice *) node->data;
     foreach_data->func(p, foreach_data->user_data);
@@ -1905,7 +1905,7 @@
                        gpointer user_data)
 {
   VoidGNCPriceDBForeachData foreach_data;
-  
+
   if(!db || !f) return;
   foreach_data.func = f;
   foreach_data.user_data = user_data;
@@ -1915,11 +1915,11 @@
                        &foreach_data);
 }
 
-static void 
+static void
 pricedb_foreach(QofCollection *col, QofEntityForeachCB cb, gpointer data)
 {
   GNCPriceDB *db = gnc_collection_get_pricedb(col);
-  void_unstable_price_traversal(db, 
+  void_unstable_price_traversal(db,
                        (void (*)(GNCPrice *, gpointer)) cb,
                        data);
 }
@@ -1939,7 +1939,7 @@
 
   val = gnc_numeric_to_string (pr->value);
   da = qof_print_date (pr->tmspec.tv_sec);
-  
+
   commodity = gnc_price_get_commodity(pr);
   currency = gnc_price_get_currency(pr);
 
@@ -1952,7 +1952,7 @@
   return buff;
 }
 
-static QofObject pricedb_object_def = 
+static QofObject pricedb_object_def =
 {
   interface_version: QOF_OBJECT_VERSION,
   e_type:            GNC_ID_PRICE,
@@ -1967,7 +1967,7 @@
   version_cmp:       NULL,
 };
 
-gboolean 
+gboolean
 gnc_pricedb_register (void)
 {
   static QofParam params[] = {
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/Makefile.am,v
retrieving revision 1.124
retrieving revision 1.125
diff -Lsrc/engine/Makefile.am -Lsrc/engine/Makefile.am -u -r1.124 -r1.125
--- src/engine/Makefile.am
+++ src/engine/Makefile.am
@@ -1,7 +1,11 @@
+#INCLUDES = /usr/include/glib-1.2 /usr/lib/glib/include
+
+qof_book_merge_lo_INCLUDES = -DG_LOG_DOMAIN=\"qof-book-merge\"
+
 SUBDIRS = . test-core test 
 PWD := $(shell pwd)
 
-pkglib_LTLIBRARIES = libgw-engine.la libgw-kvp.la libgncmod-engine.la 
+pkglib_LTLIBRARIES = libgw-engine.la libgw-kvp.la libgncmod-engine.la
 
 AM_CFLAGS = \
 	-I${top_srcdir}/lib/libc \
@@ -44,6 +48,7 @@
   messages.c \
   policy.c \
   qofbackend.c \
+  qof_book_merge.c \
   qofbook.c \
   qofclass.c \
   qofid.c \
@@ -51,7 +56,7 @@
   qofobject.c \
   qofquery.c \
   qofquerycore.c \
-  qofsession.c
+  qofsession.c 
 
 EXTRA_libgncmod_engine_la_SOURCES = iso-4217-currencies.c
 
@@ -101,6 +106,7 @@
   qofbackend.h \
   qof-be-utils.h \
   qofbook.h \
+  qof_book_merge.h \
   qofclass.h \
   qofid.h \
   qofinstance.h \
@@ -195,6 +201,7 @@
 	rm -f gnucash g-wrapped
 	ln -sf . gnucash 
 	ln -sf . g-wrapped 
+	ln -sf ${srcdir} qof
 if GNUCASH_SEPARATE_BUILDDIR
 	for X in ${SCM_FILE_LINKS} ; do \
 	  ln -sf ${srcdir}/$$X . ; \
@@ -227,6 +234,5 @@
   gw-engine.scm gw-engine.c gw-engine.h \
   gw-kvp.scm gw-kvp.c gw-kvp.h 
 
-DISTCLEANFILES = gnucash g-wrapped .scm-links ${SCM_FILE_LINKS} \
+DISTCLEANFILES = gnucash g-wrapped qof .scm-links ${SCM_FILE_LINKS} \
                  gw-engine.html gw-kvp.html
-
--- /dev/null
+++ src/engine/test/test-book-merge.c
@@ -0,0 +1,473 @@
+/*********************************************************************
+ * test-book-merge.c -- test implementation api for QoFBook merge    *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk>           *
+ *                                                                   *
+ * 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        *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                    *
+ *                                                                   *
+ ********************************************************************/
+/*
+ * Test the gncBookMerge infrastructure.
+ */
+#include <glib.h>
+#include <libguile.h>
+#define _GNU_SOURCE
+
+#include "qofinstance-p.h"
+#include "gnc-module.h"
+#include "gnc-event-p.h"
+#include "qof.h"
+#include "qof_book_merge.h"
+#include "test-stuff.h"
+
+#include "gnc-engine.h"
+#define TEST_MODULE_NAME "book-merge-test"
+#define TEST_MODULE_DESC "Test Book Merge"
+#define OBJ_NAME "somename"
+#define OBJ_AMOUNT "anamount"
+#define OBJ_DATE "nottoday"
+#define OBJ_GUID "unique"
+#define OBJ_DISCOUNT "hefty"
+#define OBJ_VERSION "early"
+#define OBJ_MINOR "tiny"
+#define OBJ_ACTIVE "ofcourse"
+
+static void test_rule_loop (qof_book_mergeRule*, guint);
+static void test_merge (void);
+gboolean myobjRegister (void);
+
+/* simple object structure */
+typedef struct obj_s
+{
+	QofInstance inst;
+	char     	*Name;
+	gnc_numeric Amount;
+	const GUID 	*obj_guid;
+	Timespec 	date;
+	double 		discount; /* cheap pun, I know. */
+	gboolean 	active;
+	gint32   	version;
+	gint64 		minor;
+}myobj;
+
+myobj* obj_create(QofBook*);
+
+/* obvious setter functions */
+void obj_setName(myobj*,	char*);
+void obj_setGUID(myobj*,	const GUID*);
+void obj_setAmount(myobj*,  gnc_numeric);
+void obj_setDate(myobj*,	Timespec h);
+void obj_setDiscount(myobj*, double);
+void obj_setActive(myobj*,  gboolean);
+void obj_setVersion(myobj*, gint32);
+void obj_setMinor(myobj*,   gint64);
+
+/* obvious getter functions */
+char*		obj_getName(myobj*);
+const GUID*	obj_getGUID(myobj*);
+gnc_numeric obj_getAmount(myobj*);
+Timespec   	obj_getDate(myobj*);
+double		obj_getDiscount(myobj*);
+gboolean	obj_getActive(myobj*);
+gint32		obj_getVersion(myobj*);
+gint64		obj_getMinor(myobj*);
+
+myobj*
+obj_create(QofBook *book)
+{
+	myobj *g;
+	g_return_val_if_fail(book, NULL);
+	g = g_new(myobj, 1);
+	qof_instance_init (&g->inst, TEST_MODULE_NAME, book);
+	obj_setGUID(g,qof_instance_get_guid(&g->inst));
+	g->date.tv_nsec = 0;
+	g->date.tv_sec = 0;
+	g->discount = 0;
+	g->active = TRUE;
+	g->version = 1;
+	g->minor = 1;
+	gnc_engine_gen_event(&g->inst.entity, GNC_EVENT_CREATE);
+	return g;
+}
+
+void
+obj_setMinor(myobj *g, gint64 h)
+{
+	g_return_if_fail(g != NULL);
+	g->minor = h;
+}
+
+gint64
+obj_getMinor(myobj *g)
+{
+	g_return_val_if_fail((g != NULL),0);
+	return g->minor;
+}
+
+void
+obj_setVersion(myobj *g, gint32 h)
+{
+	g_return_if_fail(g != NULL);
+	g->version = h;
+}
+
+gint32
+obj_getVersion(myobj *g)
+{
+	if(!g) return 0;
+	return g->version;
+}
+
+void
+obj_setActive(myobj *g, gboolean h)
+{
+	if(!g) return;
+	g->active = h;
+}
+
+gboolean
+obj_getActive(myobj *g)
+{
+	if(!g) return FALSE;
+	return g->active;
+}
+
+void
+obj_setDiscount(myobj *g, double h)
+{
+	if(!g) return;
+	g->discount = h;
+}
+
+double
+obj_getDiscount(myobj *g)
+{
+	if(!g) return 0;
+	return g->discount;
+}
+
+void
+obj_setDate(myobj *g, Timespec h)
+{
+	if(!g) return;
+	g->date = h;
+}
+
+Timespec
+obj_getDate(myobj *g)
+{
+	Timespec ts;
+	if(!g) return ts;
+	ts = g->date;
+	return ts;
+}
+
+void
+obj_setGUID(myobj* g, const GUID* h)
+{
+	if(!g) return;
+	g->obj_guid = h;
+}
+
+const GUID* 
+obj_getGUID(myobj *g)
+{
+	if(!g) return NULL;
+	return g->obj_guid;
+}
+
+void 
+obj_setName(myobj* g, char* h)
+{
+	if(!g || !h) return;
+	g->Name = strdup(h);
+}
+
+char*
+obj_getName(myobj *g)
+{
+	if(!g) return NULL;
+	return g->Name;
+}
+
+void
+obj_setAmount(myobj *g, gnc_numeric h)
+{
+	if(!g) return;
+	g->Amount = h;
+}
+
+gnc_numeric
+obj_getAmount(myobj *g)
+{
+	if(!g) return double_to_gnc_numeric(0,0,GNC_HOW_DENOM_EXACT);
+	return g->Amount;
+}
+
+static QofObject obj_object_def = {
+  interface_version:     QOF_OBJECT_VERSION,
+  e_type:                TEST_MODULE_NAME,
+  type_label:            TEST_MODULE_DESC,
+  create:                (gpointer)obj_create,
+  book_begin:            NULL,
+  book_end:              NULL,
+  is_dirty:              NULL,
+  mark_clean:            NULL,
+  foreach:               qof_collection_foreach,
+  printable:             NULL,
+  version_cmp:           (int (*)(gpointer,gpointer)) qof_instance_version_cmp,
+};
+
+gboolean myobjRegister (void)
+{
+  static QofParam params[] = {
+	{ OBJ_NAME,		QOF_TYPE_STRING,	(QofAccessFunc)obj_getName,		(QofSetterFunc)obj_setName		},
+	{ OBJ_AMOUNT,   QOF_TYPE_NUMERIC,   (QofAccessFunc)obj_getAmount,   (QofSetterFunc)obj_setAmount	},
+	{ OBJ_GUID,		QOF_TYPE_GUID,		(QofAccessFunc)obj_getGUID,		(QofSetterFunc)obj_setGUID		},
+	{ OBJ_DATE,		QOF_TYPE_DATE,		(QofAccessFunc)obj_getDate,		(QofSetterFunc)obj_setDate		},
+	{ OBJ_DISCOUNT, QOF_TYPE_DOUBLE,	(QofAccessFunc)obj_getDiscount, (QofSetterFunc)obj_setDiscount  },
+	{ OBJ_ACTIVE,   QOF_TYPE_BOOLEAN,   (QofAccessFunc)obj_getActive,   (QofSetterFunc)obj_setActive	},
+	{ OBJ_VERSION,  QOF_TYPE_INT32,		(QofAccessFunc)obj_getVersion,  (QofSetterFunc)obj_setVersion   },
+	{ OBJ_MINOR,	QOF_TYPE_INT64,		(QofAccessFunc)obj_getMinor,	(QofSetterFunc)obj_setMinor		},
+    { QOF_PARAM_BOOK, QOF_ID_BOOK,		(QofAccessFunc)qof_instance_get_book, NULL },
+    { QOF_PARAM_GUID, QOF_TYPE_GUID,	(QofAccessFunc)qof_instance_get_guid, NULL },
+    { NULL },
+  };
+
+  qof_class_register (TEST_MODULE_NAME, NULL, params);
+
+  return qof_object_register (&obj_object_def);
+}
+
+static void 
+test_merge (void)
+{
+	QofBook *target, *import;
+	double init_value, discount;
+	myobj *import_obj, *target_obj, *new_obj;
+	int result;
+	Timespec ts, tc;
+	gboolean active;
+	gint32 version;
+	gint64 minor;
+	gchar *import_init, *target_init;
+	gnc_numeric obj_amount;
+	
+	target = qof_book_new();
+	import = qof_book_new();
+	init_value = 1.00;
+	result = 0;
+	discount = 0.5;
+	active = TRUE;
+	version = 1;
+	minor = 1;
+	import_init = "test";
+	target_init = "testing";
+	qof_date_format_set(QOF_DATE_FORMAT_UK);
+	timespecFromTime_t(&ts,time(NULL));
+
+	do_test ((NULL != target), "#1 book null");
+
+	/* import book objects - tests used */
+	do_test ((NULL != import), "#2 import null");
+	import_obj = g_new(myobj, 1);
+	do_test ((NULL != import_obj), "#3 new object fail");
+	qof_instance_init (&import_obj->inst, TEST_MODULE_NAME, import);
+	do_test ((NULL != &import_obj->inst), "#4 instance init fail");
+	obj_setGUID(import_obj,qof_instance_get_guid(&import_obj->inst));
+	do_test ((NULL != &import_obj->obj_guid), "#5 guid set fail");
+	gnc_engine_gen_event(&import_obj->inst.entity, GNC_EVENT_CREATE);
+	do_test ((NULL != &import_obj->inst.entity), "#6 gnc event create fail");
+	obj_setName(import_obj, import_init);
+	do_test ((NULL != &import_obj->Name), "#7 string set");
+	obj_amount = double_to_gnc_numeric(init_value,1, GNC_HOW_DENOM_EXACT);
+	obj_setAmount(import_obj, obj_amount);
+	do_test ((gnc_numeric_check(obj_getAmount(import_obj)) == GNC_ERROR_OK), "#8 gnc_numeric set");
+	obj_setActive(import_obj, active);
+	do_test ((FALSE != &import_obj->active), "#9 gboolean set");
+	obj_setDiscount(import_obj, discount);
+	do_test ((discount == import_obj->discount), "#10 double set");
+	obj_setVersion(import_obj, version);
+	do_test ((version == import_obj->version), "#11 gint32 set");
+	obj_setMinor(import_obj, minor);
+	do_test ((minor == import_obj->minor), "#12 gint64 set");
+	obj_setDate(import_obj, ts );
+	tc = import_obj->date;
+	do_test ((timespec_cmp(&ts, &tc) == 0), "#13 date set");
+
+	obj_amount = gnc_numeric_add(obj_amount, obj_amount, 1, GNC_HOW_DENOM_EXACT);
+	discount = 0.25;
+	version = 2;
+	minor = 3;
+
+	/* second import object - test results would be the same, so not tested. */
+	new_obj = g_new(myobj, 1);
+	qof_instance_init (&new_obj->inst, TEST_MODULE_NAME, import);
+	obj_setGUID(new_obj,qof_instance_get_guid(&new_obj->inst));
+	gnc_engine_gen_event (&new_obj->inst.entity, GNC_EVENT_CREATE);
+	obj_setName(new_obj, import_init);
+	obj_setAmount(new_obj, obj_amount);
+	obj_setActive(new_obj, active);
+	obj_setDiscount(new_obj, discount);
+	obj_setVersion(new_obj, version);
+	obj_setMinor(new_obj, minor);
+	obj_setDate(new_obj, ts);
+
+	obj_amount = gnc_numeric_add(obj_amount, obj_amount, 1, GNC_HOW_DENOM_EXACT);
+	discount = 0.35;
+	version = 3;
+	minor = 6;
+	tc.tv_sec = ts.tv_sec -1;
+	tc.tv_nsec = 0;
+
+	/* target object - test results would be the same, so not tested. */
+	target_obj = g_new(myobj, 1);
+	qof_instance_init (&target_obj->inst, TEST_MODULE_NAME, target);
+	obj_setGUID(target_obj,qof_instance_get_guid(&target_obj->inst));
+	gnc_engine_gen_event (&target_obj->inst.entity, GNC_EVENT_CREATE);
+	obj_setName(target_obj, target_init);
+	obj_setAmount(target_obj, obj_amount);
+	obj_setActive(target_obj, active);
+	obj_setDiscount(target_obj, discount);
+	obj_setVersion(target_obj, version);
+	obj_setMinor(target_obj, minor);
+	obj_setDate(target_obj, tc );
+	
+	result = qof_book_mergeInit(import, target);
+	do_test ( result != -1, "FATAL: Merge could not be initialised!\t aborting . . ");
+	g_return_if_fail(result != -1);
+ 	qof_book_mergeRuleForeach(test_rule_loop, MERGE_REPORT);
+	qof_book_mergeRuleForeach(test_rule_loop, MERGE_UPDATE);
+	qof_book_mergeRuleForeach(test_rule_loop, MERGE_NEW);
+ 	/* reserved calls - test only */
+ 	qof_book_mergeRuleForeach(test_rule_loop, MERGE_ABSOLUTE);
+ 	qof_book_mergeRuleForeach(test_rule_loop, MERGE_DUPLICATE);
+
+	/* import should not be in the target - pass if import_init fails match with target */
+	do_test (((safe_strcmp(obj_getName(import_obj),obj_getName(target_obj))) != 0), "Init value test #1");
+	
+	/* a good commit returns zero */
+ 	do_test (qof_book_mergeCommit() == 0, "Commit failed");
+
+	/* import should be in the target - pass if import_init matches target */
+	do_test (((safe_strcmp(import_init,obj_getName(target_obj))) == 0), "Merged value test #1");
+
+	/* import should be the same as target - pass if values are the same */
+	do_test (((safe_strcmp(obj_getName(target_obj),obj_getName(import_obj))) == 0), "Merged value test #2");
+
+	/* check that the Amount really is a gnc_numeric */
+	do_test ((gnc_numeric_check(obj_getAmount(import_obj)) == GNC_ERROR_OK), "import gnc_numeric check");
+	do_test ((gnc_numeric_check(obj_getAmount(target_obj)) == GNC_ERROR_OK), "target gnc_numeric check");
+
+	/* obj_amount was changed after the import object was set, so expect a difference. */
+	do_test ((gnc_numeric_compare(obj_getAmount(import_obj), obj_amount) != GNC_ERROR_OK),
+			"gnc_numeric value check #1");
+
+	/* obj_amount is in the target object with the import value, expect a difference/ */
+	do_test ((gnc_numeric_compare(obj_getAmount(target_obj), obj_amount) != GNC_ERROR_OK),
+			"gnc_numeric value check #2");
+	
+	/* target had a different date, so import date should now be set */
+	/* note: If sensible defaults are not set in the create: 
+	an empty Timespec caused problems with the update - fix */
+	tc = target_obj->date;
+	do_test ((timespec_cmp(&ts, &tc) == 0), "date value check: 1");
+	tc = import_obj->date;
+	do_test ((timespec_cmp(&tc, &ts) == 0), "date value check: 2");
+	do_test ((timespec_cmp(&import_obj->date, &target_obj->date) == 0), "date value check: 3");
+
+}
+
+static void
+test_rule_loop (qof_book_mergeRule *rule, guint remainder)
+{
+	GSList *testing;
+	QofParam *eachParam;
+	char *importstring;
+	char *targetstring;
+
+	/* In this test rule_loop, any lines beginning with do_test() can be removed
+	from a working rule_loop routine. It would be wise to still use some of the
+	more obvious checks, e.g. that an entity or rule exists before querying the parameters. */
+	
+	importstring = NULL;
+	targetstring = NULL;
+	do_test ((rule != NULL), "loop:#1 Rule is NULL");
+	do_test (remainder >= 0, "loop:#2 remainder too low");
+	do_test ((safe_strcmp(NULL, rule->mergeLabel) != 0), "loop:#3 object label\n");
+	do_test ((rule->importEnt != NULL), "loop:#4 empty import entity");
+	do_test ((rule->targetEnt != NULL), "loop:#5 empty target entity");
+	do_test ((safe_strcmp(rule->importEnt->e_type, rule->targetEnt->e_type) == 0), "loop:#6 entity type mismatch");
+	do_test ((rule->mergeParam != NULL), "loop:#7 empty parameter list");
+	testing = rule->mergeParam;
+	
+	while(testing != NULL) { // start of param loop
+		eachParam = testing->data;
+		do_test ((eachParam != NULL), "loop:#8 no QofParam data");
+		do_test ((eachParam->param_name != NULL), "loop:#9 no parameter name");
+		do_test ((eachParam->param_getfcn != NULL), "loop:#10 no get function");
+		do_test ((eachParam->param_setfcn != NULL), "loop:#11 no set function");
+		/* non-generic - test routines only! */
+		if(safe_strcmp(eachParam->param_type, QOF_TYPE_STRING) == 0) {
+			/* if you use this format, you would need to check the QOF_TYPE and
+			configure the get_fcn pointers yourself. This example only works for strings. */
+			importstring = g_strdup(eachParam->param_getfcn(rule->importEnt, eachParam));
+			do_test ((importstring != NULL), "loop:#12 direct get_fcn import");
+			do_test ((safe_strcmp(importstring, "test") == 0), "loop:#13 direct import comparison");
+			targetstring = eachParam->param_getfcn(rule->targetEnt, eachParam);		
+			do_test ((targetstring != NULL), "loop:#14 direct get_fcn target");
+			do_test ((safe_strcmp(targetstring, "testing") == 0), "loop:#15 direct target comparison");
+		}
+		/* param_as_string does the conversion for display purposes only */
+		/* do NOT use as_string for calculations or set_fcn */
+		importstring = qof_book_merge_param_as_string(eachParam, rule->importEnt);
+		do_test ((importstring != NULL), "loop:#16 import param_as_string is null");
+//		printf("importstring %s\t%s Type\n", importstring, eachParam->param_type);
+
+		targetstring = qof_book_merge_param_as_string(eachParam, rule->targetEnt);
+		do_test ((targetstring != NULL), "loop:#17 target param_as_string is null");
+//		printf("targetstring %s\t%s Type\n", targetstring, eachParam->param_type);
+		/* add your own code for user involvement here. */
+		/* either store the importstring and targetstring values and display separately,
+		perhaps in alphabetical/object_type/priority order, or, obtain user input as each
+		string is available. */
+		
+		testing = g_slist_next(testing);
+	} // end param loop
+	/* set each rule dependent on the user involvement response above. */
+	/* test routine just sets all to MERGE_UPDATE */
+	qof_book_mergeUpdateResult(rule,MERGE_UPDATE);
+	do_test ((rule->mergeResult == MERGE_UPDATE), "update result fail");
+}
+
+static void
+main_helper (void *closure, int argc, char **argv)
+{
+//	gnc_module_load("gnucash/engine", 0);
+	gnc_engine_init(argc, argv);
+	myobjRegister();
+	test_merge();
+	print_test_results();
+	exit(get_rv());
+}
+
+int
+main (int argc, char **argv)
+{
+	scm_boot_guile (argc, argv, main_helper, NULL);
+	return 0;
+}
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/test/Makefile.am,v
retrieving revision 1.36
retrieving revision 1.37
diff -Lsrc/engine/test/Makefile.am -Lsrc/engine/test/Makefile.am -u -r1.36 -r1.37
--- src/engine/test/Makefile.am
+++ src/engine/test/Makefile.am
@@ -1,3 +1,5 @@
+# may need to use ln -s ./ qof in src/engine
+# INCLUDES = -DG_LOG_DOMAIN=\"qof_book_merge\"
 
 AM_CFLAGS = \
   -I${top_srcdir}/src \
@@ -12,16 +14,19 @@
   ${top_builddir}/src/gnc-module/libgncmodule.la \
   ${top_builddir}/src/test-core/libgncmod-test.la \
   ../libgncmod-engine.la \
+  ../qof_book_merge.lo \
   ../libgw-engine.la \
   ../libgw-kvp.la \
   ../test-core/libgncmod-test-engine.la \
   ${GLIB_LIBS} \
-  -lltdl
+  -lltdl \
+  -lglib
 
 # these tests are ordered kind more or less in the order
 # that they should be executed, with more basic tests coming first.
 # 
 TESTS = \
+  test-book-merge \
   test-link \
   test-load-engine \
   test-guid \
@@ -40,8 +45,10 @@
   test-transaction-reversal \
   test-transaction-voiding \
   test-freq-spec \
-  test-scm-query
-
+  test-scm-query \
+  test-book-merge 
+  
+ 
 GNC_TEST_DEPS := \
   --gnc-module-dir ${top_builddir}/src/gnc-module \
   --gnc-module-dir ${top_builddir}/src/engine \
@@ -65,6 +72,7 @@
   test-load-engine \
   test-lots \
   test-numeric \
+  test-book-merge \
   test-object \
   test-period \
   test-query \
--- /dev/null
+++ src/gnome/druid-merge.h
@@ -0,0 +1,81 @@
+/********************************************************************\
+ * druid-merge.h -- account hierarchy merge functionality           *
+ * Copyright (C) 2001 Gnumatic, Inc.                                *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk>          *
+ *                                                                  *
+ * 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       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+#ifndef DRUID_MERGE_H
+#define DRUID_MERGE_H
+/** @addtogroup GUI
+	@{ */
+/** @addtogroup NewHierarchy Merging a new account tree into an existing file
+
+<b>Collision handling principles.</b>\n
+\n
+This druid builds a second ::QofBook in memory using ::QofSession and
+populates the book with accounts created using the usual New Account Tree
+code. The druid then uses ::qof_book_mergeInit to begin the merge 
+of the new book (created with QofSession) with the existing QofBook
+(loaded by the user), with user intervention and collision handling.
+
+	-# Always check for a ::GUID first and compare. qof_book_merge only accepts valid ::QofBook
+	data  and therefore ALL objects in the import book will	include valid GUID's.
+	-# If the original import data did not contain a GUID (e.g. an external non-GnuCash source)
+	the GUID values will have been created by QofSession and will not match any existing
+	GUID's in the target book so objects that do not have a GUID match cannot be assumed to
+	be ::MERGE_NEW - parameter values must be checked.
+
+- If a GUID match exists, set qof_book_mergeRule::mergeAbsolute to \a TRUE.
+	-# If ALL parameters in the import object match the target object with the same \a GUID, 
+	set ::qof_book_mergeResult to \a MERGE_ABSOLUTE.
+	-# If any parameters differ, set ::MERGE_UPDATE.
+- If the import object \a GUID does not match an existing object,
+mergeAbsolute is unchanged from the default \a FALSE
+The parameter values of the object are compared to other objects of the same
+type in the target book.
+	-# If the same data exists in the target book with a different GUID, the object 
+	is tagged as DUPLICATE.
+	-# If the data has changed, the object is tagged as REPORT. 
+	-# If the data does not match, the object is tagged as NEW
+
+More information is at http://code.neil.williamsleesmill.me.uk/
+
+Each foreach function uses g_return_if_fail checks to protect the target book. If
+any essential data is missing, the loop returns without changing the target book.
+Note that this will not set or return an error value. However, g_return is only 
+used for critical errors that arise from programming errors, not for invalid import data 
+which should be cleaned up before creating the import QofBook.
+
+Only ::qof_book_mergeInit, ::qof_book_mergeUpdateResult and ::qof_book_mergeCommit return 
+any error values to the calling process. 
+
+	@{ */
+/** @file  druid_merge.h
+	@brief API for merging two \c QofBook* structures with collision handling
+	@author Copyright (c) 2004 Neil Williams <linux at codehelp.co.uk>
+*/
+
+void gnc_ui_qof_book_merge_druid (void);
+GtkWidget* qof_book_merge_running (void);
+
+/** @} */
+/** @} */
+
+#endif
Index: window-main.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/window-main.c,v
retrieving revision 1.187
retrieving revision 1.188
diff -Lsrc/gnome/window-main.c -Lsrc/gnome/window-main.c -u -r1.187 -r1.188
--- src/gnome/window-main.c
+++ src/gnome/window-main.c
@@ -44,6 +44,7 @@
 #include "dialog-utils.h"
 #include "druid-acct-period.h"
 #include "druid-loan.h"
+#include "druid-merge.h"
 #include "gfec.h"
 #include "global-options.h"
 #include "gnc-engine.h"
@@ -77,7 +78,6 @@
 static GnomeUIInfo * gnc_main_window_toolbar_prefix (void);
 static GnomeUIInfo * gnc_main_window_toolbar_suffix (void);
 
-
 /**
  * gnc_main_window_get_mdi_child
  *
@@ -746,6 +746,12 @@
 }
 
 static void
+gnc_main_window_file_hierarchy_merge_cb(GtkWidget *w, gpointer data)
+{
+	gnc_ui_qof_book_merge_druid();
+}
+
+static void
 gnc_main_window_file_new_account_tree_cb(GtkWidget * w, gpointer data)
 {
   gnc_main_window_open_accounts(FALSE);
@@ -789,6 +795,13 @@
       gnc_main_window_file_new_account_tree_cb, NULL, NULL, 
       GNOME_APP_PIXMAP_NONE, NULL, 0, 0, NULL
     },    
+	{
+		GNOME_APP_UI_ITEM,
+		N_("Add New Account Hierarchy"),
+		N_("Extend the current book by merging with new account type categories"),
+		gnc_main_window_file_hierarchy_merge_cb, NULL, NULL,
+		GNOME_APP_PIXMAP_NONE, NULL, 0, 0, NULL
+	},
     GNOMEUIINFO_SEPARATOR,
     GNOMEUIINFO_MENU_OPEN_ITEM(gnc_main_window_file_open_cb, NULL),
     {
Index: druid-hierarchy.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/druid-hierarchy.c,v
retrieving revision 1.22
retrieving revision 1.23
diff -Lsrc/gnome/druid-hierarchy.c -Lsrc/gnome/druid-hierarchy.c -u -r1.22 -r1.23
--- src/gnome/druid-hierarchy.c
+++ src/gnome/druid-hierarchy.c
@@ -32,6 +32,7 @@
 #include "dialog-new-user.h"
 #include "dialog-utils.h"
 #include "druid-hierarchy.h"
+#include "druid-merge.h"
 #include "druid-utils.h"
 #include "gnc-amount-edit.h"
 #include "gnc-currency-edit.h"
@@ -44,7 +45,11 @@
 #include "io-example-account.h"
 #include "top-level.h"
 
+#include "gnc-trace.h"
+static short module = MOD_IMPORT; 
+
 static GtkWidget *hierarchy_window = NULL;
+GtkWidget *qof_book_merge_window = NULL;
 static AccountGroup *our_final_group = NULL;
 
 
@@ -907,120 +912,139 @@
            gpointer         arg1,
            gpointer         user_data)
 {
-  gnc_suspend_gui_refresh ();
-
-  if (our_final_group)
-    xaccGroupForEachAccount (our_final_group, starting_balance_helper,
-                             NULL, TRUE);
-
-  delete_hierarchy_window ();
-
-  gncp_new_user_finish ();
-
-  gnc_set_first_startup (FALSE);
-
-  if (our_final_group)
-    xaccGroupConcatGroup (gnc_get_current_group (), our_final_group);
-
-  gnc_resume_gui_refresh ();
+	gnc_suspend_gui_refresh ();
+	
+	if (our_final_group)
+	xaccGroupForEachAccount (our_final_group, starting_balance_helper,
+							 NULL, TRUE);
+
+	ENTER (" ");
+	qof_book_merge_window = gtk_object_get_data (GTK_OBJECT (hierarchy_window), "Merge Druid");
+	if(qof_book_merge_window) {
+		DEBUG ("qof_book_merge_window found");
+		gtk_widget_show(qof_book_merge_window);
+		delete_hierarchy_window ();
+		gnc_resume_gui_refresh ();
+		LEAVE (" ");
+		return;
+	}
+	delete_hierarchy_window ();
+	
+	gncp_new_user_finish ();
+	
+	gnc_set_first_startup (FALSE);
+	
+	if (our_final_group)
+	xaccGroupConcatGroup (gnc_get_current_group (), our_final_group);
+	
+	gnc_resume_gui_refresh ();
+	LEAVE (" ");
 }
 
 static GtkWidget *
 gnc_create_hierarchy_druid (void)
 {
-  GtkWidget *balance_edit;
-  GtkWidget *dialog;
-  GtkWidget *druid;
-  GtkWidget *clist;
-  GtkWidget *box;
-  GHashTable *hash;
-  GladeXML *xml;
-
-  xml = gnc_glade_xml_new ("account.glade", "Hierarchy Druid");
-
-  glade_xml_signal_connect
-    (xml, "on_choose_currency_prepare",
-     GTK_SIGNAL_FUNC (on_choose_currency_prepare));
-
-  glade_xml_signal_connect
-    (xml, "on_choose_account_types_prepare",
-     GTK_SIGNAL_FUNC (on_choose_account_types_prepare));
-
-  glade_xml_signal_connect
-    (xml, "on_account_types_list_select_row",
-     GTK_SIGNAL_FUNC (on_account_types_list_select_row));
-
-  glade_xml_signal_connect
-    (xml, "on_account_types_list_unselect_row",
-     GTK_SIGNAL_FUNC (on_account_types_list_unselect_row));
-
-  glade_xml_signal_connect
-    (xml, "on_final_account_prepare",
-     GTK_SIGNAL_FUNC (on_final_account_prepare));
-
-  glade_xml_signal_connect
-    (xml, "on_final_account_tree_select_row",
-     GTK_SIGNAL_FUNC (on_final_account_tree_select_row));
-
-  glade_xml_signal_connect
-    (xml, "on_final_account_tree_unselect_row",
-     GTK_SIGNAL_FUNC (on_final_account_tree_unselect_row));
-
-  glade_xml_signal_connect
-    (xml, "on_final_account_tree_placeholder_toggled",
-     GTK_SIGNAL_FUNC (on_final_account_tree_placeholder_toggled));
-
-  glade_xml_signal_connect
-    (xml, "on_final_account_next",
-     GTK_SIGNAL_FUNC (on_final_account_next));
-
-  glade_xml_signal_connect
-    (xml, "select_all_clicked", GTK_SIGNAL_FUNC (select_all_clicked));
-
-  glade_xml_signal_connect
-    (xml, "clear_all_clicked", GTK_SIGNAL_FUNC (clear_all_clicked));
-
-  glade_xml_signal_connect (xml, "on_finish", GTK_SIGNAL_FUNC (on_finish));
-
-  glade_xml_signal_connect (xml, "on_cancel", GTK_SIGNAL_FUNC (on_cancel));
-
-  dialog = glade_xml_get_widget (xml, "Hierarchy Druid");
-  gnome_window_icon_set_from_default (GTK_WINDOW (dialog));
-
-  druid = glade_xml_get_widget (xml, "hierarchy_druid");
-  gnc_druid_set_colors (GNOME_DRUID (druid));
-
-  balance_edit = gnc_amount_edit_new ();
-  gnc_amount_edit_set_evaluate_on_enter (GNC_AMOUNT_EDIT (balance_edit), TRUE);
-  gtk_widget_show (balance_edit);
-
-  gtk_signal_connect (GTK_OBJECT (balance_edit), "amount_changed",
-                      GTK_SIGNAL_FUNC(on_balance_changed), NULL);
-
-  clist = glade_xml_get_widget (xml, "account_types_clist");
-  gtk_clist_column_titles_passive (GTK_CLIST (clist));
-
-  box = glade_xml_get_widget (xml, "start_balance_box");
-  gtk_box_pack_start (GTK_BOX (box), balance_edit, TRUE, TRUE, 0);
-
-  gtk_object_set_data (GTK_OBJECT(dialog), "balance_editor", balance_edit);
-
-  hash = g_hash_table_new (g_str_hash, g_str_equal);
-
-  gtk_object_set_data (GTK_OBJECT(dialog), "balance_hash", hash);
-
-  gtk_signal_connect (GTK_OBJECT(dialog), "destroy",
-                      GTK_SIGNAL_FUNC(gnc_hierarchy_destroy_cb), NULL);
+	GtkWidget *balance_edit;
+	GtkWidget *dialog;
+	GtkWidget *druid;
+	GtkWidget *clist;
+	GtkWidget *box;
+	GHashTable *hash;
+	GladeXML *xml;
+	
+	xml = gnc_glade_xml_new ("account.glade", "Hierarchy Druid");
+	
+	glade_xml_signal_connect
+	(xml, "on_choose_currency_prepare",
+		 GTK_SIGNAL_FUNC (on_choose_currency_prepare));
+	
+	glade_xml_signal_connect
+	(xml, "on_choose_account_types_prepare",
+		 GTK_SIGNAL_FUNC (on_choose_account_types_prepare));
+	
+	glade_xml_signal_connect
+	(xml, "on_account_types_list_select_row",
+		 GTK_SIGNAL_FUNC (on_account_types_list_select_row));
+	
+	glade_xml_signal_connect
+	(xml, "on_account_types_list_unselect_row",
+		 GTK_SIGNAL_FUNC (on_account_types_list_unselect_row));
+	
+	glade_xml_signal_connect
+	(xml, "on_final_account_prepare",
+		 GTK_SIGNAL_FUNC (on_final_account_prepare));
+	
+	glade_xml_signal_connect
+	(xml, "on_final_account_tree_select_row",
+		 GTK_SIGNAL_FUNC (on_final_account_tree_select_row));
+	
+	glade_xml_signal_connect
+	(xml, "on_final_account_tree_unselect_row",
+		 GTK_SIGNAL_FUNC (on_final_account_tree_unselect_row));
+	
+	glade_xml_signal_connect
+	(xml, "on_final_account_tree_placeholder_toggled",
+		 GTK_SIGNAL_FUNC (on_final_account_tree_placeholder_toggled));
+	
+	glade_xml_signal_connect
+	(xml, "on_final_account_next",
+		 GTK_SIGNAL_FUNC (on_final_account_next));
+	
+	glade_xml_signal_connect
+	(xml, "select_all_clicked", GTK_SIGNAL_FUNC (select_all_clicked));
+	
+	glade_xml_signal_connect
+	(xml, "clear_all_clicked", GTK_SIGNAL_FUNC (clear_all_clicked));
+	
+	glade_xml_signal_connect (xml, "on_finish", GTK_SIGNAL_FUNC (on_finish));
+	
+	glade_xml_signal_connect (xml, "on_cancel", GTK_SIGNAL_FUNC (on_cancel));
+	
+	dialog = glade_xml_get_widget (xml, "Hierarchy Druid");
+	gnome_window_icon_set_from_default (GTK_WINDOW (dialog));
+	
+	druid = glade_xml_get_widget (xml, "hierarchy_druid");
+	gnc_druid_set_colors (GNOME_DRUID (druid));
+	
+	balance_edit = gnc_amount_edit_new ();
+	gnc_amount_edit_set_evaluate_on_enter (GNC_AMOUNT_EDIT (balance_edit), TRUE);
+	gtk_widget_show (balance_edit);
+	
+	gtk_signal_connect (GTK_OBJECT (balance_edit), "amount_changed",
+					  GTK_SIGNAL_FUNC(on_balance_changed), NULL);
+	
+	clist = glade_xml_get_widget (xml, "account_types_clist");
+	gtk_clist_column_titles_passive (GTK_CLIST (clist));
+	
+	box = glade_xml_get_widget (xml, "start_balance_box");
+	gtk_box_pack_start (GTK_BOX (box), balance_edit, TRUE, TRUE, 0);
+	
+	gtk_object_set_data (GTK_OBJECT(dialog), "balance_editor", balance_edit);
+	
+	hash = g_hash_table_new (g_str_hash, g_str_equal);
+	
+	gtk_object_set_data (GTK_OBJECT(dialog), "balance_hash", hash);
+	
+	gtk_signal_connect (GTK_OBJECT(dialog), "destroy",
+					  GTK_SIGNAL_FUNC(gnc_hierarchy_destroy_cb), NULL);
+	
+	return dialog;
+}
 
-  return dialog;
+GtkWidget*
+gnc_ui_hierarchy_running (void)
+{
+	if (hierarchy_window) return hierarchy_window;
+	return NULL;
 }
 
 void
 gnc_ui_hierarchy_druid (void)
 {
-  if (hierarchy_window) return;
+	if (hierarchy_window) return;
 
-  hierarchy_window = gnc_create_hierarchy_druid ();
+	hierarchy_window = gnc_create_hierarchy_druid ();
 
-  return;
+//	qof_book_merge_window = qof_book_merge_running();
+	return;
 }
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/Makefile.am,v
retrieving revision 1.110
retrieving revision 1.111
diff -Lsrc/gnome/Makefile.am -Lsrc/gnome/Makefile.am -u -r1.110 -r1.111
--- src/gnome/Makefile.am
+++ src/gnome/Makefile.am
@@ -39,6 +39,7 @@
   dialog-scheduledxaction.c \
   druid-acct-period.c \
   druid-hierarchy.c \
+  druid-merge.c \
   druid-loan.c \
   druid-stock-split.c \
   gnc-split-reg.c \
@@ -76,6 +77,7 @@
   dialog-scheduledxaction.h \
   druid-acct-period.h \
   druid-hierarchy.h \
+  druid-merge.h \
   druid-loan.h \
   druid-stock-split.h \
   gnc-network.h \
--- /dev/null
+++ src/gnome/druid-merge.c
@@ -0,0 +1,436 @@
+/********************************************************************\
+ * druid-merge.c  --  account hierarchy merge functionality         *
+ * Copyright (C) 2001 Gnumatic, Inc.                                *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk>          *
+ *                                                                  *
+ * 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       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+#include "config.h"
+
+#include <gnome.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <libgnomeui/gnome-window-icon.h>
+
+#include "dialog-utils.h"
+#include "druid-merge.h"
+#include "druid-utils.h"
+#include "gnc-component-manager.h"
+#include "gnc-gui-query.h"
+#include "qof_book_merge.h"
+#include "druid-hierarchy.h"
+
+#include "gnc-trace.h"
+//static short module = MOD_IMPORT; 
+
+static GtkWidget			*qof_book_merge_window = NULL;
+static GtkWidget			*druid_hierarchy_window = NULL;
+static QofSession			*previous_session = NULL;
+static gint					count = 0;
+static qof_book_mergeRule	*currentRule = NULL;
+static QofSession 			*merge_session = NULL;
+static QofBook				*mergeBook = NULL;
+static QofBook				*targetBook = NULL;
+static gchar 				*buffer = "";
+
+void collision_rule_loop	( qof_book_mergeRule*, 	guint );
+void progress_rule_loop 	( qof_book_mergeRule*, 	guint );
+void summary_ForeachParam	( QofParam*, 	gpointer );
+void summary_ForeachType  	( QofObject*, 	gpointer );
+void summary_Foreach 	  	( QofEntity*, 	gpointer );
+
+void 
+summary_ForeachParam( QofParam* param, gpointer user_data) 
+{
+	QofEntity *ent;
+	char *importstring;
+
+	ent = (QofEntity*)user_data;
+	/* To control the amount of output, only strings are
+		printed in this example. Remove the loop for all 
+		data. 
+	*/
+	if(safe_strcmp(param->param_type,QOF_TYPE_STRING) == 0) {
+		importstring = NULL;
+		importstring = qof_book_merge_param_as_string(param, ent);
+		printf("%-20s\t\t%s\t\t%s\n", param->param_name, param->param_type, importstring);
+	}
+}
+
+void
+summary_Foreach ( QofEntity* ent, gpointer user_data) 
+{
+	qof_class_param_foreach(ent->e_type, summary_ForeachParam , ent);
+}
+
+void 
+summary_ForeachType ( QofObject* obj, gpointer user_data) 
+{
+	QofBook *book;
+	
+	book = (QofBook*)user_data;
+	printf("\n%s\n", obj->e_type);
+	printf("Parameter name\t\t\tData type\t\tValue\n");
+	qof_object_foreach(obj->e_type, book, summary_Foreach, NULL);
+}
+
+
+static GtkWidget*
+merge_get_widget (const char *name)
+{
+  if (!qof_book_merge_window) return NULL;
+
+  return gnc_glade_lookup_widget (qof_book_merge_window, name);
+}
+
+static void
+delete_merge_window (void)
+{
+  if (!qof_book_merge_window) return;
+
+  gtk_widget_destroy (qof_book_merge_window);
+  qof_book_merge_window = NULL;
+}
+
+static void
+gnc_merge_destroy_cb (GtkObject *obj, gpointer user_data)
+{
+
+}
+
+static gboolean
+on_qof_start_page_next(GnomeDruidPage  *gnomedruidpage,
+                       gpointer         arg1,
+                       gpointer         user_data)
+{
+	gtk_widget_show(druid_hierarchy_window);
+	gtk_widget_hide(qof_book_merge_window);
+	return FALSE;
+}
+
+static void
+on_MergeUpdate_clicked 	(GtkButton       *button,
+              		    gpointer         user_data)
+{
+	qof_book_mergeUpdateResult(currentRule, MERGE_UPDATE); 
+	count = 0;
+ 	qof_book_mergeRuleForeach(collision_rule_loop, MERGE_REPORT);
+}
+
+static void
+on_MergeDuplicate_clicked 	(GtkButton       *button,
+              			    gpointer         user_data)
+{
+	if(currentRule->mergeAbsolute == FALSE) { 
+		qof_book_mergeUpdateResult(currentRule, MERGE_DUPLICATE); 
+		count = 0;
+	}
+	if(currentRule->mergeAbsolute == TRUE) { 
+		qof_book_mergeUpdateResult(currentRule, MERGE_ABSOLUTE); 
+		count = 0;
+	}
+ 	qof_book_mergeRuleForeach(collision_rule_loop, MERGE_REPORT);
+}
+
+static void
+on_MergeNew_clicked (GtkButton       *button,
+              		gpointer         user_data)
+{
+	if(currentRule->mergeAbsolute == FALSE) { 
+		qof_book_mergeUpdateResult(currentRule, MERGE_NEW);
+	}
+	count = 0;
+ 	qof_book_mergeRuleForeach(collision_rule_loop, MERGE_REPORT);
+}
+
+static gboolean
+on_qof_book_merge_next (GnomeDruidPage  *gnomedruidpage,
+                       gpointer         arg1,
+                       gpointer         user_data)
+{
+    GtkWidget *top;
+	GtkLabel *output;
+    const char *message = _("You must resolve all collisions.");
+
+	if(count > 0) {
+		top = gtk_widget_get_toplevel (GTK_WIDGET (gnomedruidpage));
+	    gnc_error_dialog(top, message);
+		return TRUE;
+	}
+	buffer = "";
+	gnc_suspend_gui_refresh ();
+	output = GTK_LABEL(merge_get_widget("OutPut"));
+	gtk_label_set_text(output, buffer);
+	gtk_widget_show(GTK_WIDGET(output));
+	gnc_resume_gui_refresh ();
+	return FALSE;
+}
+
+static void
+on_cancel (GnomeDruid      *gnomedruid,
+                 gpointer         user_data)
+{
+	gnc_suspend_gui_refresh ();
+	delete_merge_window();
+	qof_session_set_current_session(previous_session);
+	qof_book_destroy(mergeBook);
+	qof_session_end(merge_session);	
+	gnc_resume_gui_refresh ();
+}
+
+static void
+on_finish (GnomeDruidPage  *gnomedruidpage,
+           gpointer         arg1,
+           gpointer         user_data)
+{
+	gnc_suspend_gui_refresh ();
+	qof_book_mergeCommit();
+	delete_merge_window ();
+	qof_session_set_current_session(previous_session);
+	qof_book_destroy(mergeBook);
+	qof_session_end(merge_session);	
+	gnc_resume_gui_refresh ();
+}
+
+static void
+on_qof_book_merge_prepare (GnomeDruidPage  *gnomedruidpage,
+                            gpointer         arg1,
+                            gpointer         user_data)
+{
+	gint result;
+	GtkLabel *progress;
+
+    gnc_suspend_gui_refresh ();
+	progress = GTK_LABEL (merge_get_widget("ResultsBox"));
+	/* blank out old data */
+	gtk_label_set_text(progress, "");
+	result = 0;
+	g_return_if_fail(mergeBook != NULL);
+	g_return_if_fail(targetBook != NULL);
+	result = qof_book_mergeInit(mergeBook, targetBook);
+	g_return_if_fail(result == 0);
+	qof_book_mergeRuleForeach(progress_rule_loop, MERGE_NEW);
+ 	qof_book_mergeRuleForeach(progress_rule_loop, MERGE_ABSOLUTE);
+ 	qof_book_mergeRuleForeach(progress_rule_loop, MERGE_DUPLICATE);
+ 	qof_book_mergeRuleForeach(progress_rule_loop, MERGE_UPDATE);
+	gtk_label_set_text(progress, buffer);
+ 	qof_book_mergeRuleForeach(collision_rule_loop, MERGE_REPORT);
+	gnc_resume_gui_refresh ();
+}
+
+static GtkWidget *
+gnc_create_merge_druid (void)
+{
+  GtkWidget *dialog;
+  GtkWidget *druid;
+  GladeXML *xml;
+
+	xml = gnc_glade_xml_new ("merge.glade", "Merge Druid");
+
+	glade_xml_signal_connect(xml, "on_start_page_next",
+		GTK_SIGNAL_FUNC (on_qof_start_page_next));
+	
+//	Please resolve these conflicts in the merge
+//	on_qof_book_merge_prepare
+	
+	glade_xml_signal_connect(xml, "on_qof_book_merge_prepare",
+		GTK_SIGNAL_FUNC (on_qof_book_merge_prepare));
+
+	glade_xml_signal_connect(xml, "on_qof_book_merge_next",
+		GTK_SIGNAL_FUNC (on_qof_book_merge_next));
+
+	glade_xml_signal_connect (xml, "on_finish", GTK_SIGNAL_FUNC (on_finish));
+
+	glade_xml_signal_connect (xml, "on_cancel", GTK_SIGNAL_FUNC (on_cancel));
+	
+	glade_xml_signal_connect(xml, "on_MergeUpdate_clicked",
+		GTK_SIGNAL_FUNC (on_MergeUpdate_clicked));
+		
+	glade_xml_signal_connect(xml, "on_MergeDuplicate_clicked",
+		GTK_SIGNAL_FUNC (on_MergeDuplicate_clicked));
+		
+	glade_xml_signal_connect(xml, "on_MergeNew_clicked",
+		GTK_SIGNAL_FUNC (on_MergeNew_clicked));
+
+	dialog = glade_xml_get_widget (xml, "Merge Druid");
+	gnome_window_icon_set_from_default (GTK_WINDOW (dialog));
+
+	druid = glade_xml_get_widget (xml, "merge_druid");
+	gnc_druid_set_colors (GNOME_DRUID (druid));
+
+	gtk_signal_connect (GTK_OBJECT(dialog), "destroy",
+                      GTK_SIGNAL_FUNC(gnc_merge_destroy_cb), NULL);
+	return dialog;
+}
+
+void progress_rule_loop(qof_book_mergeRule *rule, guint remainder)
+{
+	GtkLabel *progress;
+	
+	progress = GTK_LABEL(merge_get_widget("ResultsBox"));
+	buffer = "";
+	g_return_if_fail(rule != NULL);
+	currentRule = rule;
+	if(rule->mergeResult == MERGE_NEW) {
+		if (remainder == 1) { 
+			buffer = g_strconcat(buffer, 
+				g_strdup_printf("%i %s tagged as NEW.\n", remainder, rule->mergeLabel), NULL);
+			gtk_label_set_text (progress, buffer);
+			}
+		else { 
+			buffer = g_strconcat(buffer, 
+				g_strdup_printf("%i entities of type %s are tagged as NEW.\n", 
+				remainder, rule->mergeLabel), NULL); 
+			gtk_label_set_text (progress, buffer);
+		}
+		gtk_widget_show(GTK_WIDGET(progress));
+		return;
+	}
+	if(rule->mergeResult ==  MERGE_ABSOLUTE) {
+		if (remainder == 1) { 
+			buffer = g_strconcat(buffer, 
+				g_strdup_printf("%i %s tagged as an absolute GUID match.\n", 
+				remainder, rule->mergeLabel), NULL); 
+			gtk_label_set_text (progress, buffer);
+		}
+		else { 
+			buffer = g_strconcat(buffer, 
+				g_strdup_printf("%i entities of type %s tagged as an absolute GUID match.\n", 
+				remainder, rule->mergeLabel), NULL);
+			gtk_label_set_text (progress, buffer);
+		}
+		gtk_widget_show(GTK_WIDGET(progress));
+		return;
+	}
+	if(rule->mergeResult == MERGE_DUPLICATE) {
+		if (remainder == 1) { 
+			buffer = g_strconcat(buffer, g_strdup_printf("%i %s tagged as a duplicate.\n", 
+				remainder, rule->mergeLabel), NULL); 
+			gtk_label_set_text (progress, buffer);
+		}
+		else { 
+			buffer = g_strconcat(buffer, g_strdup_printf("%i entities of type %s tagged as a duplicate.\n", 
+				remainder, rule->mergeLabel), NULL);
+			gtk_label_set_text (progress, buffer);
+		}
+		gtk_widget_show(GTK_WIDGET(progress));
+		return;
+	}
+	if(rule->mergeResult == MERGE_UPDATE) {
+		if (remainder == 1) { 
+			buffer = g_strconcat(buffer, g_strdup_printf("%i %s tagged as to be updated.\n", 
+				remainder, rule->mergeLabel), NULL); 
+			gtk_label_set_text (progress, buffer);
+		}
+		else { 
+			buffer = g_strconcat(buffer, g_strdup_printf("%i entities of type %s tagged as to be updated.\n", 
+				remainder, rule->mergeLabel), NULL);
+			gtk_label_set_text (progress, buffer);
+		}
+		gtk_widget_show(GTK_WIDGET(progress));
+		return;
+	}
+	g_free(buffer);
+}
+
+void collision_rule_loop(qof_book_mergeRule *rule, guint remainder)
+{
+	GSList *user_reports;
+	QofParam *one_param;
+	gchar *importstring, *targetstring;
+	gchar *buffer;
+	GtkLabel *output;
+	
+	g_return_if_fail(rule != NULL);
+	buffer = "";
+	/* there is a rule awaiting resolution, don't print any more */
+	if(count > 0) return;
+	gnc_suspend_gui_refresh ();
+	user_reports = rule->mergeParam;
+	currentRule = rule;
+	output = GTK_LABEL(merge_get_widget("OutPut"));
+	gtk_label_set_text(output, buffer);
+	gtk_widget_show(GTK_WIDGET(output));
+	gnc_resume_gui_refresh ();
+	count = 1; /* user display text counts from 1, not zero */
+	importstring = targetstring = NULL;
+	gnc_suspend_gui_refresh ();
+ 	if(remainder == 1) { 
+		buffer = g_strdup_printf("\n%i conflict needs to be resolved.\n", remainder);
+	}
+	else { 
+		buffer = g_strdup_printf("\n%i conflicts need to be resolved.\n", remainder); 
+	}
+	buffer = g_strconcat(buffer, g_strdup_printf("\n%i parameter values for this \"%s\" object.\n", 
+		   g_slist_length(user_reports), rule->targetEnt->e_type), NULL);
+	while(user_reports != NULL) {
+		one_param = user_reports->data;
+		buffer = g_strconcat(buffer, g_strdup_printf("%i:Parameter name: %s ", 
+			count, one_param->param_name), NULL);
+		importstring = qof_book_merge_param_as_string(one_param, rule->importEnt);
+		buffer = g_strconcat(buffer, g_strdup_printf("Import data : %s ", importstring), NULL);
+		targetstring = qof_book_merge_param_as_string(one_param, rule->targetEnt);
+		buffer = g_strconcat(buffer, g_strdup_printf("Original data : %s\n", targetstring), NULL);
+		user_reports = g_slist_next(user_reports);
+		count++;
+	}
+	gtk_label_set_text(output,buffer);
+	gtk_widget_show(GTK_WIDGET(output));
+	gnc_resume_gui_refresh ();
+	g_free(buffer);
+}
+
+GtkWidget*
+qof_book_merge_running (void)
+{
+	if (qof_book_merge_window) return qof_book_merge_window;
+	return NULL;
+}
+
+
+void
+gnc_ui_qof_book_merge_druid (void)
+{
+	
+	if (qof_book_merge_window) return;
+	/*	QofSession changes to avoid using current book */
+    gnc_engine_suspend_events ();
+	previous_session = qof_session_get_current_session();
+	targetBook = qof_session_get_book(previous_session);
+	merge_session = qof_session_new();
+	qof_session_set_current_session(merge_session);
+	mergeBook = qof_session_get_book(merge_session);
+	gnc_engine_resume_events ();
+	g_return_if_fail(targetBook != NULL);
+	g_return_if_fail(mergeBook != NULL);
+	g_return_if_fail(merge_session != NULL);
+	qof_book_merge_window = gnc_create_merge_druid();
+	g_return_if_fail(qof_book_merge_window != NULL);
+	gnc_ui_hierarchy_druid();
+	druid_hierarchy_window = gnc_ui_hierarchy_running();
+	gtk_widget_hide (druid_hierarchy_window);
+	gtk_object_set_data (GTK_OBJECT (druid_hierarchy_window), "Merge Druid", qof_book_merge_window);
+	gtk_widget_show (qof_book_merge_window);
+	g_return_if_fail(targetBook != NULL);
+	g_return_if_fail(mergeBook != NULL);
+	g_return_if_fail(merge_session != NULL);
+	gnc_set_log_level(MOD_IMPORT, GNC_LOG_WARNING);
+	return;
+}
Index: druid-hierarchy.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/druid-hierarchy.h,v
retrieving revision 1.1
retrieving revision 1.2
diff -Lsrc/gnome/druid-hierarchy.h -Lsrc/gnome/druid-hierarchy.h -u -r1.1 -r1.2
--- src/gnome/druid-hierarchy.h
+++ src/gnome/druid-hierarchy.h
@@ -24,5 +24,5 @@
 #define DRUID_HIERARCHY_H
 
 void gnc_ui_hierarchy_druid (void);
-
+GtkWidget* gnc_ui_hierarchy_running (void);
 #endif
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome/glade/Makefile.am,v
retrieving revision 1.23
retrieving revision 1.24
diff -Lsrc/gnome/glade/Makefile.am -Lsrc/gnome/glade/Makefile.am -u -r1.23 -r1.24
--- src/gnome/glade/Makefile.am
+++ src/gnome/glade/Makefile.am
@@ -6,6 +6,7 @@
 	fincalc.glade \
 	help.glade \
 	lots.glade \
+	merge.glade \
 	newuser.glade \
 	price.glade \
 	print.glade \
--- /dev/null
+++ src/gnome/glade/merge.glade
@@ -0,0 +1,341 @@
+<?xml version="1.0"?>
+<GTK-Interface>
+
+<project>
+  <name>Glade</name>
+  <program_name>glade</program_name>
+  <directory></directory>
+  <source_directory></source_directory>
+  <pixmaps_directory></pixmaps_directory>
+  <language>C</language>
+  <gnome_support>True</gnome_support>
+  <gettext_support>True</gettext_support>
+  <output_main_file>False</output_main_file>
+  <output_support_files>False</output_support_files>
+  <output_build_files>False</output_build_files>
+  <backup_source_files>False</backup_source_files>
+</project>
+
+<widget>
+  <class>GtkWindow</class>
+  <name>Merge Druid</name>
+  <height>560</height>
+  <title>Merge Account Hierarchy Setup</title>
+  <type>GTK_WINDOW_TOPLEVEL</type>
+  <position>GTK_WIN_POS_CENTER</position>
+  <modal>False</modal>
+  <allow_shrink>False</allow_shrink>
+  <allow_grow>True</allow_grow>
+  <auto_shrink>False</auto_shrink>
+
+  <widget>
+    <class>GnomeDruid</class>
+    <name>merge_druid</name>
+    <signal>
+      <name>cancel</name>
+      <handler>on_cancel</handler>
+      <last_modification_time>Sat, 16 Jun 2001 23:54:54 GMT</last_modification_time>
+    </signal>
+
+    <widget>
+      <class>GnomeDruidPageStart</class>
+      <name>start_page</name>
+      <signal>
+	<name>next</name>
+	<handler>on_start_page_next</handler>
+	<last_modification_time>Mon, 20 Sep 2004 11:46:19 GMT</last_modification_time>
+      </signal>
+      <title>Merge Account Hierarchy Setup</title>
+      <text>This druid will merge your new hierarchy into the currently open 
+GnuCash file.
+
+You will be asked how to proceed if some accounts clash with the
+account tree in your existing GnuCash data file.
+
+There is NO way to undo this operation! Please ensure you 
+have a backup of your file BEFORE continuing! You will be 
+given the option to cancel the merge at all stages until the 
+final merge operation. Once you click Finish, the new 
+account tree will be committed to your current data file.
+
+There is currently no currency or price support in the merge 
+operation, the new accounts will inherit any default currency
+ or you can change the currency after the merge is complete.
+
+Click 'Cancel'  if you do not wish to merge your new 
+account types now.</text>
+      <title_color>255,255,255</title_color>
+      <text_color>0,0,0</text_color>
+      <background_color>25,25,112</background_color>
+      <logo_background_color>255,255,255</logo_background_color>
+      <textbox_color>255,255,255</textbox_color>
+    </widget>
+
+    <widget>
+      <class>GnomeDruidPageStandard</class>
+      <name>summary_page</name>
+      <title>Your new accounts are ready to merge</title>
+      <title_color>255,255,255</title_color>
+      <background_color>25,25,112</background_color>
+      <logo_background_color>255,255,255</logo_background_color>
+
+      <widget>
+	<class>GtkVBox</class>
+	<child_name>GnomeDruidPageStandard:vbox</child_name>
+	<name>druid-vbox7</name>
+	<homogeneous>False</homogeneous>
+	<spacing>0</spacing>
+	<child>
+	  <padding>0</padding>
+	  <expand>True</expand>
+	  <fill>True</fill>
+	</child>
+
+	<widget>
+	  <class>GtkLabel</class>
+	  <name>label2</name>
+	  <label>The next screen will allow you to resolve
+any conflicts in merging your new account
+tree into your current GnuCash file.</label>
+	  <justify>GTK_JUSTIFY_CENTER</justify>
+	  <wrap>False</wrap>
+	  <xalign>0.5</xalign>
+	  <yalign>0.5</yalign>
+	  <xpad>0</xpad>
+	  <ypad>0</ypad>
+	  <child>
+	    <padding>0</padding>
+	    <expand>True</expand>
+	    <fill>False</fill>
+	  </child>
+	</widget>
+      </widget>
+    </widget>
+
+    <widget>
+      <class>GnomeDruidPageStandard</class>
+      <name>qof_book_merge</name>
+      <signal>
+	<name>prepare</name>
+	<handler>on_qof_book_merge_prepare</handler>
+	<last_modification_time>Tue, 14 Sep 2004 10:06:26 GMT</last_modification_time>
+      </signal>
+      <signal>
+	<name>next</name>
+	<handler>on_qof_book_merge_next</handler>
+	<last_modification_time>Tue, 14 Sep 2004 14:19:07 GMT</last_modification_time>
+      </signal>
+      <title>Please resolve any conflicts in the merge</title>
+      <title_color>255,255,255</title_color>
+      <background_color>25,25,112</background_color>
+      <logo_background_color>255,255,255</logo_background_color>
+
+      <widget>
+	<class>GtkVBox</class>
+	<child_name>GnomeDruidPageStandard:vbox</child_name>
+	<name>druid-vbox6</name>
+	<homogeneous>False</homogeneous>
+	<spacing>0</spacing>
+	<child>
+	  <padding>0</padding>
+	  <expand>True</expand>
+	  <fill>True</fill>
+	</child>
+
+	<widget>
+	  <class>GtkHBox</class>
+	  <name>hbox105</name>
+	  <homogeneous>False</homogeneous>
+	  <spacing>0</spacing>
+	  <child>
+	    <padding>0</padding>
+	    <expand>True</expand>
+	    <fill>True</fill>
+	  </child>
+
+	  <widget>
+	    <class>GtkVBox</class>
+	    <name>vbox121</name>
+	    <homogeneous>False</homogeneous>
+	    <spacing>0</spacing>
+	    <child>
+	      <padding>0</padding>
+	      <expand>True</expand>
+	      <fill>True</fill>
+	    </child>
+
+	    <widget>
+	      <class>GtkLabel</class>
+	      <name>ResultsBox</name>
+	      <label>Number of reports still to be reconciled.</label>
+	      <justify>GTK_JUSTIFY_CENTER</justify>
+	      <wrap>False</wrap>
+	      <xalign>0.5</xalign>
+	      <yalign>0.5</yalign>
+	      <xpad>0</xpad>
+	      <ypad>0</ypad>
+	      <child>
+		<padding>0</padding>
+		<expand>False</expand>
+		<fill>False</fill>
+	      </child>
+	    </widget>
+
+	    <widget>
+	      <class>GtkHBox</class>
+	      <name>hbox106</name>
+	      <homogeneous>False</homogeneous>
+	      <spacing>0</spacing>
+	      <child>
+		<padding>0</padding>
+		<expand>True</expand>
+		<fill>True</fill>
+	      </child>
+
+	      <widget>
+		<class>GtkScrolledWindow</class>
+		<name>scrolledwindow27</name>
+		<hscrollbar_policy>GTK_POLICY_NEVER</hscrollbar_policy>
+		<vscrollbar_policy>GTK_POLICY_AUTOMATIC</vscrollbar_policy>
+		<hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+		<vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+		<child>
+		  <padding>0</padding>
+		  <expand>True</expand>
+		  <fill>True</fill>
+		</child>
+
+		<widget>
+		  <class>GtkViewport</class>
+		  <name>viewport1</name>
+		  <border_width>5</border_width>
+		  <shadow_type>GTK_SHADOW_IN</shadow_type>
+
+		  <widget>
+		    <class>GtkLabel</class>
+		    <name>OutPut</name>
+		    <label></label>
+		    <justify>GTK_JUSTIFY_LEFT</justify>
+		    <wrap>False</wrap>
+		    <xalign>0.5</xalign>
+		    <yalign>0.5</yalign>
+		    <xpad>0</xpad>
+		    <ypad>0</ypad>
+		    <focus_target>MergeUpdate</focus_target>
+		  </widget>
+		</widget>
+	      </widget>
+	    </widget>
+
+	    <widget>
+	      <class>GtkLabel</class>
+	      <name>Static</name>
+	      <label>You have three choices for each collision.
+The import object can be allowed to overwrite the target - use this to update your existing book.
+The import object can be ignored - use this if the import is a duplicate of an object in the existing book.
+The import object can be created as a new object in the existing book.</label>
+	      <justify>GTK_JUSTIFY_CENTER</justify>
+	      <wrap>False</wrap>
+	      <xalign>0.5</xalign>
+	      <yalign>0.5</yalign>
+	      <xpad>0</xpad>
+	      <ypad>0</ypad>
+	      <child>
+		<padding>0</padding>
+		<expand>False</expand>
+		<fill>False</fill>
+	      </child>
+	    </widget>
+
+	    <widget>
+	      <class>GtkButton</class>
+	      <name>MergeUpdate</name>
+	      <border_width>5</border_width>
+	      <tooltip>overwrite the original with the import data</tooltip>
+	      <can_focus>True</can_focus>
+	      <has_focus>True</has_focus>
+	      <signal>
+		<name>clicked</name>
+		<handler>on_MergeUpdate_clicked</handler>
+		<last_modification_time>Wed, 22 Sep 2004 09:42:48 GMT</last_modification_time>
+	      </signal>
+	      <label>1. Update your existing book with the import data</label>
+	      <relief>GTK_RELIEF_NORMAL</relief>
+	      <child>
+		<padding>0</padding>
+		<expand>False</expand>
+		<fill>False</fill>
+	      </child>
+	    </widget>
+
+	    <widget>
+	      <class>GtkButton</class>
+	      <name>MergeDuplicate</name>
+	      <border_width>5</border_width>
+	      <tooltip>ignore the import, leave the original untouched</tooltip>
+	      <can_focus>True</can_focus>
+	      <signal>
+		<name>clicked</name>
+		<handler>on_MergeDuplicate_clicked</handler>
+		<last_modification_time>Wed, 22 Sep 2004 09:43:50 GMT</last_modification_time>
+	      </signal>
+	      <label>2. Ignore the import data, leave original unchanged</label>
+	      <relief>GTK_RELIEF_NORMAL</relief>
+	      <child>
+		<padding>0</padding>
+		<expand>False</expand>
+		<fill>False</fill>
+	      </child>
+	    </widget>
+
+	    <widget>
+	      <class>GtkButton</class>
+	      <name>MergeNew</name>
+	      <border_width>5</border_width>
+	      <tooltip>Add the import as a new object, leave original in place</tooltip>
+	      <can_focus>True</can_focus>
+	      <signal>
+		<name>clicked</name>
+		<handler>on_MergeNew_clicked</handler>
+		<last_modification_time>Wed, 22 Sep 2004 09:44:59 GMT</last_modification_time>
+	      </signal>
+	      <label>3.  Import the data as a NEW object</label>
+	      <relief>GTK_RELIEF_NORMAL</relief>
+	      <child>
+		<padding>0</padding>
+		<expand>False</expand>
+		<fill>False</fill>
+	      </child>
+	    </widget>
+	  </widget>
+	</widget>
+      </widget>
+    </widget>
+
+    <widget>
+      <class>GnomeDruidPageFinish</class>
+      <name>MergeDruidFinishPage</name>
+      <signal>
+	<name>finish</name>
+	<handler>on_finish</handler>
+	<last_modification_time>Sat, 16 Jun 2001 23:41:40 GMT</last_modification_time>
+      </signal>
+      <title>Commit Merged Account Hierachy to data file</title>
+      <text>Press `Finish' to merge your new accounts into the current GnuCash file.
+
+Press `Back' to review your selections.
+
+Press `Cancel' to close this dialog without creating any new accounts.
+
+REMEMBER: There is no way to undo this final operation!
+Make sure you have a backup before clicking Finish.</text>
+      <background_color>25,25,112</background_color>
+      <logo_background_color>255,255,255</logo_background_color>
+      <textbox_color>255,255,255</textbox_color>
+      <text_color>0,0,0</text_color>
+      <title_color>255,255,255</title_color>
+    </widget>
+  </widget>
+</widget>
+
+</GTK-Interface>


More information about the gnucash-changes mailing list