[Gnucash-changes] setting the public book merge API (sync with QOF)

Neil Williams codehelp at cvs.gnucash.org
Sun Jun 19 17:44:52 EDT 2005


Log Message:
-----------
setting the public book merge API (sync with QOF)

Tags:
----
gnucash-gnome2-dev

Modified Files:
--------------
    gnucash:
        ChangeLog
    gnucash/src/engine:
        qof_book_merge.c
        qof_book_merge.h
        qofclass.h

Revision Data
-------------
Index: ChangeLog
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/ChangeLog,v
retrieving revision 1.1487.2.232
retrieving revision 1.1487.2.233
diff -LChangeLog -LChangeLog -u -r1.1487.2.232 -r1.1487.2.233
--- ChangeLog
+++ ChangeLog
@@ -1,4 +1,10 @@
 2005-06-19  Neil Williams <linux at codehelp.co.uk>
+	* src/engine/qof_book_merge.c: Re-organising to
+	use static functions.
+	* src/engine/qof_book_merge.h: Setting public API.
+	* src/engine/qofclass.h: Typos.
+
+2005-06-19  Neil Williams <linux at codehelp.co.uk>
 	* src/business/business-core/gncAddress.c:
 	* src/business/business-core/gncAddress.h:
 	* src/business/business-core/gncCustomer.c:
Index: qof_book_merge.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/qof_book_merge.h,v
retrieving revision 1.2.2.3
retrieving revision 1.2.2.4
diff -Lsrc/engine/qof_book_merge.h -Lsrc/engine/qof_book_merge.h -u -r1.2.2.3 -r1.2.2.4
--- src/engine/qof_book_merge.h
+++ src/engine/qof_book_merge.h
@@ -66,10 +66,10 @@
 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.
 
-	@{ */
-/**@file  qof_book_merge.h
-	@brief API for merging two \c QofBook* structures with collision handling
-	@author Copyright (c) 2004 Neil Williams <linux at codehelp.co.uk>
+ @{ */
+/** @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>
@@ -117,17 +117,15 @@
  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 */
+	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 
@@ -155,15 +153,15 @@
 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. */
+	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 char* mergeLabel;			/**< Descriptive label for the object type, useful for the
-											user intervention dialog. */
-	GSList *mergeParam;				/**< list of usable parameters for the object type */
-	GSList *linkedEntList;			/**< list of complex data types included in this object. 
+	QofIdType mergeType;     /**< type of comparison required for check for collision */
+	const char* mergeLabel;  /**< Descriptive label for the object type, useful for the
+                                 user intervention dialog. */
+	GSList *mergeParam;      /**< list of usable parameters for the object type */
+	GSList *linkedEntList;   /**< list of complex data types included in this object. 
 
 	linkedEntList contains an ::QofEntity reference to any parameter that is not
 	one of the core QOF_TYPE data types. This entity must be already registered with QOF
@@ -172,8 +170,8 @@
 	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. */
+	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;
 
 
@@ -192,28 +190,28 @@
 */
 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. */
+	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.
+	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.  */
+	GHashTable *target_table;    /**< The GHashTable to hold the qof_entity_rating values.  */
 
 }qof_book_mergeData;
 
 
 /* ======================================================================== */
-/** @name qof_book_merge API */
-/** @{
+/** @name qof_book_merge API
+ @{
 */
 /** \brief Initialise the qof_book_merge process
 
@@ -313,13 +311,11 @@
 
 */
 void qof_book_mergeRuleForeach( qof_book_mergeData*,
-								qof_book_mergeRuleForeachCB, 
-								qof_book_mergeResult );
+                                qof_book_mergeRuleForeachCB, 
+                                qof_book_mergeResult );
 
 /** \brief provides easy string access to parameter data for dialog use
 
-<b>Must only be used for display purposes!</b>
-
 Uses the param_getfcn to retrieve the parameter value as a string, suitable for
 display in dialogs and user intervention output. Within a qof_book_merge context,
 only the parameters used in the merge are available, i.e. parameters where both
@@ -412,8 +408,7 @@
 		
 */
 qof_book_mergeData*
