[Gnucash-changes] r13567 - gnucash/trunk - Move qof_book_merge to new naming convention and deprecate old code

Neil Williams codehelp at cvs.gnucash.org
Thu Mar 9 12:03:03 EST 2006


Author: codehelp
Date: 2006-03-09 12:03:01 -0500 (Thu, 09 Mar 2006)
New Revision: 13567
Trac: http://svn.gnucash.org/trac/changeset/13567

Added:
   gnucash/trunk/lib/libqof/qof/qofbookmerge.c
   gnucash/trunk/lib/libqof/qof/qofbookmerge.h
Removed:
   gnucash/trunk/lib/libqof/qof/qof_book_merge.c
   gnucash/trunk/lib/libqof/qof/qof_book_merge.h
Modified:
   gnucash/trunk/ChangeLog
   gnucash/trunk/lib/libqof/qof/Makefile.am
   gnucash/trunk/lib/libqof/qof/deprecated.c
   gnucash/trunk/lib/libqof/qof/deprecated.h
   gnucash/trunk/lib/libqof/qof/qof.h
   gnucash/trunk/src/engine/test/test-book-merge.c
   gnucash/trunk/src/gnome/druid-merge.c
Log:
Move qof_book_merge to new naming convention and deprecate old code

Modified: gnucash/trunk/ChangeLog
===================================================================
--- gnucash/trunk/ChangeLog	2006-03-09 16:54:44 UTC (rev 13566)
+++ gnucash/trunk/ChangeLog	2006-03-09 17:03:01 UTC (rev 13567)
@@ -1,4 +1,34 @@
 2006-03-09  Neil Williams <linux at codehelp.co.uk>
+
+	* src/gnome/druid-merge.c : Use new function names.
+	* src/engine/test/test-book-merge.c : Use new function names.
+
+	* lib/libqof/qof/qof_book_merge.c : Removed.
+	* lib/libqof/qof/qof_book_merge.h : Removed.
+	* lib/libqof/qof/qofbookmerge.c : Replacement file.
+	* lib/libqof/qof/qofbookmerge.h : Replacement file.
+	* lib/libqof/qof/qof.h : Use new filename.
+	* lib/libqof/qof/deprecated.c : Deprecate old function
+	names from qof_book_merge in favour of new from qofbookmerge.
+	* lib/libqof/qof/deprecated.h : Added deprecated function
+	declaration.s
+	* lib/libqof/qof/Makefile.am : New files.
+
+2006-03-09  Neil Williams <linux at codehelp.co.uk>
+
+	* lib/libqof/qof/qofquery-deserial.c : Update status; this
+	file will not exist in libqof2
+
+2006-03-09  Neil Williams <linux at codehelp.co.uk>
+
+	* lib/libqof/qof/qofevent.h : Doxygen tweak.
+
+2006-03-09  Neil Williams <linux at codehelp.co.uk>
+
+	* lib/libqof/backend/file/qsf-backend.c : Remove deprecated
+	glib function g_strncasecmp in favour of g_ascii_strncasecmp
+
+2006-03-09  Neil Williams <linux at codehelp.co.uk>
 	* src/optional/xsl : New directory for optional XSL
 	* src/optional/xsl/vcard-gnccustomer.pl : Perl script
 	to create QSF from VCards

Modified: gnucash/trunk/lib/libqof/qof/Makefile.am
===================================================================
--- gnucash/trunk/lib/libqof/qof/Makefile.am	2006-03-09 16:54:44 UTC (rev 13566)
+++ gnucash/trunk/lib/libqof/qof/Makefile.am	2006-03-09 17:03:01 UTC (rev 13567)
@@ -29,7 +29,7 @@
    qofquerycore.c    \
    qofreference.c    \
    qofsession.c      \
-   qof_book_merge.c
+   qofbookmerge.c
 
 qofincludedir = ${pkgincludedir}
 
@@ -62,7 +62,7 @@
    qofreference.h    \
    qofsession.h      \
    qofsql.h          \
-   qof_book_merge.h
+   qofbookmerge.h
 
 nodist_qofinclude_HEADERS = \
    qofla-dir.h     

Modified: gnucash/trunk/lib/libqof/qof/deprecated.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/deprecated.c	2006-03-09 16:54:44 UTC (rev 13566)
+++ gnucash/trunk/lib/libqof/qof/deprecated.c	2006-03-09 17:03:01 UTC (rev 13567)
@@ -79,3 +79,28 @@
 {
 	qof_event_gen(entity, event_type, NULL);
 }
+QofBookMergeData*
+qof_book_mergeInit(QofBook *importBook, QofBook *targetBook)
+{
+	return qof_book_merge_init(importBook, targetBook);
+}
+QofBookMergeData*
+qof_book_mergeUpdateResult(QofBookMergeData *mergeData,
+						QofBookMergeResult tag)
+{
+	return qof_book_merge_update_result(mergeData, tag);
+}
+gint
+qof_book_mergeCommit(QofBookMergeData *mergeData )
+{
+	return qof_book_merge_commit(mergeData);
+}
+void 
+qof_book_mergeRuleForeach(QofBookMergeData *mergeData, 
+						  QofBookMergeRuleForeachCB cb, 
+						  QofBookMergeResult mergeResult )
+{
+    qof_book_merge_rule_foreach(mergeData, cb, mergeResult);
+}
+
+/* ==================================================================== */

Modified: gnucash/trunk/lib/libqof/qof/deprecated.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/deprecated.h	2006-03-09 16:54:44 UTC (rev 13566)
+++ gnucash/trunk/lib/libqof/qof/deprecated.h	2006-03-09 17:03:01 UTC (rev 13567)
@@ -125,5 +125,25 @@
 /** \deprecated use qof_event_generate instead. */
 void gnc_engine_generate_event (const GUID *guid, QofIdType e_type, 
          GNCEngineEventType event_type);
+/** \deprecated use QofBookMergeResult instead. */
+#define qof_book_mergeResult QofBookMergeResult
+/** \deprecated use QofBookMergeRule instead. */
+#define qof_book_mergeRule QofBookMergeRule
+/** \deprecated use QofBookMergeData instead. */
+#define qof_book_mergeData QofBookMergeData
+/** \deprecated use qof_book_merge_init instead. */
+QofBookMergeData* qof_book_mergeInit( QofBook *importBook, QofBook *targetBook);
+/** \deprecated use QofBookMergeRuleForeachCB instead. */
+typedef void (* qof_book_mergeRuleForeachCB)(QofBookMergeData*, QofBookMergeRule*, guint);
+/** \deprecated use qof_book_merge_rule_foreach instead. */
+void qof_book_mergeRuleForeach(QofBookMergeData* mergeData,
+                               QofBookMergeRuleForeachCB callback , 
+                               QofBookMergeResult mergeResult);
+/** \deprecated use qof_book_merge_update_result instead. */
+QofBookMergeData*
+qof_book_mergeUpdateResult(QofBookMergeData *mergeData, QofBookMergeResult tag);
+/** \deprecated use qof_book_merge_commit instead. */
+gint
+qof_book_mergeCommit(QofBookMergeData *mergeData );
 
 #endif /* _DEPRECATED_H */

Modified: gnucash/trunk/lib/libqof/qof/qof.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof.h	2006-03-09 16:54:44 UTC (rev 13566)
+++ gnucash/trunk/lib/libqof/qof/qof.h	2006-03-09 17:03:01 UTC (rev 13567)
@@ -97,7 +97,7 @@
 #include "qofsession.h"
 #include "qofsql.h"
 #include "qofchoice.h"
-#include "qof_book_merge.h"
+#include "qofbookmerge.h"
 #include "qof-be-utils.h"
 #include "qofreference.h"
 #include "qofla-dir.h"

