[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(¶m_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(¶m_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