-qof_book_mergeUpdateResult(qof_book_mergeData *mergeData,
-						qof_book_mergeResult tag);
+qof_book_mergeUpdateResult(qof_book_mergeData *mergeData, qof_book_mergeResult tag);
 
 
 /** \brief Commits the import data to the target book
@@ -461,275 +456,7 @@
 
 /** @} */
 
-/* ======================================================================== */
-/* Internal callback routines */
-
-/** @name Phase 1: Import book */
-/** @{
-*/
-
-/** \brief Looks up all import objects and calls ::qof_book_mergeCompare for each. 
-
-	This callback is used to obtain a list of all objects and their parameters
-	in the book to be imported.\n
-
-	Called by ::qof_book_mergeForeachType.\n
-	Receives all instances of only those objects that exist in the import book,
-	from ::qof_object_foreach. ::qof_book_mergeData contains a full list of all registered 
-	parameters for each object in the mergeObjectParams GSList. \n
-	Looks up the live parameter data (via ::QofEntity and ::QofParam), creates the rule, 
-	compares the data and stores the result of the comparison.
-
-Process:
-
-	-# Sets default ::qof_book_mergeResult as MERGE_UNDEF - undefined.\n
-	-# Obtains GUID, parameter data, type and rule.
-	-# Compares GUID with original book, sets ::qof_book_mergeData .mergeAbsolute
-	to TRUE if exact match.
-	-# Inserts rule into ::qof_book_mergeData rule list.
-	-# Runs the comparison for that data type using ::qof_book_mergeCompare.
-	
-*/
-void qof_book_mergeForeach (QofEntity* mergeEnt, gpointer mergeData);
-
-/** \brief Registered Object Callback.
-
-	Receives one object at a time from ::qof_object_foreach_type.\n
-	Note: generic type data only, no live data accesses.\n
-	::qof_object_foreach_type called directly by ::qof_book_mergeInit.
-
-	This callback is used to obtain a list of all registered
-	objects, whether or not the objects exist in either the import or
-	original books.\n
-	
-	Invokes the callback ::qof_book_mergeForeach on every instance of a particular object type.
-	The callback will be invoked only for those instances stored in the import book and therefore
-	qof_book_mergeForeach gains the first access to any live data.
-*/
-void qof_book_mergeForeachType (QofObject* merge_obj, gpointer mergeData);
-
-/** \brief Iterates over each parameter name within the selected QofObject.
-
-	 Receives the list of parameter names from ::QofParam and fills the GSList in
-	 ::qof_book_mergeData.\n
-	 No live data access - object typing and parameter listing only.\n
-	 \b Note: This function is called by ::qof_book_mergeForeachType in the comparison
-	 stage and ::qof_book_mergeCommitRuleLoop in the commit stage. Change with care!
-*/
-void qof_book_mergeForeachParam(QofParam* param_name, gpointer mergeData);
-
-/** @} */
-/** @name Phase 2: Target book */
-/** @{
-*/
-
-/** \brief Registered Object Callback for the \b target book.
-
-	Receives one object at a time from ::qof_object_foreach_type.\n
-\n
-	This callback is used to iterate through all the registered
-	objects, in the \b Target book. When the target object type
-	matches the object type of the current import object, calls
-	::qof_book_mergeForeachTarget to store details of the possible target
-	matches in the GSList *targetList in ::qof_book_mergeData .
-	\n
-*/
-void qof_book_mergeForeachTypeTarget ( QofObject* merge_obj, gpointer mergeData);
-
-
-/** \brief Looks up all \b target objects of a specific type.
-
-	This callback is used to obtain a list of all suitable objects and their parameters
-	in the \b target book.\n
-\n
-	Called by ::qof_book_mergeForeachTypeTarget.\n
-	Receives all instances of only those objects that exist in the target book,
-	that match the object type of the current \b import object.
-	This is done when there is no GUID match and there is no way to know if
-	a corresponding object exists in the target book that would conflict with the
-	data in the import object.
-\n
-	Stores details of the QofEntity* of each suitable object in the target book
-	for later comparison by ::qof_book_mergeCompare .
-	
-*/
-void qof_book_mergeForeachTarget (QofEntity* mergeEnt, gpointer mergeData);
-
-/** \brief 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.
-	
-*/
-gboolean qof_book_merge_rule_cmp(gconstpointer a, gconstpointer b);
-
-/** \brief Recursively matches orphaned entities
-
-Any entities from the import book that were superceded by a better match are
-retained in the mergeData orphan_list. This function processes each one
-and finds the best remaining match and adds the rule back to the
-::qof_book_mergeData::mergeList 
-until ::qof_book_mergeData::orphan_list is empty.
-
-*/
-void qof_book_merge_match_orphans(qof_book_mergeData *mergeData);
-
-/** @} */
-/** @name Phase 3: User Intervention
-*/
-/** @{
-*/
-
-/** \brief Iterates over the rules and declares the number of rules left to match
-
-	The second argument is a guint holding the remainder which might be
-	useful for progress feedback in the GUI. 
-*/
-void qof_book_mergeRuleCB(gpointer, gpointer);
-
-/** @} */
-/** @name Phase 4: Commit import to target book
-*/
-/** @{
-*/
-
-/** \brief Separates the rules according to the comparison results.
-
-	Used to create a list of all rules that match a particular ::qof_book_mergeResult.
-	Intended for ::MERGE_NEW and ::MERGE_UPDATE, it can be used for ::MERGE_ABSOLUTE or
-	::MERGE_DUPLICATE if you want to maybe report on what will be ignored in the import.
-	
-	It can \b NOT be used for ::MERGE_UNDEF, ::MERGE_INVALID or ::MERGE_REPORT.
-*/
-void qof_book_mergeCommitForeach (
-				qof_book_mergeRuleForeachCB cb, 
-				qof_book_mergeResult mergeResult,
-				qof_book_mergeData *mergeData );
-
-/** \brief Iterates over the rules and declares the number of rules left to commit
-
-	The second argument is a guint holding the remainder which might be
-	useful for progress feedback in the GUI. 
-
-*/
-void qof_book_mergeCommitForeachCB(gpointer, gpointer);
-
-/** \brief Commit the data from the import to the target QofBook.
-
-	Called by ::qof_book_mergeCommit to commit data from each rule in turn.
-	Uses QofParam->param_getfcn - ::QofAccessFunc to query the import book
-	and param_setfcn - ::QofSetterFunc to update the target book.
-\n	
-	Note: Not all param_getfcn can have a matching param_setfcn.
-	Getting the balance of an account is obviously necessary to other routines
-	but is pointless in a comparison for a merge - the balance is calculated from
-	transactions, it cannot be set by the account. A discrepancy in the calculated
-	figures for an account object should not cause a MERGE_REPORT.
-\n
- 	Limits the comparison routines to only calling param_getfcn if 
-	param_setfcn is not NULL. 
-	
-*/
-
-void qof_book_mergeCommitRuleLoop(qof_book_mergeData *mergeData, 
-									qof_book_mergeRule *rule, 
-									guint remainder);
-
-/** @} */
-
-
-/** @name Comparison and Rule routines
-*/
-/** @{
-*/
-
-/** \brief Set the ::qof_book_mergeResult for each QofIdType parameter, using ::GUID if any.
-
-\n
-GUID's used in comparisons of entities and instances.
-
-If there is no GUID match, \a mergeData->mergeAbsolute will be set to FALSE.
-::qof_book_mergeCompare will receive a GSList of ::QofEntity * targets instead of one
-unique match and will iterate through the parameter comparisons for each member of 
-the GSList *targetList.
-
-Sets function pointers to the parameter_getter and parameter_setter routines from 
-::QofParam *mergeObjectParams and matches the comparison to the incoming data type:\n
-
- at param mergeRule - contains the GUID, the import book reference, ::QofIdType of the object 
-that contains the parameter and mergeResult.
-
-\return -1 in case of error, otherwise 0.
-
-*/
-int 
-qof_book_mergeCompare( qof_book_mergeData* );
-
-/** \brief Makes the decisions about how matches and conflicts are tagged.
-
-Paramater Type Weighting is used via the gint argument. This is used to give
-priority to string matches over boolean or numerical matches. Higher values
-of weight decrease the likelihood of that entity being the best match.
-
-New rules start at:
-	- ::MERGE_ABSOLUTE\n 
-		(GUID's match; first parameter matches) OR
-	- ::MERGE_DUPLICATE\n
-		(GUID's do NOT match; first parameter DOES match) OR
-	- ::MERGE_NEW\n
-	(GUID's do NOT match; first parameters does NOT match).
-	
-If subsequent parameters in the same object FAIL a match:
-	- \a MERGE_ABSOLUTE fallsback to ::MERGE_UPDATE \n
-		(GUID matches but some parameters differ)\n
-		(guidTarget will be updated with mergeEnt)
-	- \a MERGE_DUPLICATE fallsback to ::MERGE_REPORT\n
-		(GUID does not match and some parameters do NOT match)
-	- \a MERGE_NEW fallsback to \a MERGE_REPORT
-		(GUID does not match and some parameters now DO match)
-
-<b>Comparisons without a GUID match.</b>
-	Only sets a failed match if ALL parameters fail to match.
-	When absolute is FALSE, all suitable target objects are compared.
-
-	Identifies the closest match using a difference rank. This avoids 
-	using non-generic tests for object similarities. The
-	value closest to zero is used.
-	
-	qof_book_merge use sa high value of weight to make a good match
-	more important and make it more likely that the chosen target will
-	have matching values for the types with the highest weight.
-
- at param	currentRule - the ::qof_book_mergeRule to update.
- at param	mergeMatch	- whether the two entities match or not
- at param	weight		- Parameter Type Weighting.
-
- at return 	returns the qof_book_mergeRule.
-
-*/
-qof_book_mergeRule*
-qof_book_mergeUpdateRule( qof_book_mergeRule *currentRule, gboolean mergeMatch, gint weight);
-
 /** @} */
 /** @} */
 #endif // QOFBOOKMERGE_H