Deleted: gnucash/trunk/lib/libqof/qof/qof_book_merge.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof_book_merge.c	2006-03-09 16:54:44 UTC (rev 13566)
+++ gnucash/trunk/lib/libqof/qof/qof_book_merge.c	2006-03-09 17:03:01 UTC (rev 13567)
@@ -1,996 +0,0 @@
-/*********************************************************************
- * qof_book_merge.c -- api for QoFBook merge with collision handling *
- * Copyright (C) 2004-2005 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        *
- * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
- * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
- *                                                                   *
- ********************************************************************/
-
-#include "config.h"
-#include "qof.h"
-
-static QofLogModule log_module = QOF_MOD_MERGE;
-
-/* private rule iteration struct */
-struct qof_book_mergeRuleIterate {
-	qof_book_mergeRuleForeachCB   fcn;
-	qof_book_mergeData *data;
-	qof_book_mergeRule *rule;
-	GList *ruleList;
-	guint remainder;
-};
-
-/* Make string type parameters 3 times more
-	important in the match than default types.
-	i.e. even if two other parameters differ, 
-	a string match will still provide a better target
-	than when other types match and the string does not.
-*/
-#define DEFAULT_MERGE_WEIGHT    1
-#define QOF_STRING_WEIGHT       3
-#define QOF_DATE_STRING_LENGTH  MAX_DATE_LENGTH
-
-static qof_book_mergeRule*
-qof_book_mergeUpdateRule(qof_book_mergeRule *currentRule, gboolean match, gint weight)
-{
-	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 += weight;
-		if(currentRule->mergeResult == MERGE_DUPLICATE) {
-			currentRule->mergeResult = MERGE_REPORT;
-		}
-	}
-	return currentRule;
-}
-
-struct collect_list_s
-{
-	GSList *linkedEntList;
-};
-
-static void
-collect_reference_cb (QofEntity *ent, gpointer user_data)
-{
-	struct collect_list_s *s;
-
-	s = (struct collect_list_s*)user_data;
-	if(!ent || !s) { return; }
-	s->linkedEntList = g_slist_prepend(s->linkedEntList, ent);
-}
-
-static int 
-qof_book_mergeCompare( qof_book_mergeData *mergeData ) 
-{
-	qof_book_mergeRule *currentRule;
-	QofCollection *mergeColl, *targetColl;
-	gchar      *stringImport, *stringTarget;
-	QofEntity  *mergeEnt, *targetEnt, *referenceEnt;
-	const GUID *guidImport, *guidTarget;
-	QofParam   *qtparam;
-	KvpFrame   *kvpImport, *kvpTarget;
-	QofIdType  mergeParamName;
-	QofType    mergeType;
-	GSList    *paramList;
-	gboolean  absolute, mergeError, knowntype, mergeMatch, booleanImport, booleanTarget,
-                                                 (*boolean_getter) (QofEntity*, QofParam*);
-	Timespec      tsImport, tsTarget,            (*date_getter)    (QofEntity*, QofParam*);
-	gnc_numeric   numericImport, numericTarget,  (*numeric_getter) (QofEntity*, QofParam*);
-	double        doubleImport, doubleTarget,    (*double_getter)  (QofEntity*, QofParam*);
-	gint32        i32Import, i32Target,          (*int32_getter)   (QofEntity*, QofParam*);
-	gint64        i64Import, i64Target,          (*int64_getter)   (QofEntity*, QofParam*);
-	gchar         charImport, charTarget,        (*char_getter)    (QofEntity*, QofParam*);
-
-	g_return_val_if_fail((mergeData != NULL), -1);
-	currentRule = mergeData->currentRule;
-	g_return_val_if_fail((currentRule != NULL), -1);
-	absolute = currentRule->mergeAbsolute;
-	mergeEnt = currentRule->importEnt;
-	targetEnt = currentRule->targetEnt;
-	paramList = currentRule->mergeParam;
-	currentRule->difference = 0;
-	currentRule->mergeResult = MERGE_UNDEF;
-	currentRule->linkedEntList = NULL;
-	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; }
-			/* Give special weight to a string match */
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, QOF_STRING_WEIGHT);
-			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; }
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
-			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; }
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
-			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; }
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
-			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; }
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
-			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; }
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT); 
-			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; }
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT); 
-			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; }
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
-			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; }
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT); 
-			knowntype= TRUE;
-		}
-		if(safe_strcmp(mergeType, QOF_TYPE_CHAR) == 0) { 
-			char_getter = (gchar (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
-			charImport = char_getter(mergeEnt, qtparam);
-			charTarget = char_getter(targetEnt, qtparam);
-			if(charImport == charTarget) { mergeMatch = TRUE; }
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT); 
-			knowntype= TRUE;
-		}
-		/* No object should have QofSetterFunc defined for the book, but just to be safe, do nothing. */
-		if(safe_strcmp(mergeType, QOF_ID_BOOK) == 0) { knowntype= TRUE; }
-		if(safe_strcmp(mergeType, QOF_TYPE_COLLECT) == 0) {
-			struct collect_list_s s;
-			s.linkedEntList = NULL;
-			mergeColl = qtparam->param_getfcn(mergeEnt, qtparam);
-			targetColl = qtparam->param_getfcn(targetEnt, qtparam);
-			s.linkedEntList = g_slist_copy(currentRule->linkedEntList);
-			qof_collection_foreach(mergeColl, collect_reference_cb, &s);
-			currentRule->linkedEntList = g_slist_copy(s.linkedEntList);
-			if(0 == qof_collection_compare(mergeColl, targetColl)) { mergeMatch = TRUE; }
-			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
-			knowntype = TRUE;
-		}
-		if(safe_strcmp(mergeType, QOF_TYPE_CHOICE) ==0) {
-			referenceEnt = qtparam->param_getfcn(mergeEnt, qtparam);
-			currentRule->linkedEntList = g_slist_prepend(currentRule->linkedEntList, referenceEnt);
-			if(referenceEnt == qtparam->param_getfcn(targetEnt, qtparam)) { mergeMatch = TRUE; }
-			knowntype = TRUE;
-		}
-		if(knowntype == FALSE) {
-			referenceEnt = qtparam->param_getfcn(mergeEnt, qtparam);
-			if((referenceEnt != NULL)
-				&&(safe_strcmp(referenceEnt->e_type, mergeType) == 0)) {
-					currentRule->linkedEntList = g_slist_prepend(currentRule->linkedEntList, referenceEnt);
-					if(referenceEnt == qtparam->param_getfcn(targetEnt, qtparam)) { mergeMatch = TRUE; }
-					currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
-			}
-		}
-		paramList = g_slist_next(paramList);
-	}
-	mergeData->currentRule = currentRule;
-	g_free(kvpImport);
-	g_free(kvpTarget);
-	return 0;
-}
-
-static void 
-qof_book_mergeCommitForeachCB(gpointer rule, gpointer arg)
-{
-	struct qof_book_mergeRuleIterate *iter;
-
-	g_return_if_fail(arg != NULL);
-	iter = (struct qof_book_mergeRuleIterate*)arg;
-	g_return_if_fail(iter->data != NULL);
-	iter->fcn (iter->data, (qof_book_mergeRule*)rule, iter->remainder);
-	iter->remainder--;
-}
-
-static void
-qof_book_mergeCommitForeach (
-			qof_book_mergeRuleForeachCB cb, 
-			qof_book_mergeResult mergeResult,
-			qof_book_mergeData *mergeData)
-{
-	struct qof_book_mergeRuleIterate iter;
-	qof_book_mergeRule *currentRule;
-	GList *subList;
-
-	g_return_if_fail(cb != NULL);
-	g_return_if_fail(mergeData != NULL);
-	currentRule = mergeData->currentRule;
-	g_return_if_fail(currentRule != 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);
-	iter.data = mergeData;
-	g_list_foreach (subList, qof_book_mergeCommitForeachCB, &iter);
-}
-
-/* build the table of target comparisons
-
-This can get confusing, so bear with me. (!)
-
-Whilst iterating through the entities in the mergeBook, qof_book_mergeForeach assigns
-a targetEnt to each mergeEnt (until it runs out of targetEnt or mergeEnt). Each match
-is made against the one targetEnt that best matches the mergeEnt. Fine so far.
-
-Any mergeEnt is only ever assigned a targetEnt if the calculated difference between
-the two is less than the difference between that targetEnt and any previous mergeEnt
-match.
-
-The next mergeEnt may be a much better match for that targetEnt and the target_table
-is designed to solve the issues that result from this conflict. The previous match
-must be re-assigned because if two mergeEnt's are matched with only one targetEnt,
-data loss \b WILL follow. Equally, the current mergeEnt must replace the previous
-one as it is a better match. qof_entity_rating holds the details required to identify
-the correct mergeEnt to be re-assigned and these mergeEnt entities are therefore
-orphaned - to be re-matched later.
-
-Meanwhile, the current mergeEnt is entered into target_table with it's difference and
-rule data, in case an even better match is found later in the mergeBook.
-
-Finally, each mergeEnt in the orphan_list is now put through the comparison again.
-
-*/
-static gboolean
-qof_book_merge_rule_cmp(gconstpointer a, gconstpointer b)
-{
-	qof_book_mergeRule *ra = (qof_book_mergeRule *) a;
-	qof_book_mergeRule *rb = (qof_book_mergeRule *) b;
-	if (ra->difference == rb->difference) { return TRUE; }
-	else return FALSE;
-}
-
-static void
-qof_book_merge_orphan_check(double difference, qof_book_mergeRule *mergeRule, qof_book_mergeData *mergeData)
-{
-	/* Called when difference is lower than previous
-		Lookup target to find previous match
-		and re-assign mergeEnt to orphan_list */
-	qof_book_mergeRule *rule;
-
-	g_return_if_fail(mergeRule != NULL);
-	g_return_if_fail(mergeData != NULL);
-	if(g_hash_table_size(mergeData->target_table) == 0) { return; }
-	rule = (qof_book_mergeRule*)g_hash_table_lookup(mergeData->target_table, mergeRule->targetEnt);
-	/* If NULL, no match was found. */
-	if(rule == NULL) { return; }
-	/* Only orphan if this is a better match than already exists. */
-	if(difference >= rule->difference) { return; }
-	rule->targetEnt = NULL;
-	rule->mergeResult = MERGE_UNDEF;
-	mergeData->orphan_list = g_slist_append(mergeData->orphan_list, rule);
-}
-
-static void
-qof_book_merge_match_orphans(qof_book_mergeData *mergeData)
-{
-	GSList *orphans, *targets;
-	qof_book_mergeRule *rule, *currentRule;
-	QofEntity *best_matchEnt;
-	double difference;
-
-	g_return_if_fail(mergeData != NULL);
-	currentRule = mergeData->currentRule;
-	g_return_if_fail(currentRule != NULL);
-	/* This routine does NOT copy the orphan list, it
-		is used recursively until empty. */
-	orphans = mergeData->orphan_list;
-	targets = g_slist_copy(mergeData->targetList);
-	while(orphans != NULL) {
-		rule = orphans->data;
-		g_return_if_fail(rule != NULL);
-		difference = g_slist_length(mergeData->mergeObjectParams);
-		if(rule->targetEnt == NULL) {
-			rule->mergeResult = MERGE_NEW;
-			rule->difference = 0;
-			mergeData->mergeList = g_list_prepend(mergeData->mergeList,rule);
-			orphans = g_slist_next(orphans);
-			continue;
-		}
-		mergeData->currentRule = rule;
-		g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
-		if(difference > mergeData->currentRule->difference) {
-			best_matchEnt = currentRule->targetEnt;
-			difference = currentRule->difference;
-			rule = currentRule;
-			mergeData->mergeList = g_list_prepend(mergeData->mergeList,rule);
-			qof_book_merge_orphan_check(difference, rule, mergeData);
-		}
-		orphans = g_slist_next(orphans);
-	}
-	g_slist_free(mergeData->orphan_list);
-	g_slist_free(targets);
-}
-
-static void 
-qof_book_mergeForeachTarget (QofEntity* targetEnt, gpointer user_data)
-{
-	qof_book_mergeData *mergeData;
-
-	g_return_if_fail(user_data != NULL);
-	mergeData = (qof_book_mergeData*)user_data;
-	g_return_if_fail(targetEnt != NULL);
-	mergeData->targetList = g_slist_prepend(mergeData->targetList,targetEnt);
-}
-
-static void 
-qof_book_mergeForeachTypeTarget ( QofObject* merge_obj, gpointer user_data) 
-{
-	qof_book_mergeData *mergeData;
-	qof_book_mergeRule *currentRule;
-
-	g_return_if_fail(user_data != NULL);
-	mergeData = (qof_book_mergeData*)user_data;
-	currentRule = mergeData->currentRule;
-	g_return_if_fail(currentRule != NULL);
-	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, user_data);
-	}
-}
-
-static void 
-qof_book_mergeForeach ( QofEntity* mergeEnt, gpointer user_data) 
-{
-	qof_book_mergeRule *mergeRule, *currentRule;
-	qof_book_mergeData *mergeData;
-	QofEntity *targetEnt, *best_matchEnt;
-	GUID *g;
-	double difference;
-	GSList *c;
-
-	g_return_if_fail(user_data != NULL);
-	mergeData = (qof_book_mergeData*)user_data;
-	g_return_if_fail(mergeEnt != NULL);
-	currentRule = mergeData->currentRule;
-	g_return_if_fail(currentRule != 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;
-	mergeData->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(mergeData) != -1);
-		mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
-		mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
-		return;
-	}
-	/* no absolute match exists */
-	g_slist_free(mergeData->targetList);
-	mergeData->targetList = NULL;
-	qof_object_foreach_type(qof_book_mergeForeachTypeTarget, mergeData);
-	if(g_slist_length(mergeData->targetList) == 0) {
-		mergeRule->mergeResult = MERGE_NEW;
-	}
-	difference = g_slist_length(mergeRule->mergeParam);
-	c = g_slist_copy(mergeData->targetList);
-	while(c != NULL) {
-		mergeRule->targetEnt = c->data;
-		currentRule = mergeRule;
-		/* compare two entities and sum the differences */
-		g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
-		if(mergeRule->difference == 0) {
-			/* check if this is a better match than one already assigned */
-			best_matchEnt = mergeRule->targetEnt;
-			mergeRule->mergeResult = MERGE_DUPLICATE;
-			difference = 0;
-			mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
-			g_slist_free(c);
-			guid_free(g);
-			/* exact match, return */
-			return;
-		}
-		if(difference > mergeRule->difference) {
-			/* The chosen targetEnt determines the parenting of any child object */
-			/* check if this is a better match than one already assigned */
-			best_matchEnt = mergeRule->targetEnt;
-			difference = mergeRule->difference;
-			/* Use match to lookup the previous entity that matched this targetEnt (if any)
-				and remove targetEnt from the rule for that mergeEnt.
-				Add the previous mergeEnt to orphan_list.
-			*/			
-			qof_book_merge_orphan_check(difference, mergeRule, mergeData);
-		}
-		c = g_slist_next(c);
-	}
-	g_slist_free(c);
-	if(best_matchEnt != NULL ) {
-		mergeRule->targetEnt = best_matchEnt;
-		mergeRule->difference = difference;
-		/* Set this entity in the target_table in case a better match can be made
-			with the next mergeEnt. */
-		g_hash_table_insert(mergeData->target_table, mergeRule->targetEnt, mergeRule);
-		/* compare again with the best partial match */
-		g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
-		mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
-	}
-	else {
-		mergeRule->targetEnt = NULL;
-		mergeRule->difference = 0;
-		mergeRule->mergeResult = MERGE_NEW;
-		mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
-	}
-	mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
-	guid_free(g);
-	/* return to qof_book_mergeInit */
-}
-
-static void 
-qof_book_mergeForeachParam( QofParam* param, gpointer user_data) 
-{
-	qof_book_mergeData *mergeData;
-
-	g_return_if_fail(user_data != NULL);
-	mergeData = (qof_book_mergeData*)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);
-	}
-}
-
-static void 
-qof_book_mergeForeachType ( QofObject* merge_obj, gpointer user_data) 
-{
-	qof_book_mergeData *mergeData;
-
-	g_return_if_fail(user_data != NULL);
-	mergeData = (qof_book_mergeData*)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 , mergeData);
-	qof_object_foreach(merge_obj->e_type, mergeData->mergeBook, qof_book_mergeForeach, mergeData);
-}
-
-static void
-qof_book_mergeRuleCB(gpointer rule, gpointer arg)
-{
-	struct qof_book_mergeRuleIterate *iter;
-	qof_book_mergeData *mergeData;
-
-	g_return_if_fail(arg != NULL);
-	iter = (struct qof_book_mergeRuleIterate*)arg;
-	mergeData = iter->data;
-	g_return_if_fail(mergeData != NULL);
-	g_return_if_fail(mergeData->abort == FALSE);
-	iter->fcn (mergeData, (qof_book_mergeRule*)rule, iter->remainder);
-	iter->data = mergeData;
-	iter->remainder--;
-}
-
-static void 
-qof_book_mergeCommitRuleLoop(
-						qof_book_mergeData *mergeData,
-						qof_book_mergeRule *rule, 
-						guint remainder) 
-{ 
-	QofInstance *inst;
-	gboolean    registered_type;
-	QofEntity   *referenceEnt;
-	/* cm_ prefix used for variables that hold the data to commit */
-	QofCollection *cm_coll;
-	QofParam    *cm_param;
-	gchar       *cm_string;
-	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*);
-	gchar        cm_char,    (*char_getter)     (QofEntity*, QofParam*);
-	/* function pointers to the parameter setters */
-	void (*string_setter)    (QofEntity*, const gchar*);
-	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*, gchar);
-	void (*kvp_frame_setter) (QofEntity*, KvpFrame*);
-	void (*reference_setter) (QofEntity*, QofEntity*);
-	void (*collection_setter)(QofEntity*, QofCollection*);
-
-	g_return_if_fail(rule != NULL);
-	g_return_if_fail(mergeData != NULL);
-	g_return_if_fail(mergeData->targetBook != NULL);
-	g_return_if_fail((rule->mergeResult != MERGE_NEW)||(rule->mergeResult != MERGE_UPDATE));
-	/* create a new object for MERGE_NEW */
-	/* The new object takes the GUID from the import to retain an absolute match */
-	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;
-		qof_entity_set_guid(rule->targetEnt, qof_entity_get_guid(rule->importEnt));
-	}
-	/* currentRule->targetEnt is now set,
-		1. by an absolute GUID match or
-		2. by best_matchEnt and difference or
-		3. by MERGE_NEW.
-	*/
-	while(rule->mergeParam != NULL) {
-		registered_type = FALSE;
-		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 gchar*))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) { 
-			char_getter = (gchar (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
-			cm_char = char_getter(rule->importEnt,cm_param);
-			char_setter = (void(*)(QofEntity*, gchar))cm_param->param_setfcn;
-			if(char_setter != NULL) { char_setter(rule->targetEnt, cm_char); }
-			registered_type = TRUE;
-		}
-		if(safe_strcmp(rule->mergeType, QOF_TYPE_COLLECT) == 0) {
-			cm_coll = cm_param->param_getfcn(rule->importEnt, cm_param);
-			collection_setter = (void(*)(QofEntity*, QofCollection*))cm_param->param_setfcn;
-			if(collection_setter != NULL) { collection_setter(rule->targetEnt, cm_coll); }
-			registered_type = TRUE;
-		}
-		if(safe_strcmp(rule->mergeType, QOF_TYPE_CHOICE) == 0) {
-			referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param);
-			reference_setter = (void(*)(QofEntity*, QofEntity*))cm_param->param_setfcn;
-			if(reference_setter != NULL) 
-			{ 
-				reference_setter(rule->targetEnt, referenceEnt); 
-			}
-			registered_type = TRUE;
-		}
-		if(registered_type == FALSE) {
-			referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param);
-			if(referenceEnt) {
-				reference_setter = (void(*)(QofEntity*, QofEntity*))cm_param->param_setfcn;
-				if(reference_setter != NULL) 
-				{ 
-					reference_setter(rule->targetEnt, referenceEnt); 
-				}
-			}
-		}
-		rule->mergeParam = g_slist_next(rule->mergeParam);
-	}
-}
-/* ================================================================ */
-/* API functions. */
-
-qof_book_mergeData*
-qof_book_mergeInit( QofBook *importBook, QofBook *targetBook) 
-{
-	qof_book_mergeData *mergeData;
-	qof_book_mergeRule *currentRule;
-	GList *check;
-
-	g_return_val_if_fail((importBook != NULL)&&(targetBook != NULL), NULL);
-	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;
-	mergeData->orphan_list = NULL;
-	mergeData->target_table = g_hash_table_new( g_direct_hash, qof_book_merge_rule_cmp);
-	currentRule = g_new(qof_book_mergeRule, 1);
-	mergeData->currentRule = currentRule;
-	qof_object_foreach_type(qof_book_mergeForeachType, mergeData);
-	g_return_val_if_fail(mergeData->mergeObjectParams, NULL);
-	if(mergeData->orphan_list != NULL) {
-		qof_book_merge_match_orphans(mergeData);
-	}
-	
-	check = g_list_copy(mergeData->mergeList);
-	while(check != NULL) {
-		currentRule = check->data;
-		if(currentRule->mergeResult == MERGE_INVALID) {
-			mergeData->abort = TRUE;
-			return(NULL);
-		}
-		check = g_list_next(check);
-	}
-	g_list_free(check);
-	return mergeData;
-}
-
-void
-qof_book_merge_abort (qof_book_mergeData *mergeData)
-{
-	qof_book_mergeRule *currentRule;
-
-	g_return_if_fail(mergeData != NULL);
-	while(mergeData->mergeList != NULL) {
-		currentRule = mergeData->mergeList->data;
-		g_slist_free(currentRule->linkedEntList);
-		g_slist_free(currentRule->mergeParam);
-		g_free(mergeData->mergeList->data);
-		if(currentRule) {
-			g_slist_free(currentRule->linkedEntList);
-			g_slist_free(currentRule->mergeParam);
-			g_free(currentRule);
-		}
-		mergeData->mergeList = g_list_next(mergeData->mergeList);
-	}
-	g_list_free(mergeData->mergeList);
-	g_slist_free(mergeData->mergeObjectParams);
-	g_slist_free(mergeData->targetList);
-	if(mergeData->orphan_list != NULL) { g_slist_free(mergeData->orphan_list); }
-	g_hash_table_destroy(mergeData->target_table);
-	g_free(mergeData);
-}
-
-/* The QOF_TYPE_DATE output format from
-qof_book_merge_param_as_string has been changed to QSF_XSD_TIME,
-a UTC formatted timestring: 2005-01-01T10:55:23Z
-If you change QOF_UTC_DATE_FORMAT, change 
-backend/file/qsf-xml.c : qsf_entity_foreach to
-reformat to QSF_XSD_TIME or the QSF XML will
-FAIL the schema validation and QSF exports will become invalid.
-
-The QOF_TYPE_BOOLEAN is lowercase for the same reason.
-
-\todo deprecate and replace with
-gchar* qof_instance_param_as_string(const QofParam*, QofInstance*);
-and then add
-gchar* qof_class_get_param_as_string(QofIdTypeConst, QofInstance*); ?
-*/
-gchar*
-qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt)
-{
-	gchar       *param_string, param_date[QOF_DATE_STRING_LENGTH];
-	gchar       param_sa[GUID_ENCODING_LENGTH + 1];
-	QofType     paramType;
-	const GUID *param_guid;
-	time_t      param_t;
-	gnc_numeric param_numeric,  (*numeric_getter) (QofEntity*, QofParam*);
-	Timespec    param_ts,       (*date_getter)    (QofEntity*, QofParam*);
-	double      param_double,   (*double_getter)  (QofEntity*, QofParam*);
-	gboolean    param_boolean,  (*boolean_getter) (QofEntity*, QofParam*);
-	gint32      param_i32,      (*int32_getter)   (QofEntity*, QofParam*);
-	gint64      param_i64,      (*int64_getter)   (QofEntity*, QofParam*);
-	gchar       param_char,     (*char_getter)    (QofEntity*, QofParam*);
-
-	param_string = NULL;
-	paramType = qtparam->param_type;
-	if(safe_strcmp(paramType, QOF_TYPE_STRING) == 0)  { 
-			param_string = g_strdup(qtparam->param_getfcn(qtEnt,qtparam));
-			if(param_string == NULL) { param_string = ""; }
-			return param_string;
-		}
-		if(safe_strcmp(paramType, QOF_TYPE_DATE) == 0) { 
-			date_getter = (Timespec (*)(QofEntity*, QofParam*))qtparam->param_getfcn;
-			param_ts = date_getter(qtEnt, qtparam);
-			param_t = timespecToTime_t(param_ts);
-			strftime(param_date, QOF_DATE_STRING_LENGTH, QOF_UTC_DATE_FORMAT, gmtime(&param_t));
-			param_string = g_strdup(param_date);
-			return param_string;
-		}
-		if((safe_strcmp(paramType, QOF_TYPE_NUMERIC) == 0)  ||
-		(safe_strcmp(paramType, QOF_TYPE_DEBCRED) == 0)) { 
-			numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
-			param_numeric = numeric_getter(qtEnt,qtparam);
-			param_string = g_strdup(gnc_numeric_to_string(param_numeric));
-			return param_string;
-		}
-		if(safe_strcmp(paramType, QOF_TYPE_GUID) == 0) { 
-			param_guid = qtparam->param_getfcn(qtEnt,qtparam);
-			guid_to_string_buff(param_guid, param_sa);
-			param_string = g_strdup(param_sa);
-			return param_string;
-		}
-		if(safe_strcmp(paramType, QOF_TYPE_INT32) == 0) { 
-			int32_getter = (gint32 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
-			param_i32 = int32_getter(qtEnt, qtparam);
-			param_string = g_strdup_printf("%d", param_i32);
-			return param_string;
-		}
-		if(safe_strcmp(paramType, QOF_TYPE_INT64) == 0) { 
-			int64_getter = (gint64 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
-			param_i64 = int64_getter(qtEnt, qtparam);
-			param_string = g_strdup_printf("%" G_GINT64_FORMAT, param_i64);
-			return param_string;
-		}
-		if(safe_strcmp(paramType, QOF_TYPE_DOUBLE) == 0) { 
-			double_getter = (double (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
-			param_double = double_getter(qtEnt, qtparam);
-			param_string = g_strdup_printf("%f", param_double);
-			return param_string;
-		}
-		if(safe_strcmp(paramType, QOF_TYPE_BOOLEAN) == 0){ 
-			boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
-			param_boolean = boolean_getter(qtEnt, qtparam);
-			/* Boolean values need to be lowercase for QSF validation. */
-			if(param_boolean == TRUE) { param_string = g_strdup("true"); }
-			else { param_string = g_strdup("false"); }
-			return param_string;
-		}
-		/* "kvp" contains repeating values, cannot be a single string for the frame. */
-		if(safe_strcmp(paramType, QOF_TYPE_KVP) == 0) { return param_string; }
-		if(safe_strcmp(paramType, QOF_TYPE_CHAR) == 0) { 
-			char_getter = (gchar (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
-			param_char = char_getter(qtEnt, qtparam);
-			param_string = g_strdup_printf("%c", param_char);
-			return param_string;
-		}
-	return NULL;
-}
-
-qof_book_mergeData*
-qof_book_mergeUpdateResult(qof_book_mergeData *mergeData,
-						qof_book_mergeResult tag)
-{
-	qof_book_mergeRule *resolved;
-
-	g_return_val_if_fail((mergeData != NULL), NULL);
-	g_return_val_if_fail((tag > 0), NULL);
-	g_return_val_if_fail((tag != MERGE_REPORT), NULL);
-	resolved = mergeData->currentRule;
-	g_return_val_if_fail((resolved != NULL), NULL);
-	if((resolved->mergeAbsolute == TRUE)&&(tag == MERGE_DUPLICATE))
-	{ 
-		tag = MERGE_ABSOLUTE; 
-	}
-	if((resolved->mergeAbsolute == TRUE)&&(tag == MERGE_NEW))
-	{
-		tag = MERGE_UPDATE; 
-	}
-	if((resolved->mergeAbsolute == FALSE)&&	(tag == MERGE_ABSOLUTE))
-	{ 
-		tag = MERGE_DUPLICATE; 
-	}
-	if((resolved->mergeResult == MERGE_NEW)&&(tag == MERGE_UPDATE)) 
-	{ 
-		tag = MERGE_NEW; 
-	}
-	if(resolved->updated == FALSE) { resolved->mergeResult = tag; }
-	resolved->updated = TRUE;
-	if(tag >= MERGE_INVALID) { 
-		mergeData->abort = TRUE;
-		mergeData->currentRule = resolved;
-		return NULL; 
-	}
-	mergeData->currentRule = resolved;
-	return mergeData;
-}
-
-int
-qof_book_mergeCommit( qof_book_mergeData *mergeData )
-{
-	qof_book_mergeRule *currentRule;
-	GList *check;
-
-	g_return_val_if_fail(mergeData != NULL, -1);
-	g_return_val_if_fail(mergeData->mergeList != NULL, -1);
-	g_return_val_if_fail(mergeData->targetBook != NULL, -1);
-	if(mergeData->abort == TRUE) return -1;
-	check = g_list_copy(mergeData->mergeList);
-	g_return_val_if_fail(check != NULL, -1);
-	while(check != NULL) {
-		currentRule = check->data;
-		if(currentRule->mergeResult == MERGE_INVALID) {
-			qof_book_merge_abort(mergeData);
-			return(-2);
-		}
-		if(currentRule->mergeResult == MERGE_REPORT) {
-			g_list_free(check);
-			return 1;
-		}
-		check = g_list_next(check);
-	}
-	qof_book_mergeCommitForeach( qof_book_mergeCommitRuleLoop, MERGE_NEW, mergeData);
-	qof_book_mergeCommitForeach( qof_book_mergeCommitRuleLoop, MERGE_UPDATE, mergeData);
-	/* Placeholder for QofObject merge_helper_cb - all objects and all parameters set */
-	while(mergeData->mergeList != NULL) {
-		currentRule = mergeData->mergeList->data;
-		g_slist_free(currentRule->mergeParam);
-		g_slist_free(currentRule->linkedEntList);
-		mergeData->mergeList = g_list_next(mergeData->mergeList);
-	}
-	g_list_free(mergeData->mergeList);
-	g_slist_free(mergeData->mergeObjectParams);
-	g_slist_free(mergeData->targetList);
-	if(mergeData->orphan_list != NULL) { g_slist_free(mergeData->orphan_list); }
-	g_hash_table_destroy(mergeData->target_table);
-	g_free(mergeData);
-	return 0;
-}
-
-void 
-qof_book_mergeRuleForeach( qof_book_mergeData *mergeData, 
-								qof_book_mergeRuleForeachCB cb, 
-								qof_book_mergeResult mergeResult )
-{
-	struct qof_book_mergeRuleIterate iter;
-	qof_book_mergeRule *currentRule;
-	GList *matching_rules;
-
-	g_return_if_fail(cb != NULL);
-	g_return_if_fail(mergeData != NULL);
-	currentRule = mergeData->currentRule;
-	g_return_if_fail(mergeResult > 0);
-	g_return_if_fail(mergeResult != MERGE_INVALID);
-	g_return_if_fail(mergeData->abort == FALSE);
-	iter.fcn = cb;
-	iter.data = mergeData;
-	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_foreach (matching_rules, qof_book_mergeRuleCB, &iter);
-	g_list_free(matching_rules);
-}
-
-/* End of file. */
-/* ==================================================================== */

Deleted: gnucash/trunk/lib/libqof/qof/qof_book_merge.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof_book_merge.h	2006-03-09 16:54:44 UTC (rev 13566)
+++ gnucash/trunk/lib/libqof/qof/qof_book_merge.h	2006-03-09 17:03:01 UTC (rev 13567)
@@ -1,462 +0,0 @@
-/*********************************************************************
- * 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        *
- * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
- * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
- *                                                                   *
- ********************************************************************/
-
-#ifndef QOFBOOKMERGE_H
-#define QOFBOOKMERGE_H
-
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-#define QOF_MOD_MERGE "qof-merge"
-
-/** @addtogroup BookMerge
-
-<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_mergeUpdateResult and ::qof_book_mergeCommit return 
-any error values to the calling process. ::qof_book_mergeInit returns a
-pointer to the ::qof_book_mergeData struct - the calling process needs to
-make sure this is non-NULL to know that the Init has been successful.
-
-(to be renamed qofbookmerge.h in libqof2)
- @{
-*/
-/** @file  qof_book_merge.h
-    @brief API for merging two \c QofBook structures with collision handling
-    @author Copyright (c) 2004-2005 Neil Williams <linux at codehelp.co.uk>
-*/
-
-#include <glib.h>
-#include "gnc-engine-util.h"
-#include "qofbook.h"
-#include "qofclass.h"
-#include "qofobject.h"
-#include "qofinstance.h"
-#include "qoflog.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 One rule per entity, built into a single GList for the entire merge 
-
-All rules are stored in the GList 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
-
-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.
-
-The GHashTable targetTable in qof_book_mergeRule will probably replace the GSList of the
-same name in mergeData.
-
-*/
-
-typedef struct 
-{
-	/* internal counters and reference variables */
-	gboolean mergeAbsolute;   /**< Only set if the GUID of the import matches the target */
-	double 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 gchar* 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;
-
-
-/** \brief 	mergeData contains the essential context 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;          /**< GList of all ::qof_book_mergeRule rules for the merge operation. */
-	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_mergeRule *currentRule; /**< placeholder for the rule currently being tested or applied. */
-	GSList *orphan_list;         /**< List of QofEntity's that need to be rematched.
-
-	When one QofEntity has a lower difference to the targetEnt than the previous best_match,
-	the new match takes precedence. This list holds those orphaned entities that are not a good
-	enough match so that these can be rematched later. The ranking is handled using
-	the private qof_entity_rating struct and the GHashTable ::qof_book_mergeData::target_table.
-	*/
-	GHashTable *target_table;    /**< The GHashTable to hold the qof_entity_rating values.  */
-
-}qof_book_mergeData;
-
-
-/* ======================================================================== */
-/** @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 *). Returns a pointer to ::qof_book_mergeData which must be checked for a
-	NULL before continuing. \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 NULL in case of error, otherwise a ::qof_book_mergeData* metadata context.
-
-*/
-qof_book_mergeData*
-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_mergeData *mergeData, qof_book_mergeRule *rule, guint remainder);\n
-void test_rule_loop(qof_book_mergeData *mergeData, qof_book_mergeRule *rule, guint remainder) \n
-{\n
-	g_return_if_fail(rule != NULL);\n
-	g_return_if_fail(mergeData != NULL);
-	printf("Rule Result %s", rule->mergeType);\n
-	qof_book_mergeUpdateResult(mergeData, 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:
-	- data : pointer to the ::qof_book_mergeData metadata context returned by Init.
-	- 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.
-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_mergeData*, 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.
-
- at param	callback	external loop of type qof_book_mergeRuleForeachCB
- at param	mergeResult	::qof_book_mergeResult value to look up.
- at param	mergeData	::qof_book_mergeData merge context.
-
-\b Note : MERGE_NEW causes a new entity to be created in the target book at Commit
-which is then assigned as the targetEnt of that rule. If mergeResult == MERGE_NEW,
-the rules returned by qof_book_mergeRuleForeach will have a NULL set for the targetEnt.
-This is because Commit has not yet been called and no changes can be made to the target
-book. The calling process must handle the NULL targetEnt and NOT call any param_getfcn
-routines for the target entity. The import entity is available for display.
-
-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_mergeData* mergeData,
-                                qof_book_mergeRuleForeachCB callback , 
-                                qof_book_mergeResult mergeResult);
-
-/** \brief provides easy string access to parameter data for dialog use
-
-Uses the param_getfcn to retrieve the parameter value as a string, suitable for
-display in dialogs and user intervention output. Within a qof_book_merge context,
-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.
-
-*/
-gchar* 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 because no suitable target exists for the
-merge.
-
-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.
-
-The calling process must check the return value and call
-::qof_book_merge_abort(mergeData) if non-zero.
-
- at param	mergeData	the merge context, ::qof_book_mergeData*
- at param	tag			the result to attempt to set, ::qof_book_mergeResult
-
-\return -1 if supplied parameters are invalid or NULL, 0 on success.
-		
-*/
-qof_book_mergeData*
-qof_book_mergeUpdateResult(qof_book_mergeData *mergeData, 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. 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
-
- at param	mergeData	the merge context, ::qof_book_mergeData* 
-
-\return 
-	- -2 if any rules are tagged as ::MERGE_INVALID
-		- mergeData will have been g_free'd).
-		- note that this will be before any operations are done on the target
-			QofBook.
-	- -1 if mergeData is invalid or no merge has been initialised with
-		::qof_book_mergeInit - the calling process must check the value of mergeData
-	- +1 if some entities are still tagged as \a MERGE_REPORT - use 
-		::qof_book_mergeUpdateRule and try again (mergeData is retained).
-	- 0 on success - mergeData will have been freed.
-*/
-int
-qof_book_mergeCommit( qof_book_mergeData *mergeData );
-
-/** \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(qof_book_mergeData *mergeData);
-
-#endif // QOFBOOKMERGE_H
-/** @} */
-/** @} */

Copied: gnucash/trunk/lib/libqof/qof/qofbookmerge.c (from rev 13555, gnucash/trunk/lib/libqof/qof/qof_book_merge.c)
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof_book_merge.c	2006-03-09 13:03:29 UTC (rev 13555)
+++ gnucash/trunk/lib/libqof/qof/qofbookmerge.c	2006-03-09 17:03:01 UTC (rev 13567)
@@ -0,0 +1,1019 @@
+/*********************************************************************
+ * QofBookMerge.c -- api for QoFBook merge with collision handling   *
+ * Copyright (C) 2004-2005 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        *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+ *                                                                   *
+ ********************************************************************/
+
+#include "qof.h"
+
+static QofLogModule log_module = QOF_MOD_MERGE;
+
+/* private rule iteration struct */
+struct QofBookMergeRuleIterate {
+	QofBookMergeRuleForeachCB   fcn;
+	QofBookMergeData *data;
+	QofBookMergeRule *rule;
+	GList *ruleList;
+	guint remainder;
+};
+
+/* Make string type parameters 3 times more
+	important in the match than default types.
+	i.e. even if two other parameters differ, 
+	a string match will still provide a better target
+	than when other types match and the string does not.
+*/
+#define DEFAULT_MERGE_WEIGHT    1
+#define QOF_STRING_WEIGHT       3
+#define QOF_DATE_STRING_LENGTH  MAX_DATE_LENGTH
+
+static QofBookMergeRule*
+qof_book_merge_update_rule(QofBookMergeRule *currentRule, gboolean match, gint weight)
+{
+	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 += weight;
+		if(currentRule->mergeResult == MERGE_DUPLICATE) {
+			currentRule->mergeResult = MERGE_REPORT;
+		}
+	}
+	return currentRule;
+}
+
+struct collect_list_s
+{
+	GSList *linkedEntList;
+};
+
+static void
+collect_reference_cb (QofEntity *ent, gpointer user_data)
+{
+	struct collect_list_s *s;
+
+	s = (struct collect_list_s*)user_data;
+	if(!ent || !s) { return; }
+	s->linkedEntList = g_slist_prepend(s->linkedEntList, ent);
+}
+
+static int 
+qof_book_merge_compare(QofBookMergeData *mergeData ) 
+{
+	QofBookMergeRule *currentRule;
+	QofCollection *mergeColl, *targetColl;
+	gchar      *stringImport, *stringTarget;
+	QofEntity  *mergeEnt, *targetEnt, *referenceEnt;
+	const GUID *guidImport, *guidTarget;
+	QofParam   *qtparam;
+	KvpFrame   *kvpImport, *kvpTarget;
+	QofIdType  mergeParamName;
+	QofType    mergeType;
+	GSList    *paramList;
+	gboolean  absolute, mergeError, knowntype, mergeMatch, booleanImport, booleanTarget,
+                                                 (*boolean_getter) (QofEntity*, QofParam*);
+	Timespec      tsImport, tsTarget,            (*date_getter)    (QofEntity*, QofParam*);
+	gnc_numeric   numericImport, numericTarget,  (*numeric_getter) (QofEntity*, QofParam*);
+	double        doubleImport, doubleTarget,    (*double_getter)  (QofEntity*, QofParam*);
+	gint32        i32Import, i32Target,          (*int32_getter)   (QofEntity*, QofParam*);
+	gint64        i64Import, i64Target,          (*int64_getter)   (QofEntity*, QofParam*);
+	gchar         charImport, charTarget,        (*char_getter)    (QofEntity*, QofParam*);
+
+	g_return_val_if_fail((mergeData != NULL), -1);
+	currentRule = mergeData->currentRule;
+	g_return_val_if_fail((currentRule != NULL), -1);
+	absolute = currentRule->mergeAbsolute;
+	mergeEnt = currentRule->importEnt;
+	targetEnt = currentRule->targetEnt;
+	paramList = currentRule->mergeParam;
+	currentRule->difference = 0;
+	currentRule->mergeResult = MERGE_UNDEF;
+	currentRule->linkedEntList = NULL;
+	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; }
+			/* Give special weight to a string match */
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, QOF_STRING_WEIGHT);
+			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; }
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, DEFAULT_MERGE_WEIGHT);
+			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; }
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, DEFAULT_MERGE_WEIGHT);
+			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; }
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, DEFAULT_MERGE_WEIGHT);
+			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; }
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, DEFAULT_MERGE_WEIGHT);
+			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; }
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, DEFAULT_MERGE_WEIGHT); 
+			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; }
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, DEFAULT_MERGE_WEIGHT); 
+			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; }
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, DEFAULT_MERGE_WEIGHT);
+			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; }
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, DEFAULT_MERGE_WEIGHT); 
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_CHAR) == 0) { 
+			char_getter = (gchar (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			charImport = char_getter(mergeEnt, qtparam);
+			charTarget = char_getter(targetEnt, qtparam);
+			if(charImport == charTarget) { mergeMatch = TRUE; }
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, DEFAULT_MERGE_WEIGHT); 
+			knowntype= TRUE;
+		}
+		/* No object should have QofSetterFunc defined for the book,
+        but just to be safe, do nothing. */
+		if(safe_strcmp(mergeType, QOF_ID_BOOK) == 0) { knowntype= TRUE; }
+		if(safe_strcmp(mergeType, QOF_TYPE_COLLECT) == 0) {
+			struct collect_list_s s;
+			s.linkedEntList = NULL;
+			mergeColl = qtparam->param_getfcn(mergeEnt, qtparam);
+			targetColl = qtparam->param_getfcn(targetEnt, qtparam);
+			s.linkedEntList = g_slist_copy(currentRule->linkedEntList);
+			qof_collection_foreach(mergeColl, collect_reference_cb, &s);
+			currentRule->linkedEntList = g_slist_copy(s.linkedEntList);
+			if(0 == qof_collection_compare(mergeColl, targetColl)) 
+                { mergeMatch = TRUE; }
+			currentRule = qof_book_merge_update_rule(currentRule, 
+                mergeMatch, DEFAULT_MERGE_WEIGHT);
+			knowntype = TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_CHOICE) ==0) {
+			referenceEnt = qtparam->param_getfcn(mergeEnt, qtparam);
+			currentRule->linkedEntList = 
+                g_slist_prepend(currentRule->linkedEntList, referenceEnt);
+			if(referenceEnt == qtparam->param_getfcn(targetEnt, qtparam)) 
+                { mergeMatch = TRUE; }
+			knowntype = TRUE;
+		}
+		if(knowntype == FALSE) {
+			referenceEnt = qtparam->param_getfcn(mergeEnt, qtparam);
+			if((referenceEnt != NULL)
+				&&(safe_strcmp(referenceEnt->e_type, mergeType) == 0)) {
+					currentRule->linkedEntList = 
+                        g_slist_prepend(currentRule->linkedEntList, referenceEnt);
+					if(referenceEnt == qtparam->param_getfcn(targetEnt, qtparam)) 
+                        { mergeMatch = TRUE; }
+					currentRule = qof_book_merge_update_rule(currentRule, 
+                        mergeMatch, DEFAULT_MERGE_WEIGHT);
+			}
+		}
+		paramList = g_slist_next(paramList);
+	}
+	mergeData->currentRule = currentRule;
+	g_free(kvpImport);
+	g_free(kvpTarget);
+	return 0;
+}
+
+static void 
+qof_book_merge_commit_foreach_cb(gpointer rule, gpointer arg)
+{
+	struct QofBookMergeRuleIterate *iter;
+
+	g_return_if_fail(arg != NULL);
+	iter = (struct QofBookMergeRuleIterate*)arg;
+	g_return_if_fail(iter->data != NULL);
+	iter->fcn (iter->data, (QofBookMergeRule*)rule, iter->remainder);
+	iter->remainder--;
+}
+
+static void
+qof_book_merge_commit_foreach (
+			QofBookMergeRuleForeachCB cb, 
+			QofBookMergeResult mergeResult,
+			QofBookMergeData *mergeData)
+{
+	struct QofBookMergeRuleIterate iter;
+	QofBookMergeRule *currentRule;
+	GList *subList;
+
+	g_return_if_fail(cb != NULL);
+	g_return_if_fail(mergeData != NULL);
+	currentRule = mergeData->currentRule;
+	g_return_if_fail(currentRule != 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);
+	iter.data = mergeData;
+	g_list_foreach (subList, qof_book_merge_commit_foreach_cb, &iter);
+}
+
+/* build the table of target comparisons
+
+This can get confusing, so bear with me. (!)
+
+Whilst iterating through the entities in the mergeBook, qof_book_mergeForeach assigns
+a targetEnt to each mergeEnt (until it runs out of targetEnt or mergeEnt). Each match
+is made against the one targetEnt that best matches the mergeEnt. Fine so far.
+
+Any mergeEnt is only ever assigned a targetEnt if the calculated difference between
+the two is less than the difference between that targetEnt and any previous mergeEnt
+match.
+
+The next mergeEnt may be a much better match for that targetEnt and the target_table
+is designed to solve the issues that result from this conflict. The previous match
+must be re-assigned because if two mergeEnt's are matched with only one targetEnt,
+data loss \b WILL follow. Equally, the current mergeEnt must replace the previous
+one as it is a better match. qof_entity_rating holds the details required to identify
+the correct mergeEnt to be re-assigned and these mergeEnt entities are therefore
+orphaned - to be re-matched later.
+
+Meanwhile, the current mergeEnt is entered into target_table with it's difference and
+rule data, in case an even better match is found later in the mergeBook.
+
+Finally, each mergeEnt in the orphan_list is now put through the comparison again.
+
+*/
+static gboolean
+qof_book_merge_rule_cmp(gconstpointer a, gconstpointer b)
+{
+	QofBookMergeRule *ra = (QofBookMergeRule *) a;
+	QofBookMergeRule *rb = (QofBookMergeRule *) b;
+	if (ra->difference == rb->difference) { return TRUE; }
+	else return FALSE;
+}
+
+static void
+qof_book_merge_orphan_check(double difference, QofBookMergeRule *mergeRule, 
+    QofBookMergeData *mergeData)
+{
+	/* Called when difference is lower than previous
+		Lookup target to find previous match
+		and re-assign mergeEnt to orphan_list */
+	QofBookMergeRule *rule;
+
+	g_return_if_fail(mergeRule != NULL);
+	g_return_if_fail(mergeData != NULL);
+	if(g_hash_table_size(mergeData->target_table) == 0) { return; }
+	rule = (QofBookMergeRule*)g_hash_table_lookup(mergeData->target_table, 
+        mergeRule->targetEnt);
+	/* If NULL, no match was found. */
+	if(rule == NULL) { return; }
+	/* Only orphan if this is a better match than already exists. */
+	if(difference >= rule->difference) { return; }
+	rule->targetEnt = NULL;
+	rule->mergeResult = MERGE_UNDEF;
+	mergeData->orphan_list = g_slist_append(mergeData->orphan_list, rule);
+}
+
+static void
+qof_book_merge_match_orphans(QofBookMergeData *mergeData)
+{
+	GSList *orphans, *targets;
+	QofBookMergeRule *rule, *currentRule;
+	QofEntity *best_matchEnt;
+	double difference;
+
+	g_return_if_fail(mergeData != NULL);
+	currentRule = mergeData->currentRule;
+	g_return_if_fail(currentRule != NULL);
+	/* This routine does NOT copy the orphan list, it
+		is used recursively until empty. */
+	orphans = mergeData->orphan_list;
+	targets = g_slist_copy(mergeData->targetList);
+	while(orphans != NULL) {
+		rule = orphans->data;
+		g_return_if_fail(rule != NULL);
+		difference = g_slist_length(mergeData->mergeObjectParams);
+		if(rule->targetEnt == NULL) {
+			rule->mergeResult = MERGE_NEW;
+			rule->difference = 0;
+			mergeData->mergeList = g_list_prepend(mergeData->mergeList,rule);
+			orphans = g_slist_next(orphans);
+			continue;
+		}
+		mergeData->currentRule = rule;
+		g_return_if_fail(qof_book_merge_compare(mergeData) != -1);
+		if(difference > mergeData->currentRule->difference) {
+			best_matchEnt = currentRule->targetEnt;
+			difference = currentRule->difference;
+			rule = currentRule;
+			mergeData->mergeList = g_list_prepend(mergeData->mergeList,rule);
+			qof_book_merge_orphan_check(difference, rule, mergeData);
+		}
+		orphans = g_slist_next(orphans);
+	}
+	g_slist_free(mergeData->orphan_list);
+	g_slist_free(targets);
+}
+
+static void 
+qof_book_merge_foreach_target (QofEntity* targetEnt, gpointer user_data)
+{
+	QofBookMergeData *mergeData;
+
+	g_return_if_fail(user_data != NULL);
+	mergeData = (qof_book_mergeData*)user_data;
+	g_return_if_fail(targetEnt != NULL);
+	mergeData->targetList = g_slist_prepend(mergeData->targetList,targetEnt);
+}
+
+static void 
+qof_book_merge_foreach_type_target ( QofObject* merge_obj, gpointer user_data) 
+{
+	QofBookMergeData *mergeData;
+	QofBookMergeRule *currentRule;
+
+	g_return_if_fail(user_data != NULL);
+	mergeData = (QofBookMergeData*)user_data;
+	currentRule = mergeData->currentRule;
+	g_return_if_fail(currentRule != NULL);
+	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_merge_foreach_target, user_data);
+	}
+}
+
+static void 
+qof_book_merge_foreach ( QofEntity* mergeEnt, gpointer user_data) 
+{
+	QofBookMergeRule *mergeRule, *currentRule;
+	QofBookMergeData *mergeData;
+	QofEntity *targetEnt, *best_matchEnt;
+	GUID *g;
+	double difference;
+	GSList *c;
+
+	g_return_if_fail(user_data != NULL);
+	mergeData = (QofBookMergeData*)user_data;
+	g_return_if_fail(mergeEnt != NULL);
+	currentRule = mergeData->currentRule;
+	g_return_if_fail(currentRule != NULL);
+	g = guid_malloc();
+	*g = mergeEnt->guid;
+	mergeRule = g_new(QofBookMergeRule,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;
+	mergeData->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_merge_compare(mergeData) != -1);
+		mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+		mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
+		return;
+	}
+	/* no absolute match exists */
+	g_slist_free(mergeData->targetList);
+	mergeData->targetList = NULL;
+	qof_object_foreach_type(qof_book_merge_foreach_type_target, mergeData);
+	if(g_slist_length(mergeData->targetList) == 0) {
+		mergeRule->mergeResult = MERGE_NEW;
+	}
+	difference = g_slist_length(mergeRule->mergeParam);
+	c = g_slist_copy(mergeData->targetList);
+	while(c != NULL) {
+		mergeRule->targetEnt = c->data;
+		currentRule = mergeRule;
+		/* compare two entities and sum the differences */
+		g_return_if_fail(qof_book_merge_compare(mergeData) != -1);
+		if(mergeRule->difference == 0) {
+			/* check if this is a better match than one already assigned */
+			best_matchEnt = mergeRule->targetEnt;
+			mergeRule->mergeResult = MERGE_DUPLICATE;
+			difference = 0;
+			mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+			g_slist_free(c);
+			guid_free(g);
+			/* exact match, return */
+			return;
+		}
+		if(difference > mergeRule->difference) {
+			/* The chosen targetEnt determines the parenting of any child object */
+			/* check if this is a better match than one already assigned */
+			best_matchEnt = mergeRule->targetEnt;
+			difference = mergeRule->difference;
+			/* Use match to lookup the previous entity that matched this targetEnt (if any)
+				and remove targetEnt from the rule for that mergeEnt.
+				Add the previous mergeEnt to orphan_list.
+			*/			
+			qof_book_merge_orphan_check(difference, mergeRule, mergeData);
+		}
+		c = g_slist_next(c);
+	}
+	g_slist_free(c);
+	if(best_matchEnt != NULL ) {
+		mergeRule->targetEnt = best_matchEnt;
+		mergeRule->difference = difference;
+		/* Set this entity in the target_table in case a better match can be made
+			with the next mergeEnt. */
+		g_hash_table_insert(mergeData->target_table, mergeRule->targetEnt, mergeRule);
+		/* compare again with the best partial match */
+		g_return_if_fail(qof_book_merge_compare(mergeData) != -1);
+		mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+	}
+	else {
+		mergeRule->targetEnt = NULL;
+		mergeRule->difference = 0;
+		mergeRule->mergeResult = MERGE_NEW;
+		mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+	}
+	mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
+	guid_free(g);
+	/* return to qof_book_merge_init */
+}
+
+static void 
+qof_book_merge_foreach_param( QofParam* param, gpointer user_data) 
+{
+	QofBookMergeData *mergeData;
+
+	g_return_if_fail(user_data != NULL);
+	mergeData = (QofBookMergeData*)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);
+	}
+}
+
+static void 
+qof_book_merge_foreach_type ( QofObject* merge_obj, gpointer user_data) 
+{
+	QofBookMergeData *mergeData;
+
+	g_return_if_fail(user_data != NULL);
+	mergeData = (QofBookMergeData*)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_merge_foreach_param , mergeData);
+	qof_object_foreach(merge_obj->e_type, mergeData->mergeBook, 
+            qof_book_merge_foreach, mergeData);
+}
+
+static void
+qof_book_merge_rule_cb(gpointer rule, gpointer arg)
+{
+	struct QofBookMergeRuleIterate *iter;
+	QofBookMergeData *mergeData;
+
+	g_return_if_fail(arg != NULL);
+	iter = (struct QofBookMergeRuleIterate*)arg;
+	mergeData = iter->data;
+	g_return_if_fail(mergeData != NULL);
+	g_return_if_fail(mergeData->abort == FALSE);
+	iter->fcn (mergeData, (QofBookMergeRule*)rule, iter->remainder);
+	iter->data = mergeData;
+	iter->remainder--;
+}
+
+static void 
+qof_book_merge_commit_rule_loop(
+						QofBookMergeData *mergeData,
+						QofBookMergeRule *rule, 
+						guint remainder) 
+{ 
+	QofInstance *inst;
+	gboolean    registered_type;
+	QofEntity   *referenceEnt;
+	/* cm_ prefix used for variables that hold the data to commit */
+	QofCollection *cm_coll;
+	QofParam    *cm_param;
+	gchar       *cm_string;
+	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*);
+	gchar        cm_char,    (*char_getter)     (QofEntity*, QofParam*);
+	/* function pointers to the parameter setters */
+	void (*string_setter)    (QofEntity*, const gchar*);
+	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*, gchar);
+	void (*kvp_frame_setter) (QofEntity*, KvpFrame*);
+	void (*reference_setter) (QofEntity*, QofEntity*);
+	void (*collection_setter)(QofEntity*, QofCollection*);
+
+	g_return_if_fail(rule != NULL);
+	g_return_if_fail(mergeData != NULL);
+	g_return_if_fail(mergeData->targetBook != NULL);
+	g_return_if_fail((rule->mergeResult != MERGE_NEW)||(rule->mergeResult != MERGE_UPDATE));
+	/* create a new object for MERGE_NEW */
+	/* The new object takes the GUID from the import to retain an absolute match */
+	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;
+		qof_entity_set_guid(rule->targetEnt, qof_entity_get_guid(rule->importEnt));
+	}
+	/* currentRule->targetEnt is now set,
+		1. by an absolute GUID match or
+		2. by best_matchEnt and difference or
+		3. by MERGE_NEW.
+	*/
+	while(rule->mergeParam != NULL) {
+		registered_type = FALSE;
+		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 gchar*))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) { 
+			char_getter = (gchar (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+			cm_char = char_getter(rule->importEnt,cm_param);
+			char_setter = (void(*)(QofEntity*, gchar))cm_param->param_setfcn;
+			if(char_setter != NULL) { char_setter(rule->targetEnt, cm_char); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_COLLECT) == 0) {
+			cm_coll = cm_param->param_getfcn(rule->importEnt, cm_param);
+			collection_setter = (void(*)(QofEntity*, QofCollection*))cm_param->param_setfcn;
+			if(collection_setter != NULL) { collection_setter(rule->targetEnt, cm_coll); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_CHOICE) == 0) {
+			referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param);
+			reference_setter = (void(*)(QofEntity*, QofEntity*))cm_param->param_setfcn;
+			if(reference_setter != NULL) 
+			{ 
+				reference_setter(rule->targetEnt, referenceEnt); 
+			}
+			registered_type = TRUE;
+		}
+		if(registered_type == FALSE) {
+			referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param);
+			if(referenceEnt) {
+				reference_setter = (void(*)(QofEntity*, QofEntity*))cm_param->param_setfcn;
+				if(reference_setter != NULL) 
+				{ 
+					reference_setter(rule->targetEnt, referenceEnt); 
+				}
+			}
+		}
+		rule->mergeParam = g_slist_next(rule->mergeParam);
+	}
+}
+/* ================================================================ */
+/* API functions. */
+
+QofBookMergeData*
+qof_book_merge_init( QofBook *importBook, QofBook *targetBook) 
+{
+	QofBookMergeData *mergeData;
+	QofBookMergeRule *currentRule;
+	GList *check;
+
+	g_return_val_if_fail((importBook != NULL)&&(targetBook != NULL), NULL);
+	mergeData = g_new(QofBookMergeData, 1);
+	mergeData->abort = FALSE;
+	mergeData->mergeList = NULL;
+	mergeData->targetList = NULL;
+	mergeData->mergeBook = importBook;
+	mergeData->targetBook = targetBook;
+	mergeData->mergeObjectParams = NULL;
+	mergeData->orphan_list = NULL;
+	mergeData->target_table = g_hash_table_new( g_direct_hash, qof_book_merge_rule_cmp);
+	currentRule = g_new(QofBookMergeRule, 1);
+	mergeData->currentRule = currentRule;
+	qof_object_foreach_type(qof_book_merge_foreach_type, mergeData);
+	g_return_val_if_fail(mergeData->mergeObjectParams, NULL);
+	if(mergeData->orphan_list != NULL) {
+		qof_book_merge_match_orphans(mergeData);
+	}
+	
+	check = g_list_copy(mergeData->mergeList);
+	while(check != NULL) {
+		currentRule = check->data;
+		if(currentRule->mergeResult == MERGE_INVALID) {
+			mergeData->abort = TRUE;
+			return(NULL);
+		}
+		check = g_list_next(check);
+	}
+	g_list_free(check);
+	return mergeData;
+}
+
+void
+qof_book_merge_abort (QofBookMergeData *mergeData)
+{
+	QofBookMergeRule *currentRule;
+
+	g_return_if_fail(mergeData != NULL);
+	while(mergeData->mergeList != NULL) {
+		currentRule = mergeData->mergeList->data;
+		g_slist_free(currentRule->linkedEntList);
+		g_slist_free(currentRule->mergeParam);
+		g_free(mergeData->mergeList->data);
+		if(currentRule) {
+			g_slist_free(currentRule->linkedEntList);
+			g_slist_free(currentRule->mergeParam);
+			g_free(currentRule);
+		}
+		mergeData->mergeList = g_list_next(mergeData->mergeList);
+	}
+	g_list_free(mergeData->mergeList);
+	g_slist_free(mergeData->mergeObjectParams);
+	g_slist_free(mergeData->targetList);
+	if(mergeData->orphan_list != NULL) { g_slist_free(mergeData->orphan_list); }
+	g_hash_table_destroy(mergeData->target_table);
+	g_free(mergeData);
+}
+
+/* The QOF_TYPE_DATE output format from
+qof_book_merge_param_as_string has been changed to QSF_XSD_TIME,
+a UTC formatted timestring: 2005-01-01T10:55:23Z
+If you change QOF_UTC_DATE_FORMAT, change 
+backend/file/qsf-xml.c : qsf_entity_foreach to
+reformat to QSF_XSD_TIME or the QSF XML will
+FAIL the schema validation and QSF exports will become invalid.
+
+The QOF_TYPE_BOOLEAN is lowercase for the same reason.
+
+\todo deprecate and replace with
+gchar* qof_instance_param_as_string(const QofParam*, QofInstance*);
+and then add
+gchar* qof_class_get_param_as_string(QofIdTypeConst, QofInstance*); ?
+*/
+gchar*
+qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt)
+{
+	gchar       *param_string, param_date[QOF_DATE_STRING_LENGTH];
+	gchar       param_sa[GUID_ENCODING_LENGTH + 1];
+	QofType     paramType;
+	const GUID *param_guid;
+	time_t      param_t;
+	gnc_numeric param_numeric,  (*numeric_getter) (QofEntity*, QofParam*);
+	Timespec    param_ts,       (*date_getter)    (QofEntity*, QofParam*);
+	double      param_double,   (*double_getter)  (QofEntity*, QofParam*);
+	gboolean    param_boolean,  (*boolean_getter) (QofEntity*, QofParam*);
+	gint32      param_i32,      (*int32_getter)   (QofEntity*, QofParam*);
+	gint64      param_i64,      (*int64_getter)   (QofEntity*, QofParam*);
+	gchar       param_char,     (*char_getter)    (QofEntity*, QofParam*);
+
+	param_string = NULL;
+	paramType = qtparam->param_type;
+	if(safe_strcmp(paramType, QOF_TYPE_STRING) == 0)  { 
+			param_string = g_strdup(qtparam->param_getfcn(qtEnt,qtparam));
+			if(param_string == NULL) { param_string = ""; }
+			return param_string;
+		}
+		if(safe_strcmp(paramType, QOF_TYPE_DATE) == 0) { 
+			date_getter = (Timespec (*)(QofEntity*, QofParam*))qtparam->param_getfcn;
+			param_ts = date_getter(qtEnt, qtparam);
+			param_t = timespecToTime_t(param_ts);
+			strftime(param_date, QOF_DATE_STRING_LENGTH, QOF_UTC_DATE_FORMAT, gmtime(&param_t));
+			param_string = g_strdup(param_date);
+			return param_string;
+		}
+		if((safe_strcmp(paramType, QOF_TYPE_NUMERIC) == 0)  ||
+		(safe_strcmp(paramType, QOF_TYPE_DEBCRED) == 0)) { 
+			numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			param_numeric = numeric_getter(qtEnt,qtparam);
+			param_string = g_strdup(gnc_numeric_to_string(param_numeric));
+			return param_string;
+		}
+		if(safe_strcmp(paramType, QOF_TYPE_GUID) == 0) { 
+			param_guid = qtparam->param_getfcn(qtEnt,qtparam);
+			guid_to_string_buff(param_guid, param_sa);
+			param_string = g_strdup(param_sa);
+			return param_string;
+		}
+		if(safe_strcmp(paramType, QOF_TYPE_INT32) == 0) { 
+			int32_getter = (gint32 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			param_i32 = int32_getter(qtEnt, qtparam);
+			param_string = g_strdup_printf("%d", param_i32);
+			return param_string;
+		}
+		if(safe_strcmp(paramType, QOF_TYPE_INT64) == 0) { 
+			int64_getter = (gint64 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			param_i64 = int64_getter(qtEnt, qtparam);
+			param_string = g_strdup_printf("%" G_GINT64_FORMAT, param_i64);
+			return param_string;
+		}
+		if(safe_strcmp(paramType, QOF_TYPE_DOUBLE) == 0) { 
+			double_getter = (double (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			param_double = double_getter(qtEnt, qtparam);
+			param_string = g_strdup_printf("%f", param_double);
+			return param_string;
+		}
+		if(safe_strcmp(paramType, QOF_TYPE_BOOLEAN) == 0){ 
+			boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			param_boolean = boolean_getter(qtEnt, qtparam);
+			/* Boolean values need to be lowercase for QSF validation. */
+			if(param_boolean == TRUE) { param_string = g_strdup("true"); }
+			else { param_string = g_strdup("false"); }
+			return param_string;
+		}
+		/* "kvp" contains repeating values, cannot be a single string for the frame. */
+		if(safe_strcmp(paramType, QOF_TYPE_KVP) == 0) { return param_string; }
+		if(safe_strcmp(paramType, QOF_TYPE_CHAR) == 0) { 
+			char_getter = (gchar (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			param_char = char_getter(qtEnt, qtparam);
+			param_string = g_strdup_printf("%c", param_char);
+			return param_string;
+		}
+	return NULL;
+}
+
+QofBookMergeData*
+qof_book_merge_update_result(QofBookMergeData *mergeData,
+						QofBookMergeResult tag)
+{
+	QofBookMergeRule *resolved;
+
+	g_return_val_if_fail((mergeData != NULL), NULL);
+	g_return_val_if_fail((tag > 0), NULL);
+	g_return_val_if_fail((tag != MERGE_REPORT), NULL);
+	resolved = mergeData->currentRule;
+	g_return_val_if_fail((resolved != NULL), NULL);
+	if((resolved->mergeAbsolute == TRUE)&&(tag == MERGE_DUPLICATE))
+	{ 
+		tag = MERGE_ABSOLUTE; 
+	}
+	if((resolved->mergeAbsolute == TRUE)&&(tag == MERGE_NEW))
+	{
+		tag = MERGE_UPDATE; 
+	}
+	if((resolved->mergeAbsolute == FALSE)&&	(tag == MERGE_ABSOLUTE))
+	{ 
+		tag = MERGE_DUPLICATE; 
+	}
+	if((resolved->mergeResult == MERGE_NEW)&&(tag == MERGE_UPDATE)) 
+	{ 
+		tag = MERGE_NEW; 
+	}
+	if(resolved->updated == FALSE) { resolved->mergeResult = tag; }
+	resolved->updated = TRUE;
+	if(tag >= MERGE_INVALID) { 
+		mergeData->abort = TRUE;
+		mergeData->currentRule = resolved;
+		return NULL; 
+	}
+	mergeData->currentRule = resolved;
+	return mergeData;
+}
+
+gint
+qof_book_merge_commit(QofBookMergeData *mergeData )
+{
+	QofBookMergeRule *currentRule;
+	GList *check;
+
+	g_return_val_if_fail(mergeData != NULL, -1);
+	g_return_val_if_fail(mergeData->mergeList != NULL, -1);
+	g_return_val_if_fail(mergeData->targetBook != NULL, -1);
+	if(mergeData->abort == TRUE) return -1;
+	check = g_list_copy(mergeData->mergeList);
+	g_return_val_if_fail(check != NULL, -1);
+	while(check != NULL) {
+		currentRule = check->data;
+		if(currentRule->mergeResult == MERGE_INVALID) {
+			qof_book_merge_abort(mergeData);
+			return(-2);
+		}
+		if(currentRule->mergeResult == MERGE_REPORT) {
+			g_list_free(check);
+			return 1;
+		}
+		check = g_list_next(check);
+	}
+	qof_book_merge_commit_foreach(qof_book_merge_commit_rule_loop, 
+        MERGE_NEW, mergeData);
+	qof_book_merge_commit_foreach(qof_book_merge_commit_rule_loop, 
+        MERGE_UPDATE, mergeData);
+	/* Placeholder for QofObject merge_helper_cb - all objects
+        and all parameters set */
+	while(mergeData->mergeList != NULL) {
+		currentRule = mergeData->mergeList->data;
+		g_slist_free(currentRule->mergeParam);
+		g_slist_free(currentRule->linkedEntList);
+		mergeData->mergeList = g_list_next(mergeData->mergeList);
+	}
+	g_list_free(mergeData->mergeList);
+	g_slist_free(mergeData->mergeObjectParams);
+	g_slist_free(mergeData->targetList);
+	if(mergeData->orphan_list != NULL) { g_slist_free(mergeData->orphan_list); }
+	g_hash_table_destroy(mergeData->target_table);
+	g_free(mergeData);
+	return 0;
+}
+
+void 
+qof_book_merge_rule_foreach(QofBookMergeData *mergeData, 
+							QofBookMergeRuleForeachCB cb, 
+							QofBookMergeResult mergeResult )
+{
+	struct QofBookMergeRuleIterate iter;
+	QofBookMergeRule *currentRule;
+	GList *matching_rules;
+
+	g_return_if_fail(cb != NULL);
+	g_return_if_fail(mergeData != NULL);
+	currentRule = mergeData->currentRule;
+	g_return_if_fail(mergeResult > 0);
+	g_return_if_fail(mergeResult != MERGE_INVALID);
+	g_return_if_fail(mergeData->abort == FALSE);
+	iter.fcn = cb;
+	iter.data = mergeData;
+	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_foreach (matching_rules, qof_book_merge_rule_cb, &iter);
+	g_list_free(matching_rules);
+}
+
+/* End of file. */
+/* ==================================================================== */

Copied: gnucash/trunk/lib/libqof/qof/qofbookmerge.h (from rev 13555, gnucash/trunk/lib/libqof/qof/qof_book_merge.h)
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof_book_merge.h	2006-03-09 13:03:29 UTC (rev 13555)
+++ gnucash/trunk/lib/libqof/qof/qofbookmerge.h	2006-03-09 17:03:01 UTC (rev 13567)
@@ -0,0 +1,498 @@
+/*********************************************************************
+ * qofbookmerge.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        *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+ *                                                                   *
+ ********************************************************************/
+
+#ifndef QOFBOOKMERGE_H
+#define QOFBOOKMERGE_H
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#define QOF_MOD_MERGE "qof-merge"
+
+/** @addtogroup BookMerge
+
+<b>Collision handling principles.</b>\n
+\n
+	-# Always check for a ::GUID first and compare. qofbookmerge 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_merge_rule::mergeAbsolute to \a TRUE.
+	-# If ALL parameters in the import object match the target object with
+    the same \a GUID, 
+	set ::qof_book_merge_result 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_merge_update_result and ::qof_book_merge_commit return 
+any error values to the calling process. ::qof_book_merge_init returns a
+pointer to the ::QofBookMergeData struct - the calling process needs to
+make sure this is non-NULL to know that the Init has been successful.
+
+ @{
+*/
+/** @file  qofbookmerge.h
+    @brief API for merging two \c QofBook structures with collision handling
+    @author Copyright (c) 2004-2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#include <glib.h>
+#include "gnc-engine-util.h"
+#include "qofbook.h"
+#include "qofclass.h"
+#include "qofobject.h"
+#include "qofinstance.h"
+#include "qoflog.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 qofbookmerge.
+
+\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_merge_rule_foreach and call ::qof_book_merge_commit.
+
+\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 dialogue/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 */
+}QofBookMergeResult;
+
+/** \brief One rule per entity, built into a single GList for the entire merge 
+
+All rules are stored in the GList QofBookMergeData::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
+
+NOTE 1: if mergeAbsolute == \c TRUE, ::QofBookMergeResult 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 dialogue.
+
+The GHashTable targetTable in QofBookMergeRule will probably replace the
+GSList of the
+same name in mergeData.
+
+*/
+
+typedef struct 
+{
+	/* internal counters and reference variables */
+	gboolean mergeAbsolute;   /**< Only set if the GUID of the import matches
+                                the target */
+	double 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 gchar* mergeLabel;  /**< Descriptive label for the object type,
+                                useful for the user intervention dialogue. */
+	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.
+	*/
+	QofBookMergeResult 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. */
+}QofBookMergeRule;
+
+
+/** \brief 	mergeData contains the essential context 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 ::QofBookMergeRule
+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;          /**< GList of all ::qof_book_mergeRule rules
+                                    for the merge operation. */
+	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. */
+	QofBookMergeRule *currentRule; /**< placeholder for the rule currently
+                                    being tested or applied. */
+	GSList *orphan_list;         /**< List of QofEntity's that need to be rematched.
+
+	When one QofEntity has a lower difference to the targetEnt than the
+    previous best_match, the new match takes precedence. This list holds those
+    orphaned entities that are not a good enough match so that these can be
+    rematched later. The ranking is handled using the private QofEntityRating
+    struct and the GHashTable ::QofBookMergeData::target_table.
+	*/
+	GHashTable *target_table;    /**< The GHashTable to hold the
+                                    QofEntityRating values.  */
+
+}QofBookMergeData;
+
+
+/* ======================================================================== */
+/** @name qof_book_merge API
+ @{
+*/
+/** \brief Initialise the QofBookMerge process
+
+	First function of the QofBookMerge API. Every merge must begin with init.
+
+	Requires the book to import (::QofBook *) and the book to receive the
+    import, the target book	(::QofBook *). Returns a pointer to
+    ::QofBookMergeData which must be checked for a NULL before continuing. \n
+Process:
+
+ 	-# Invoke the callback ::qof_book_merge_foreach_type 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 QofBookMerge - no registered object or
+    parameter is omitted from any merge operation.
+	-# Use ::qof_object_foreach to invoke the callback ::qof_book_merge_foreach,
+    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_merge_foreach 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_merge_compare sets the ::QofBookMergeResult of the comparison.
+	-# Inserts the completed rule into QofBookMergeData::mergeList GSList.
+
+\return NULL in case of error, otherwise a ::QofBookMergeData* metadata context.
+
+*/
+QofBookMergeData*
+qof_book_merge_init( QofBook *importBook, QofBook *targetBook);
+
+
+/** \brief Definition of the dialogue 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(QofBookMergeData *mergeData, QofBookMergeRule *rule, guint remainder);\n
+void test_rule_loop(QofBookMergeData *mergeData, QofBookMergeRule *rule, guint remainder) \n
+{\n
+	g_return_if_fail(rule != NULL);\n
+	g_return_if_fail(mergeData != NULL);
+	printf("Rule Result %s", rule->mergeType);\n
+	qof_book_merge_update_result(mergeData, rule, MERGE_UPDATE);\n
+}</tt>
+
+The dialogue is free to call ::qof_book_merge_update_result 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:
+	- data : pointer to the ::QofBookMergeData metadata context returned by init.
+	- rule : pointer to the ::QofBookMergeRule 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 dialogue, it might not. When updating MERGE_REPORT,
+        remainder must equal zero before calling ::qof_book_merge_commit or
+        the import will abort.
+\n
+
+If the dialogue sets \b any rule result to ::MERGE_INVALID, the import will
+abort when ::qof_book_merge_commit is called. It is the responsibility of
+the calling function to handle the error code from ::qof_book_merge_commit,
+close the dialogue and return. 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 dialogue
+process to report back to QofBookMerge in this situation.
+*/
+typedef void (* QofBookMergeRuleForeachCB)( QofBookMergeData*, QofBookMergeRule*, guint);
+
+/** \brief Dialogue Control Callback
+
+This function is designed to be used to iterate over all rules tagged with a
+specific ::QofBookMergeResult value.
+
+ at param	callback	external loop of type QofBookMergeRuleForeachCB
+ at param	mergeResult	::QofBookMergeResult value to look up.
+ at param	mergeData	::QofBookMergeData merge context.
+
+\b Note : MERGE_NEW causes a new entity to be created in the target book at
+commit which is then assigned as the targetEnt of that rule. If
+mergeResult == MERGE_NEW, the rules returned by qof_book_merge_rule_foreach
+will have a NULL set for the targetEnt. This is because commit has not yet
+been called and no changes can be made to the target book. The calling
+process must handle the NULL targetEnt and NOT call any param_getfcn
+routines for the target entity. The import entity is available for display.
+
+Uses ::qof_book_get_collection with the QofBookMergeRule::mergeType object
+type to return a collection of ::QofEntity entities from either the
+QofBookMergeData::mergeBook or QofBookMergeData::targetBook. Then
+uses ::qof_collection_lookup_entity to lookup the QofBookMergeRule::importEnt
+and again the qof_book_mergeRule::targetEnt to return the two specific entities.
+
+*/
+void qof_book_merge_rule_foreach( QofBookMergeData* mergeData,
+                                QofBookMergeRuleForeachCB callback , 
+                                QofBookMergeResult mergeResult);
+
+/** \brief provides easy string access to parameter data for dialogue use
+
+Uses the param_getfcn to retrieve the parameter value as a string, suitable for
+display in dialogues and user intervention output. Within a QofBookMerge context,
+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 dialogue as QofBookMergeRule::mergeLabel.
+
+This allows the dialog to display the description of the object and all
+parameter data.
+
+*/
+gchar* qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt);
+
+/** \brief called by dialogue callback to set the result of user intervention
+
+Set \b any rule result to ::MERGE_INVALID to abort the import when
+::qof_book_merge_commit 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 because no suitable target exists for the
+merge.
+
+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 - QofBookMergeRule::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_merge_commit only commits entities tagged 
+with MERGE_NEW and MERGE_UPDATE results.
+\n
+Entities tagged with MERGE_ABSOLUTE and MERGE_DUPLICATE results are ignored.
+
+The calling process must check the return value and call
+::qof_book_merge_abort(mergeData) if non-zero.
+
+ at param	mergeData	the merge context, ::QofBookMergeData*
+ at param	tag			the result to attempt to set, ::QofBookMergeResult
+
+\return -1 if supplied parameters are invalid or NULL, 0 on success.
+		
+*/
+QofBookMergeData*
+qof_book_merge_update_result(QofBookMergeData *mergeData, QofBookMergeResult tag);
+
+/** \brief Commits the import data to the target book
+
+	The last function in the API and the final part of any QofBookMerge operation.
+
+qof_book_merge_commit 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
+dialogue and return. qof_book_merge_commit 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 dialogue process to report
+back to qof_book_merge in this situation.
+
+qof_book_merge_commit 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
+
+ at param	mergeData	the merge context, ::QofBookMergeData* 
+
+\return 
+	- -2 if any rules are tagged as ::MERGE_INVALID
+		- mergeData will have been g_free'd).
+		- note that this will be before any operations are done on the target
+			QofBook.
+	- -1 if mergeData is invalid or no merge has been initialised with
+		::qof_book_merge_init - the calling process must check the value of
+        mergeData
+	- +1 if some entities are still tagged as \a MERGE_REPORT - use 
+		::qof_book_merge_update_rule and try again (mergeData is retained).
+	- 0 on success - mergeData will have been freed.
+*/
+gint
+qof_book_merge_commit(QofBookMergeData *mergeData );
+
+/** \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(QofBookMergeData *mergeData);
+
+#endif // QOFBOOKMERGE_H
+/** @} */
+/** @} */

Modified: gnucash/trunk/src/engine/test/test-book-merge.c
===================================================================
--- gnucash/trunk/src/engine/test/test-book-merge.c	2006-03-09 16:54:44 UTC (rev 13566)
+++ gnucash/trunk/src/engine/test/test-book-merge.c	2006-03-09 17:03:01 UTC (rev 13567)
@@ -41,7 +41,7 @@
 #define OBJ_MINOR "tiny"
 #define OBJ_ACTIVE "ofcourse"
 
-static void test_rule_loop (qof_book_mergeData*, qof_book_mergeRule*, guint);
+static void test_rule_loop (QofBookMergeData*, QofBookMergeRule*, guint);
 static void test_merge (void);
 gboolean myobjRegister (void);
 
@@ -50,7 +50,7 @@
 {
 	QofInstance inst;
 	char     	*Name;
-	gnc_numeric Amount;
+	gnc_numeric	Amount;
 	const GUID 	*obj_guid;
 	Timespec 	date;
 	double 		discount; /* cheap pun, I know. */
@@ -261,7 +261,7 @@
 	gint64 minor;
 	gchar *import_init, *target_init;
 	gnc_numeric obj_amount;
-	qof_book_mergeData *mergeData;
+	QofBookMergeData *mergeData;
 	
 	target = qof_book_new();
 	import = qof_book_new();
@@ -343,21 +343,21 @@
 	obj_setMinor(target_obj, minor);
 	obj_setDate(target_obj, tc );
 	
-	mergeData = qof_book_mergeInit(import, target);
+	mergeData = qof_book_merge_init(import, target);
 	do_test ( mergeData != NULL, "FATAL: Merge could not be initialised!\t aborting . . ");
 	g_return_if_fail(mergeData != NULL);
- 	qof_book_mergeRuleForeach(mergeData, test_rule_loop, MERGE_REPORT);
-	qof_book_mergeRuleForeach(mergeData, test_rule_loop, MERGE_UPDATE);
-	qof_book_mergeRuleForeach(mergeData, test_rule_loop, MERGE_NEW);
+ 	qof_book_merge_rule_foreach(mergeData, test_rule_loop, MERGE_REPORT);
+	qof_book_merge_rule_foreach(mergeData, test_rule_loop, MERGE_UPDATE);
+	qof_book_merge_rule_foreach(mergeData, test_rule_loop, MERGE_NEW);
  	/* reserved calls - test only */
- 	qof_book_mergeRuleForeach(mergeData, test_rule_loop, MERGE_ABSOLUTE);
- 	qof_book_mergeRuleForeach(mergeData, test_rule_loop, MERGE_DUPLICATE);
+ 	qof_book_merge_rule_foreach(mergeData, test_rule_loop, MERGE_ABSOLUTE);
+ 	qof_book_merge_rule_foreach(mergeData, 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(mergeData) == 0, "Commit failed");
+ 	do_test (qof_book_merge_commit(mergeData) == 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");
@@ -389,7 +389,7 @@
 }
 
 static void
-test_rule_loop (qof_book_mergeData *mergeData, qof_book_mergeRule *rule, guint remainder)
+test_rule_loop (QofBookMergeData *mergeData, QofBookMergeRule *rule, guint remainder)
 {
 	GSList *testing;
 	QofParam *eachParam;
@@ -461,17 +461,17 @@
 	} // end param loop
 	/* set each rule dependent on the user involvement response above. */
 	/* test routine just sets all MERGE_REPORT to MERGE_UPDATE */
-	mergeData = qof_book_mergeUpdateResult(mergeData, MERGE_UPDATE);
+	mergeData = qof_book_merge_update_result(mergeData, MERGE_UPDATE);
 	do_test ((rule->mergeResult != MERGE_REPORT), "update result fail");
 }
 
 int
 main (int argc, char **argv)
 {
-	gnc_engine_init(argc, argv);
+	qof_init();
 	myobjRegister();
 	test_merge();
 	print_test_results();
-	exit(get_rv());
+	qof_close();
 	return 0;
 }

Modified: gnucash/trunk/src/gnome/druid-merge.c
===================================================================
--- gnucash/trunk/src/gnome/druid-merge.c	2006-03-09 16:54:44 UTC (rev 13566)
+++ gnucash/trunk/src/gnome/druid-merge.c	2006-03-09 17:03:01 UTC (rev 13567)
@@ -44,21 +44,21 @@
 static GtkWidget        *qsf_import_merge_window = NULL;
 QofSession              *previous_session = NULL;
 gint                    count = 0;
-qof_book_mergeData      *mergeData = NULL;
+QofBookMergeData        *mergeData = NULL;
 QofSession              *merge_session = NULL;
 QofBook                 *mergeBook = NULL;
 QofBook                 *targetBook = NULL;
 
 static QofLogModule log_module = GNC_QSF_IMPORT;
 
-void collision_rule_loop ( qof_book_mergeData*, qof_book_mergeRule*, guint );
+void collision_rule_loop (QofBookMergeData*, QofBookMergeRule*, guint );
 
 static GtkWidget*
 merge_get_widget (const char *name)
 {
-  if (!qsf_import_merge_window) return NULL;
+	if (!qsf_import_merge_window) return NULL;
 
-  return gnc_glade_lookup_widget (qsf_import_merge_window, name);
+	return gnc_glade_lookup_widget (qsf_import_merge_window, name);
 }
 
 static void
@@ -93,51 +93,51 @@
 
 	g_return_if_fail(mergeData != NULL);
 	ENTER (" ");
-	mergeData = qof_book_mergeUpdateResult(mergeData, MERGE_UPDATE); 
+	mergeData = qof_book_merge_update_result(mergeData, MERGE_UPDATE); 
 	count = 0;
- 	qof_book_mergeRuleForeach(mergeData, collision_rule_loop, MERGE_REPORT);
-    if(count == 0)
-    {
-    	output = GTK_LABEL(merge_get_widget("OutPut"));
-    	gtk_label_set_text(output,_("No conflicts to be resolved."));
-	    gtk_widget_show(GTK_WIDGET(output));
-    }
-    LEAVE (" ");
+	qof_book_merge_rule_foreach(mergeData, collision_rule_loop, MERGE_REPORT);
+	if(count == 0)
+	{
+		output = GTK_LABEL(merge_get_widget("OutPut"));
+		gtk_label_set_text(output,_("No conflicts to be resolved."));
+		gtk_widget_show(GTK_WIDGET(output));
+	}
+	LEAVE (" ");
 }
 
 static void
 on_MergeDuplicate_clicked 	(GtkButton       *button,
               			    gpointer         user_data)
 {
-	qof_book_mergeRule *currentRule;
+	QofBookMergeRule *currentRule;
 	GtkLabel *output;
 	
 	g_return_if_fail(mergeData != NULL);
 	ENTER (" ");
 	currentRule = mergeData->currentRule;
 	if(currentRule->mergeAbsolute == FALSE) { 
-		mergeData = qof_book_mergeUpdateResult(mergeData, MERGE_DUPLICATE); 
+		mergeData = qof_book_merge_update_result(mergeData, MERGE_DUPLICATE); 
 		count = 0;
 	}
 	if(currentRule->mergeAbsolute == TRUE) { 
-		mergeData = qof_book_mergeUpdateResult(mergeData, MERGE_ABSOLUTE); 
+		mergeData = qof_book_merge_update_result(mergeData, MERGE_ABSOLUTE); 
 		count = 0;
 	}
- 	qof_book_mergeRuleForeach(mergeData, collision_rule_loop, MERGE_REPORT);
-    if(count == 0)
-    {
-    	output = GTK_LABEL(merge_get_widget("OutPut"));
-    	gtk_label_set_text(output,_("No conflicts to be resolved."));
-	    gtk_widget_show(GTK_WIDGET(output));
-    }
-    LEAVE (" ");
+ 	qof_book_merge_rule_foreach(mergeData, collision_rule_loop, MERGE_REPORT);
+	if(count == 0)
+	{
+    		output = GTK_LABEL(merge_get_widget("OutPut"));
+	    	gtk_label_set_text(output,_("No conflicts to be resolved."));
+		gtk_widget_show(GTK_WIDGET(output));
+	}
+	LEAVE (" ");
 }
 
 static void
 on_MergeNew_clicked (GtkButton       *button,
               		gpointer         user_data)
 {
-	qof_book_mergeRule *currentRule;
+	QofBookMergeRule *currentRule;
 	GtkLabel *output;
 
 	g_return_if_fail(mergeData != NULL);
@@ -145,17 +145,17 @@
 	g_return_if_fail(currentRule != NULL);
 	ENTER (" ");
 	if(currentRule->mergeAbsolute == FALSE) { 
-		mergeData = qof_book_mergeUpdateResult(mergeData, MERGE_NEW);
+		mergeData = qof_book_merge_update_result(mergeData, MERGE_NEW);
 	}
 	count = 0;
- 	qof_book_mergeRuleForeach(mergeData, collision_rule_loop, MERGE_REPORT);
-    if(count == 0)
-    {
-    	output = GTK_LABEL(merge_get_widget("OutPut"));
-    	gtk_label_set_text(output,_("No conflicts to be resolved."));
-	    gtk_widget_show(GTK_WIDGET(output));
-    }
-    LEAVE (" ");
+ 	qof_book_merge_rule_foreach(mergeData, collision_rule_loop, MERGE_REPORT);
+	if(count == 0)
+	{
+		output = GTK_LABEL(merge_get_widget("OutPut"));
+		gtk_label_set_text(output,_("No conflicts to be resolved."));
+		gtk_widget_show(GTK_WIDGET(output));
+	}
+	LEAVE (" ");
 }
 
 static gboolean
@@ -166,15 +166,15 @@
 	GtkWidget *top;
 	gchar *message;
 
-    if(count > 0)
-    {
-        message = g_strdup_printf(_("Error: Please resolve all %d "
-            "conflicts before trying to commit the data."), count);
+	if(count > 0)
+	{
+		message = g_strdup_printf(_("Error: Please resolve all %d "
+			"conflicts before trying to commit the data."), count);
 		top = gtk_widget_get_toplevel (GTK_WIDGET (gnomedruidpage));
-	    gnc_error_dialog(top, message);
-        g_free(message);
-        return TRUE;
-    }
+		gnc_error_dialog(top, message);
+		g_free(message);
+		return TRUE;
+	}
 	return FALSE;
 }
 
@@ -218,11 +218,11 @@
 	ENTER (" ");
 	g_return_if_fail(mergeData != NULL);
 	gnc_suspend_gui_refresh ();
-	result = qof_book_mergeCommit(mergeData);
+	result = qof_book_merge_commit(mergeData);
 	if(result != 0) {
-    	message = _("Error: the Commit operation failed.");
+		message = _("Error: the Commit operation failed.");
 		top = gtk_widget_get_toplevel (GTK_WIDGET (gnomedruidpage));
-	    gnc_error_dialog(top, message);
+		gnc_error_dialog(top, message);
 	}
 	g_return_if_fail(result == 0);
 	delete_merge_window ();
@@ -247,16 +247,16 @@
 	/* blank out old data */
 	gtk_label_set_text(progress, "");
 	g_return_if_fail(mergeBook || targetBook);
-	mergeData = qof_book_mergeInit(mergeBook, targetBook);
+	mergeData = qof_book_merge_init(mergeBook, targetBook);
 	g_return_if_fail(mergeData != NULL);
-    count = 0;
- 	qof_book_mergeRuleForeach(mergeData, collision_rule_loop, MERGE_REPORT);
-    if(count == 0)
-    {
-    	output = GTK_LABEL(merge_get_widget("OutPut"));
-    	gtk_label_set_text(output,_("No conflicts to be resolved."));
-	    gtk_widget_show(GTK_WIDGET(output));
-    }
+	count = 0;
+ 	qof_book_merge_rule_foreach(mergeData, collision_rule_loop, MERGE_REPORT);
+	if(count == 0)
+	{
+		output = GTK_LABEL(merge_get_widget("OutPut"));
+		gtk_label_set_text(output,_("No conflicts to be resolved."));
+		gtk_widget_show(GTK_WIDGET(output));
+	}
 	gnc_resume_gui_refresh ();
 	LEAVE (" ");
 }