+
Index: qof_book_merge.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/qof_book_merge.c,v
retrieving revision 1.2.2.8
retrieving revision 1.2.2.9
diff -Lsrc/engine/qof_book_merge.c -Lsrc/engine/qof_book_merge.c -u -r1.2.2.8 -r1.2.2.9
--- src/engine/qof_book_merge.c
+++ src/engine/qof_book_merge.c
@@ -23,7 +23,7 @@
 
 #include "qof_book_merge.h"
 #include "qofid-p.h"
-static short module = MOD_IMPORT; 
+static short module = MOD_ENGINE;
 
 /* private rule iteration struct */
 struct qof_book_mergeRuleIterate {
@@ -44,287 +44,49 @@
 #define QOF_STRING_WEIGHT       3
 #define QOF_DATE_STRING_LENGTH  MAX_DATE_LENGTH
 
-/* ================================================================ */
-/* API functions. */
-
-qof_book_mergeData*
-qof_book_mergeInit( QofBook *importBook, QofBook *targetBook) 
-{
-	qof_book_mergeData *mergeData;
-	qof_book_mergeRule *currentRule;
-	GList *check;
+/** \brief Makes the decisions about how matches and conflicts are tagged.
 
-	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);
-	}
+Paramater Type Weighting is used via the gint argument. This is used to give
+priority to string matches over boolean or numerical matches. Higher values
+of weight decrease the likelihood of that entity being the best match.
+
+New rules start at:
+	- ::MERGE_ABSOLUTE\n 
+		(GUID's match; first parameter matches) OR
+	- ::MERGE_DUPLICATE\n
+		(GUID's do NOT match; first parameter DOES match) OR
+	- ::MERGE_NEW\n
+	(GUID's do NOT match; first parameters does NOT match).
 	
-	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;
+If subsequent parameters in the same object FAIL a match:
+	- \a MERGE_ABSOLUTE fallsback to ::MERGE_UPDATE \n
+		(GUID matches but some parameters differ)\n
+		(guidTarget will be updated with mergeEnt)
+	- \a MERGE_DUPLICATE fallsback to ::MERGE_REPORT\n
+		(GUID does not match and some parameters do NOT match)
+	- \a MERGE_NEW fallsback to \a MERGE_REPORT
+		(GUID does not match and some parameters now DO match)
+
+<b>Comparisons without a GUID match.</b>
+	Only sets a failed match if ALL parameters fail to match.
+	When absolute is FALSE, all suitable target objects are compared.
+
+	Identifies the closest match using a difference rank. This avoids 
+	using non-generic tests for object similarities. The
+	value closest to zero is used.
 	
-	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);
-}
-
-/*
-	Need to fix the KVP->string. How?
+	qof_book_merge use sa high value of weight to make a good match
+	more important and make it more likely that the chosen target will
+	have matching values for the types with the highest weight.
+
+ at param	currentRule - the ::qof_book_mergeRule to update.
+ at param	mergeMatch	- whether the two entities match or not
+ at param	weight		- Parameter Type Weighting.
 
-	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.
+ at return 	returns the qof_book_mergeRule.
 
-	The QOF_TYPE_BOOLEAN is lowercase for the same reason.
 */
-char*
-qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt)
-{
-	gchar 		*param_string, param_date[QOF_DATE_STRING_LENGTH];
-	char 		param_sa[GUID_ENCODING_LENGTH + 1];
-	KvpFrame 	*param_kvp;
-	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*);
-	char        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("%lld", 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" */
-		/* FIXME: how can this be a string??? */
-		if(safe_strcmp(paramType, QOF_TYPE_KVP) == 0) { 
-			param_kvp = kvp_frame_copy(qtparam->param_getfcn(qtEnt,qtparam));
-
-			return param_string;
-		}
-		if(safe_strcmp(paramType, QOF_TYPE_CHAR) == 0) { 
-			char_getter = (char (*)(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;
-}
-
-/* End of API functions. Internal code follows. */
-/* ==================================================================== */
-
-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);
-}
-
-qof_book_mergeRule*
+static qof_book_mergeRule*
 qof_book_mergeUpdateRule(qof_book_mergeRule *currentRule, gboolean match, gint weight)
 {
 	gboolean absolute;
@@ -346,26 +108,41 @@
 	return currentRule;
 }
 
-int 
+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, *charImport, *charTarget;
-	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      *stringImport, *stringTarget, *charImport, *charTarget;
+	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*);
 
 	g_return_val_if_fail((mergeData != NULL), -1);
 	currentRule = mergeData->currentRule;
@@ -378,7 +155,6 @@
 	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;
@@ -387,7 +163,6 @@
 		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)  { 
@@ -475,22 +250,24 @@
 			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_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;
 		}
-		/* deal with custom type parameters : 
-		 using references to other registered QOF objects. */
 		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);
-					/* Compare the mergeEnt reference with targetEnt reference */
+					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);
 			}
@@ -503,7 +280,19 @@
 	return 0;
 }
 
-void
+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,
@@ -535,23 +324,37 @@
 	g_list_foreach (subList, qof_book_mergeCommitForeachCB, &iter);
 }
 
-void qof_book_mergeCommitForeachCB(gpointer rule, gpointer arg)
-{
-	struct qof_book_mergeRuleIterate *iter;
+/** \brief build the table of target comparisons
 
-	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--;
-}
+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.
 
-gboolean
+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;
 }
@@ -563,7 +366,7 @@
 		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; }
@@ -577,7 +380,7 @@
 	mergeData->orphan_list = g_slist_append(mergeData->orphan_list, rule);
 }
 
-void
+static void
 qof_book_merge_match_orphans(qof_book_mergeData *mergeData)
 {
 	GSList *orphans, *targets;
@@ -604,21 +407,49 @@
 			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);
-			}
+		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);
 }
 
-void 
+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;
@@ -627,7 +458,7 @@
 	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);
@@ -717,67 +548,39 @@
 	/* return to qof_book_mergeInit */
 }
 
-void qof_book_mergeForeachTarget (QofEntity* targetEnt, gpointer user_data)
+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(targetEnt != NULL);
-	mergeData->targetList = g_slist_prepend(mergeData->targetList,targetEnt);
-}
 