@@ -264,8 +264,8 @@
 static GtkWidget *
 gnc_create_import_druid ( void )
 {
-  GtkWidget *dialog, *druid, *start_page;
-  GladeXML *xml;
+	GtkWidget *dialog, *druid, *start_page;
+	GladeXML *xml;
 
 	xml = gnc_glade_xml_new ("merge.glade", "Merge Druid");
 
@@ -274,7 +274,7 @@
 	gnc_druid_set_colors (GNOME_DRUID (druid));
 	start_page = glade_xml_get_widget (xml, "start_page");
 	gtk_widget_show (start_page);
-    gtk_widget_show (glade_xml_get_widget (xml, "MergeDruidFinishPage"));
+	gtk_widget_show (glade_xml_get_widget (xml, "MergeDruidFinishPage"));
 
 	glade_xml_signal_connect(xml, "on_start_page_next",
 		G_CALLBACK (on_import_start_page_next));
@@ -305,7 +305,7 @@
 	return dialog;
 }
 
-void collision_rule_loop(qof_book_mergeData *mergeData, qof_book_mergeRule *rule, 
+void collision_rule_loop(QofBookMergeData *mergeData, QofBookMergeRule *rule, 
                         guint remainder)
 {
 	GSList *user_reports;
@@ -367,7 +367,7 @@
 	g_free(buffer);
 	g_free(importstring);
 	g_free(targetstring);
-    LEAVE (" ");
+	LEAVE (" ");
 }
 
 void



More information about the gnucash-changes mailing list