-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);
+	g_return_if_fail(param != NULL);
+	if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
+		mergeData->mergeObjectParams = g_slist_append(mergeData->mergeObjectParams, param);
 	}
 }
 
-void 
+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);
-}
 
-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);
+	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);
 }
 
-void
+static void
 qof_book_mergeRuleCB(gpointer rule, gpointer arg)
 {
 	struct qof_book_mergeRuleIterate *iter;
@@ -814,7 +617,24 @@
 	return referenceEnt;
 }
 
-void qof_book_mergeCommitRuleLoop(
+/** \brief Commit the data from the import to the target QofBook.
+
+	Called by ::qof_book_mergeCommit to commit data from each rule in turn.
+	Uses QofParam->param_getfcn - ::QofAccessFunc to query the import book
+	and param_setfcn - ::QofSetterFunc to update the target book.
+\n	
+	Note: Not all param_getfcn can have a matching param_setfcn.
+	Getting the balance of an account is obviously necessary to other routines
+	but is pointless in a comparison for a merge - the balance is calculated from
+	transactions, it cannot be set by the account. A discrepancy in the calculated
+	figures for an account object should not cause a MERGE_REPORT.
+\n
+ 	Limits the comparison routines to only calling param_getfcn if 
+	param_setfcn is not NULL. 
+	
+*/
+static void 
+qof_book_mergeCommitRuleLoop(
 						qof_book_mergeData *mergeData,
 						qof_book_mergeRule *rule, 
 						guint remainder) 
@@ -950,7 +770,6 @@
 		if(registered_type == FALSE) {
 			linkage = g_slist_copy(rule->linkedEntList);
 			referenceEnt = NULL;
-//			currentRule = NULL;
 			reference_setter = (void(*)(QofEntity*, QofEntity*))cm_param->param_setfcn;
 			if((linkage == NULL)&&(rule->mergeResult == MERGE_NEW)) {
 				referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param);
@@ -970,3 +789,274 @@
 		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.
+*/
+char*
+qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt)
+{
+	gchar       *param_string, param_date[QOF_DATE_STRING_LENGTH];
+	char        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*);
+	char        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("%lld", 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 = (char (*)(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. */
+/* ==================================================================== */
Index: qofclass.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/qofclass.h,v
retrieving revision 1.1.6.6
retrieving revision 1.1.6.7
diff -Lsrc/engine/qofclass.h -Lsrc/engine/qofclass.h -u -r1.1.6.6 -r1.1.6.7
--- src/engine/qofclass.h
+++ src/engine/qofclass.h
@@ -42,8 +42,8 @@
   declaring a new QOF Class.
 
   Because a QOF Class associates getters and setters with
-  a type, one can then ask, at run time, what paramters
-  are associated with a given type, even if those paramters
+  a type, one can then ask, at run time, what parameters
+  are associated with a given type, even if those parameters
   were not known at compile time.  Thus, a QOF Class is 
   sort-of like a DynAny implementation.  QOF classes can
   be used to provide "object introspection", i.e. asking 


More information about the gnucash-changes mailing list