[Gnucash-changes] r11882 - gnucash/trunk/lib - New locations for QOF files

Neil Williams codehelp at cvs.gnucash.org
Mon Nov 7 10:46:12 EST 2005


Author: codehelp
Date: 2005-11-07 10:45:58 -0500 (Mon, 07 Nov 2005)
New Revision: 11882

Added:
   gnucash/trunk/lib/libqof/
   gnucash/trunk/lib/libqof/Makefile.am
   gnucash/trunk/lib/libqof/backend/
   gnucash/trunk/lib/libqof/backend/Makefile.am
   gnucash/trunk/lib/libqof/backend/file/
   gnucash/trunk/lib/libqof/backend/file/Makefile.am
   gnucash/trunk/lib/libqof/backend/file/pilot-qsf-GnuCashInvoice.xml
   gnucash/trunk/lib/libqof/backend/file/qof-backend-qsf.h
   gnucash/trunk/lib/libqof/backend/file/qsf-backend.c
   gnucash/trunk/lib/libqof/backend/file/qsf-dir.h.in
   gnucash/trunk/lib/libqof/backend/file/qsf-map.xsd.xml
   gnucash/trunk/lib/libqof/backend/file/qsf-object.xsd.xml
   gnucash/trunk/lib/libqof/backend/file/qsf-xml-map.c
   gnucash/trunk/lib/libqof/backend/file/qsf-xml.c
   gnucash/trunk/lib/libqof/backend/file/qsf-xml.h
   gnucash/trunk/lib/libqof/qof/
   gnucash/trunk/lib/libqof/qof/Makefile.am
   gnucash/trunk/lib/libqof/qof/gnc-date.c
   gnucash/trunk/lib/libqof/qof/gnc-date.h
   gnucash/trunk/lib/libqof/qof/gnc-engine-util.c
   gnucash/trunk/lib/libqof/qof/gnc-engine-util.h
   gnucash/trunk/lib/libqof/qof/gnc-event-p.h
   gnucash/trunk/lib/libqof/qof/gnc-event.c
   gnucash/trunk/lib/libqof/qof/gnc-event.h
   gnucash/trunk/lib/libqof/qof/gnc-numeric.c
   gnucash/trunk/lib/libqof/qof/gnc-numeric.h
   gnucash/trunk/lib/libqof/qof/gnc-numeric.scm
   gnucash/trunk/lib/libqof/qof/gnc-trace.c
   gnucash/trunk/lib/libqof/qof/gnc-trace.h
   gnucash/trunk/lib/libqof/qof/guid.c
   gnucash/trunk/lib/libqof/qof/guid.h
   gnucash/trunk/lib/libqof/qof/kvp-util-p.h
   gnucash/trunk/lib/libqof/qof/kvp-util.c
   gnucash/trunk/lib/libqof/qof/kvp-util.h
   gnucash/trunk/lib/libqof/qof/kvp_frame.c
   gnucash/trunk/lib/libqof/qof/kvp_frame.h
   gnucash/trunk/lib/libqof/qof/md5.c
   gnucash/trunk/lib/libqof/qof/md5.h
   gnucash/trunk/lib/libqof/qof/qof-be-utils.h
   gnucash/trunk/lib/libqof/qof/qof.h
   gnucash/trunk/lib/libqof/qof/qof_book_merge.c
   gnucash/trunk/lib/libqof/qof/qof_book_merge.h
   gnucash/trunk/lib/libqof/qof/qofbackend-p.h
   gnucash/trunk/lib/libqof/qof/qofbackend.c
   gnucash/trunk/lib/libqof/qof/qofbackend.h
   gnucash/trunk/lib/libqof/qof/qofbook-p.h
   gnucash/trunk/lib/libqof/qof/qofbook.c
   gnucash/trunk/lib/libqof/qof/qofbook.h
   gnucash/trunk/lib/libqof/qof/qofchoice.c
   gnucash/trunk/lib/libqof/qof/qofchoice.h
   gnucash/trunk/lib/libqof/qof/qofclass-p.h
   gnucash/trunk/lib/libqof/qof/qofclass.c
   gnucash/trunk/lib/libqof/qof/qofclass.h
   gnucash/trunk/lib/libqof/qof/qofgobj.c
   gnucash/trunk/lib/libqof/qof/qofgobj.h
   gnucash/trunk/lib/libqof/qof/qofid-p.h
   gnucash/trunk/lib/libqof/qof/qofid.c
   gnucash/trunk/lib/libqof/qof/qofid.h
   gnucash/trunk/lib/libqof/qof/qofinstance-p.h
   gnucash/trunk/lib/libqof/qof/qofinstance.c
   gnucash/trunk/lib/libqof/qof/qofinstance.h
   gnucash/trunk/lib/libqof/qof/qofla-dir.h.in
   gnucash/trunk/lib/libqof/qof/qofmath128.c
   gnucash/trunk/lib/libqof/qof/qofmath128.h
   gnucash/trunk/lib/libqof/qof/qofobject-p.h
   gnucash/trunk/lib/libqof/qof/qofobject.c
   gnucash/trunk/lib/libqof/qof/qofobject.h
   gnucash/trunk/lib/libqof/qof/qofquery-deserial.c
   gnucash/trunk/lib/libqof/qof/qofquery-deserial.h
   gnucash/trunk/lib/libqof/qof/qofquery-p.h
   gnucash/trunk/lib/libqof/qof/qofquery-serialize.c
   gnucash/trunk/lib/libqof/qof/qofquery-serialize.h
   gnucash/trunk/lib/libqof/qof/qofquery.c
   gnucash/trunk/lib/libqof/qof/qofquery.h
   gnucash/trunk/lib/libqof/qof/qofquerycore-p.h
   gnucash/trunk/lib/libqof/qof/qofquerycore.c
   gnucash/trunk/lib/libqof/qof/qofquerycore.h
   gnucash/trunk/lib/libqof/qof/qofsession-p.h
   gnucash/trunk/lib/libqof/qof/qofsession.c
   gnucash/trunk/lib/libqof/qof/qofsession.h
   gnucash/trunk/lib/libqof/qof/qofsql.c
   gnucash/trunk/lib/libqof/qof/qofsql.h
Log:
New locations for QOF files

Added: gnucash/trunk/lib/libqof/Makefile.am
===================================================================
--- gnucash/trunk/lib/libqof/Makefile.am	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/Makefile.am	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,2 @@
+SUBDIRS = . qof backend
+

Added: gnucash/trunk/lib/libqof/backend/Makefile.am
===================================================================
--- gnucash/trunk/lib/libqof/backend/Makefile.am	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/Makefile.am	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,2 @@
+SUBDIRS = file
+

Added: gnucash/trunk/lib/libqof/backend/file/Makefile.am
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/Makefile.am	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/Makefile.am	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,43 @@
+SUBDIRS = . 
+
+lib_LTLIBRARIES = libqof-backend-qsf.la
+
+AM_CFLAGS = \
+  -I.. -I../.. \
+  -DLOCALE_DIR=\""$(datadir)/locale"\" \
+  ${QOF_CFLAGS} \
+  ${LIBXML2_CFLAGS} \
+  ${GLIB_CFLAGS}
+
+libqof_backend_qsf_la_SOURCES = \
+	qsf-backend.c \
+	qsf-xml-map.c \
+        qsf-xml.c
+
+LIBADD = \
+ ${QOF_LIBS} \
+ ${GLIB_LIBS} \
+ ${LIBXML2_LIBS}
+
+qsfschemadir = $(QSF_SCHEMA_DIR)
+qsfschema_DATA = \
+        qsf-object.xsd.xml \
+        qsf-map.xsd.xml \
+        pilot-qsf-GnuCashInvoice.xml
+
+EXTRA_DIST = \
+	$(qsfschema_DATA) \
+	qsf-dir.h.in      \
+	qof-backend-qsf.h \
+	qsf-xml.h
+
+qsf-dir.h: qsf-dir.h.in
+	rm -f $@.tmp
+	sed < $< > $@.tmp \
+	        -e 's:@-QSF_SCHEMA_DIR-@:${QSF_SCHEMA_DIR}:g'
+	mv $@.tmp $@
+
+BUILT_SOURCES = qsf-dir.h
+
+CONFIG_CLEAN_FILES = qsf-dir.h
+

Added: gnucash/trunk/lib/libqof/backend/file/pilot-qsf-GnuCashInvoice.xml
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/pilot-qsf-GnuCashInvoice.xml	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/pilot-qsf-GnuCashInvoice.xml	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- this map does NOT post invoices automatically -->
+<!-- maps use the same sequence of parameter types as other QSF -->
+<!-- Trans:desc can be set by expenses or datebook -->
+<qsf-map
+xmlns="http://qof.sourceforge.net/">
+<definition qof_version="3">
+  <define e_type="pilot_expenses">Pilot-link QOF expenses</define>
+  <define e_type="pilot_datebook">Pilot-link QOF datebook</define>
+  <define e_type="pilot_address">Pilot-link QOF address</define>
+  <define e_type="gncInvoice">Invoice</define>
+  <define e_type="Trans">Transaction</define>
+  <define e_type="gncEntry">Order/Invoice/Bill Entry</define>
+  <default name="mileage_rate" type="numeric" value="28/100"/>
+  <default name="use_weekday_descriptor" type="boolean" value="true"/>
+  <default name="use_discount" type="boolean" value="false"/>
+  <default name="tax_included" type="boolean" value="false"/>
+  <default name="post_account" type="string" value="Assets:Current Assets:CD account"/>
+  <default name="expenses_account" type="string" value="Income:Other Income"/>
+  <default name="datebook_account" type="string" value="Income:Locum Income"/>
+  <default name="tax_included" type="enum" value="1">GNC_TAXINCLUDED_YES</default>
+  <default name="tax_included" type="enum" value="2">GNC_TAXINCLUDED_NO</default>
+  <default name="tax_included" type="enum" value="3">GNC_TAXINCLUDED_USEGLOBAL</default>
+  <default name="amount_type" type="enum" value="1">GNC_AMT_TYPE_VALUE</default>
+  <default name="amount_type" type="enum" value="2">GNC_AMT_TYPE_PERCENT</default>
+</definition>
+<object type="gncEntry">
+  <calculate type="string" value="desc">
+    <if boolean="use_weekday_descriptor">
+      <set format="%A">expense_date</set>
+    </if>
+    <else type="qof_expenses">
+      <set>expense_vendor</set>
+    </else>
+  </calculate>
+  <calculate type="string" value="action">
+    <if type="qof-expenses">
+      <set>Material</set>
+    </if>
+    <else type="qof-datebook">
+      <set>Hours</set>
+    </else>
+  </calculate>
+  <calculate type="string" value="notes">
+    <set object="qof-expenses">expense_note</set>
+  </calculate>
+  <calculate type="guid" value="bill-to"/>
+  <calculate type="boolean" value="invoice-taxable"/>
+  <calculate type="boolean" value="bill-taxable"/>
+  <calculate type="boolean" value="billable?"/>
+  <calculate type="boolean" value="bill-tax-included"/>
+  <calculate type="boolean" value="invoice-tax-included">
+    <set>tax_included</set>
+  </calculate>
+  <calculate type="numeric" value="iprice">
+    <if type="string" value="expense_type">
+      <equals type="string" value="etMileage">
+        <set>mileage_rate</set>
+      </equals>
+    </if>
+  </calculate>
+  <calculate type="numeric" value="bprice"/>
+  <calculate type="numeric" value="qty">
+    <if string="expense_type">
+      <equals type="string" value="etMileage">
+        <set object="qof-expenses">expense_amount</set>
+      </equals>
+    </if>
+    <else type="string" value="expense_type">
+      <set>0/1</set>
+    </else>
+  </calculate>
+  <calculate type="numeric" value="invoice-discount">
+    <set>0/1</set>
+  </calculate>
+  <calculate type="date" value="date-entered">
+    <set>qsf_time_now</set>
+  </calculate>
+  <calculate type="date" value="date">
+    <set>qsf_enquiry_date</set>
+  </calculate>
+  <calculate type="gint32" value="discount-type">
+    <set option="amount_type_enum">GNC_AMT_TYPE_PERCENT</set>
+  </calculate>
+  <calculate type="gint32" value="discount-method">
+    <set>2</set>
+  </calculate>
+  <calculate type="gint32" value="bill-payment-type"/>
+</object>
+<object type="Trans">
+  <calculate type="string" value="num"/>
+  <calculate type="string" value="desc"/>
+  <calculate type="date" value="date-entered"/>
+  <calculate type="date" value="date-posted"/>
+  <calculate type="date" value="date-due"/>
+  <calculate type="string" value="notes"/>
+</object>
+<object type="gncInvoice">
+  <calculate type="string" value="id"/>
+  <calculate type="string" value="billing_id"/>
+  <calculate type="string" value="notes"/>
+  <calculate type="guid" value="invoice_owner"/>
+  <calculate type="guid" value="account"/>
+  <calculate type="guid" value="posted_txn"/>
+  <calculate type="guid" value="posted_lot"/>
+  <calculate type="guid" value="terms"/>
+  <calculate type="guid" value="bill-to"/>
+  <calculate type="boolean" value="active">
+    <set>true</set>
+  </calculate>
+  <calculate type="date" value="date_opened">
+      <set>qsf_enquiry_date</set>
+  </calculate>
+  <calculate type="date" value="date_posted"/>
+</object>
+</qsf-map>

Added: gnucash/trunk/lib/libqof/backend/file/qof-backend-qsf.h
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qof-backend-qsf.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qof-backend-qsf.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,150 @@
+/***************************************************************************
+ *            qof-backend-qsf.h
+ *
+ *  Sat Jun 11 19:34:36 2005
+ *  Copyright  2005  Neil Williams
+ *  linux at codehelp.co.uk
+ ****************************************************************************/
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+ 
+/** @addtogroup Backend
+    @{ */
+/** @addtogroup QSF QOF Serialisation Format
+
+This is the public interface of the qof-backend-qsf library.
+
+QSF - QOF Serialization Format is an XML serialization format
+i.e. it lays out a QOF object in a series of XML tags. The format will  
+consist of two component formats:
+
+qof-qsf for the QSF object data and
+
+qsf-map to map sets of QSF objects between QOF applications.
+
+Maps exist to allow complex conversions between objects where object parameters
+need to be calculated, combined or processed using conditionals. Some QSF objects
+can be converted using XSL or other standard tools. For more information on maps,
+see http://code.neil.williamsleesmill.me.uk/map.html
+
+QSF object files will contain user data and are to be exported from QOF applications 
+under user control or they can be hand-edited. QSF maps contain application data and 
+can be created by application developers from application source code. Tools may be 
+created later to generate maps interactively but maps require application support as 
+well as an understanding of QOF objects in the import and output applications and are 
+intended to remain within the scope of application developers rather than users.
+
+A QSF file written by one QOF application will need an appropriate QSF map before the 
+data can be accessed by a different application using QOF. Any QSF objects that are 
+not defined in the map will be ignored. QSF files written and accessed by the same 
+application can use maps if necessary or can simply import the QSF data as a whole.
+
+Unless specifically mentioned otherwise, all defined strings are case-sensitive.
+
+Full documentation of this format is at:
+
+http://code.neil.williamsleesmill.me.uk/qsf.html
+
+QSF itself is now being built into the QOF library for use with pilot-link to allow
+Palm objects to be described in QOF, written to XML as QSF and imported directly into 
+GnuCash and other QOF-compliant applications. As a generic format, it does not depend 
+on any pre-defined objects - as the current GnuCash XML format depends on AccountGroup. 
+Instead, QSF is a simple container for all QOF objects.
+
+QSF grew from the qof_book_merge code base and uses the qof_book_merge code that is now 
+part of QOF. Any QofBook generated by QSF still needs to be merged into the existing 
+application data using qof_book_merge. See ::BookMerge.
+
+QSF can be used as an export or offline storage format for QOF applications (although 
+long term storage may be best performed using separate (non-XML) methods, depending 
+on the application).
+
+QSF is designed to cope with partial QofBooks at the QofObject level. There is no 
+requirement for specific objects to always be defined, as long as each QOF object 
+specified is fully defined, no orphan or missing parameters are allowed. Part of the
+handling for partial books requires a storage mechanism for references to entities
+that are not within reach of the current book. This requires a little extra coding
+in the QSF QofBackend to contain the reference data so that when the book is
+written out, the reference can be included. When the file is imported back in, a
+little extra code then rebuilds those references during the merge.
+
+Copying entites from an existing QofBook using the qof_entity_copy routines will 
+automatically create the reference table. If your QOF objects use references to other
+entities, books that are created manually also need to create a reference table.
+
+Work is continuing on supporting QSF in GnuCash and QOF. QSF is a very open format - 
+the majority of the work will be in standardising object types and creating maps that 
+convert between objects. Applications that read QSF should ignore any objects that do 
+not match the available maps and warn the user about missing data. 
+
+Anyone is free to create their own QSF objects, subject to the GNU GPL. It is intended 
+that QSF can be used as the flexible, open and free format for QOF data - providing 
+all that is lacking from a typical CSV export with all the validation benefits of XML 
+and the complex data handling of QOF. The QSF object and map formats remain under the 
+GNU GPL licence and QSF is free software.
+
+\todo
+	- Adding more map support, some parts of the map are still not coded. equals, 
+		variables and the conditional logic may not be up to the task of the
+		datebook repetition calculations.
+	- Rationalise the API - remove functions that shouldn't be public.
+
+\todo QOF contains numerous g_string_sprintf and g_string_sprintfa calls.
+	These are deprecated and should be renamed to g_string_printf and g_string_append_printf
+	respectively.
+
+QSF is in three sections:
+	- QSF Backend : a QofBackend for file:/ QSF objects and maps.
+		qsf-backend.c
+	- QSF Object  : Input, export and validation of QSF object files.
+		qsf-xml.c
+	- QSF Map : Validation, processing and conversion routines.
+		qsf-xml-map.c
+
+    @{ */
+/** @file qof-backend-qsf.h
+    @brief  QSF API - Backend, maps and objects.
+    @author Copyright (C) 2004-2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#ifndef _QOF_BACKEND_QSF_H
+#define _QOF_BACKEND_QSF_H
+
+#include "gnc-trace.h"
+#include "qofbackend.h"
+#include "qof-be-utils.h"
+
+#define QOF_MOD_QSF "qof-backend-qsf"
+
+/** \brief Describe this backend to the application. 
+
+Sets QSF Backend Version 0.1, access method = file:
+
+This is the QOF backend interface, not a GnuCash module.
+*/
+void qsf_provider_init(void);
+
+/** \brief Create a new QSF backend.
+
+	Initialises the backend and provides access to the
+	functions that will load and save the data.
+*/
+QofBackend* qsf_backend_new(void);
+
+/** @} */
+/** @} */
+
+#endif /* _QOF_BACKEND_QSF_H */

Added: gnucash/trunk/lib/libqof/backend/file/qsf-backend.c
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-backend.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-backend.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1185 @@
+/***************************************************************************
+ *            qsf-backend.c
+ *
+ *  Sat Jan  1 15:07:14 2005
+ *  Copyright  2005  Neil Williams
+ *  linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+
+#include "qof-backend-qsf.h"
+#include "qsf-xml.h"
+#include "qsf-dir.h"
+#include <errno.h>
+#include <sys/stat.h>
+
+#define QSF_TYPE_BINARY "binary"
+#define QSF_TYPE_GLIST "glist"
+#define QSF_TYPE_FRAME "frame"
+#define QSF_COMPRESS "compression_level"
+
+static QofLogModule log_module = QOF_MOD_QSF;
+static int use_gz_level = 0;
+
+static void option_cb (QofBackendOption *option, gpointer data)
+{
+	if(0 == safe_strcmp(QSF_COMPRESS, option->option_name)) {
+		use_gz_level = *(gint*)option->value;
+	}
+}
+
+static void
+qsf_load_config(QofBackend *be, KvpFrame *config)
+{
+  qof_backend_option_foreach(config, option_cb, NULL);
+}
+
+static KvpFrame*
+qsf_get_config(QofBackend *be)
+{
+	QofBackendOption *option;
+
+	if(!be) { return NULL; }
+	qof_backend_prepare_frame(be);
+	option = g_new0(QofBackendOption, 1);
+	option->option_name = QSF_COMPRESS;
+	option->description = _("Level of compression to use: 0 for none, 9 for highest.");
+	option->tooltip = _("QOF can compress QSF XML files using gzip. "
+		"Note that compression is not used when outputting to STDOUT.");
+	option->type = KVP_TYPE_GINT64;
+	option->value = (gpointer)&use_gz_level;
+	qof_backend_prepare_option(be, option);
+	g_free(option);
+	return qof_backend_complete_frame(be);
+}
+
+struct QSFBackend_s 
+{
+	QofBackend be;
+	qsf_param *params;
+	char *fullpath;
+};
+
+typedef struct QSFBackend_s QSFBackend;
+
+void
+qsf_param_init(qsf_param *params)
+{
+	Timespec *qsf_ts;
+	gchar qsf_time_string[QSF_DATE_LENGTH];
+	gchar qsf_enquiry_date[QSF_DATE_LENGTH];
+	gchar qsf_time_match[QSF_DATE_LENGTH];
+	gchar qsf_time_now[QSF_DATE_LENGTH];
+	time_t qsf_time_now_t;
+	gchar *qsf_time_precision;
+
+	g_return_if_fail(params != NULL);
+	params->count = 0;
+	params->supported_types = NULL;
+	params->file_type = QSF_UNDEF;
+	params->qsf_ns = NULL;
+	params->output_doc = NULL;
+	params->output_node = NULL;
+	params->lister = NULL;
+	params->map_ns = NULL;
+	params->qsf_object_list = NULL;
+	params->qsf_parameter_hash = g_hash_table_new(g_str_hash, g_str_equal);
+	params->qsf_default_hash = g_hash_table_new(g_str_hash, g_str_equal);
+	params->qsf_define_hash = g_hash_table_new(g_str_hash, g_str_equal);
+	params->qsf_calculate_hash = g_hash_table_new(g_str_hash, g_str_equal);
+	params->referenceList = NULL;
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_STRING);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_GUID);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_BOOLEAN);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_NUMERIC);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_DATE);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_INT32);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_INT64);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_DOUBLE);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_CHAR);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_KVP);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_COLLECT);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_CHOICE);
+	qsf_time_precision = "%j";
+	qsf_time_now_t = time(NULL);
+	qsf_ts = g_new(Timespec, 1);
+	timespecFromTime_t(qsf_ts, qsf_time_now_t);
+	strftime(qsf_enquiry_date, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(&qsf_time_now_t));
+	strftime(qsf_time_match, QSF_DATE_LENGTH, qsf_time_precision, gmtime(&qsf_time_now_t));
+	strftime(qsf_time_string, QSF_DATE_LENGTH, "%F", gmtime(&qsf_time_now_t));
+	strftime(qsf_time_now, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(&qsf_time_now_t));
+	g_hash_table_insert(params->qsf_default_hash, "qsf_enquiry_date", qsf_enquiry_date);
+	g_hash_table_insert(params->qsf_default_hash, "qsf_time_now", &qsf_time_now_t);
+	g_hash_table_insert(params->qsf_default_hash, "qsf_time_string", qsf_time_string);
+}
+
+static gboolean 
+qsf_determine_file_type(const char *path)
+{
+	struct stat sbuf;
+
+	PINFO (" %s", path);
+	if (!path) { return TRUE; }
+	if (0 == safe_strcmp(path, QOF_STDOUT)) { return TRUE; }
+	if (stat(path, &sbuf) <0)    { return FALSE; }
+	if (sbuf.st_size == 0)       { return TRUE; }
+	if(is_our_qsf_object(path))  { return TRUE; }
+	else if(is_qsf_object(path)) { return TRUE; }
+    else if(is_qsf_map(path))    { return TRUE; }
+	return FALSE;
+}
+
+/* GnuCash does LOTS of filesystem work, QSF is going to leave most of it to libxml2. :-)
+Just strip the file: from the start of the book_path URL. Locks and file
+creation are not implemented.
+*/
+static void
+qsf_session_begin(QofBackend *be, QofSession *session, const char *book_path,
+                   gboolean ignore_lock, gboolean create_if_nonexistent)
+{
+	QSFBackend *qsf_be;
+	char *p, *path;
+
+	PINFO (" ignore_lock=%d create_if_nonexistent=%d", ignore_lock, create_if_nonexistent);
+	g_return_if_fail(be != NULL);
+	qsf_be = (QSFBackend*)be;
+	g_return_if_fail(qsf_be->params != NULL);
+	qsf_be->fullpath = NULL;
+	if(book_path == NULL)
+	{
+		qof_backend_set_error(be, ERR_BACKEND_NO_ERR);
+		return;
+	}
+	p = strchr (book_path, ':');
+	if (p) {
+		path = g_strdup (book_path);
+		if (!g_strncasecmp(path, "file:", 5)) {
+			p = g_new(char, strlen(path) - 5 + 1);
+			strcpy(p, path + 5);
+		}
+		qsf_be->fullpath = g_strdup(p);
+		g_free (path);
+	}
+	else {
+		qsf_be->fullpath = g_strdup(book_path);
+	}
+	qof_backend_set_error(be, ERR_BACKEND_NO_ERR);
+}
+
+static void
+qsf_session_end( QofBackend *be)
+{
+	QSFBackend *qsf_be;
+
+	qsf_be = (QSFBackend*)be;
+	g_return_if_fail(qsf_be != NULL);
+	qsf_free_params(qsf_be->params);
+	g_free(qsf_be->fullpath);
+	qsf_be->fullpath = NULL;
+	xmlCleanupParser();
+}
+
+static void 
+qsf_destroy_backend (QofBackend *be)
+{
+	g_free(be);
+}
+
+QofBackendError 
+qof_session_load_our_qsf_object(QofSession *first_session, const char *path)
+{
+	QofSession *qsf_session;
+	
+	qsf_session = qof_session_new();
+	qof_session_begin(qsf_session, path, FALSE, FALSE);
+	qof_session_load(qsf_session, NULL);
+	/* FIXME: This needs to return success and set the open not merge error in file_open */
+	return ERR_QSF_OPEN_NOT_MERGE;
+}
+
+QofBackendError 
+qof_session_load_qsf_object(QofSession *first_session, const char *path)
+{
+	PINFO ("%s = ERR_QSF_NO_MAP", path);
+	return ERR_QSF_NO_MAP;
+}
+
+void
+qsf_file_type(QofBackend *be, QofBook *book)
+{
+	QSFBackend *qsf_be;
+	qsf_param *params;
+	char *path;
+	gboolean result;
+
+	g_return_if_fail(be != NULL);
+	g_return_if_fail(book != NULL);
+	qsf_be = (QSFBackend*) be;
+	g_return_if_fail(qsf_be != NULL);
+	g_return_if_fail(qsf_be->fullpath != NULL);
+	g_return_if_fail(qsf_be->params != NULL);
+	params = qsf_be->params;
+	params->book = book;
+	path = g_strdup(qsf_be->fullpath);
+	params->filepath = g_strdup(path);
+	qof_backend_get_error(be);
+	result = is_our_qsf_object_be(params);
+	if(result) {
+		params->file_type = OUR_QSF_OBJ;
+		result = load_our_qsf_object(book, path, params);
+		if(!result) { qof_backend_set_error(be, ERR_FILEIO_PARSE_ERROR); }
+		return;
+	} 
+	else if(is_qsf_object_be(params)) {
+		params->file_type = IS_QSF_OBJ;
+		result = load_qsf_object(book, path, params);
+		if(!result) { qof_backend_set_error(be, ERR_FILEIO_PARSE_ERROR); }
+	}
+	if(result == FALSE) {
+		if(is_qsf_map_be(params)) {
+		params->file_type = IS_QSF_MAP;
+		qof_backend_set_error(be, ERR_QSF_MAP_NOT_OBJ);
+		}
+	}
+}
+
+static void
+ent_ref_cb (QofEntity* ent, gpointer user_data)
+{
+	qsf_param *params;
+	QofEntityReference *ref;
+	void (*reference_setter) (QofEntity*, QofEntity*);
+	QofEntity *reference;
+	QofCollection *coll;
+	QofIdType type;
+
+	params = (qsf_param*)user_data;
+	g_return_if_fail(params);
+	while(params->referenceList)
+	{
+		ref = (QofEntityReference*)params->referenceList->data;
+		if(qof_object_is_choice(ent->e_type)) { type = ref->choice_type; }
+		else { type = ref->type; }
+		coll = qof_book_get_collection(params->book, type);
+		reference = qof_collection_lookup_entity(coll, ref->ref_guid);
+		reference_setter = (void(*)(QofEntity*, QofEntity*))ref->param->param_setfcn;
+		if(reference_setter != NULL)
+		{
+			qof_begin_edit((QofInstance*)ent);
+			qof_begin_edit((QofInstance*)reference);
+			reference_setter(ent, reference);
+			qof_commit_edit((QofInstance*)ent);
+			qof_commit_edit((QofInstance*)reference);
+		}
+		params->referenceList = g_list_next(params->referenceList);
+	}
+}
+
+static void
+insert_ref_cb(QofObject *obj, gpointer user_data)
+{
+	qsf_param *params;
+
+	params = (qsf_param*)user_data;
+	g_return_if_fail(params);
+	qof_object_foreach(obj->e_type, params->book, ent_ref_cb, params);
+}
+
+/*================================================
+	Load QofEntity into QofBook from XML in memory
+==================================================*/
+
+static gboolean 
+qsfdoc_to_qofbook(xmlDocPtr doc, qsf_param *params)
+{
+	QofInstance *inst;
+	struct qsf_node_iterate iter;
+	QofBook *book;
+	GList *object_list;
+	xmlNodePtr qsf_root;
+	xmlNsPtr qsf_ns;
+
+	g_return_val_if_fail(params != NULL, FALSE);
+	g_return_val_if_fail(params->input_doc != NULL, FALSE);
+	g_return_val_if_fail(params->book != NULL, FALSE);
+	g_return_val_if_fail(params->file_type == OUR_QSF_OBJ, FALSE);
+	qsf_root = xmlDocGetRootElement(params->input_doc);
+	if(!qsf_root) { return FALSE; }
+	qsf_ns = qsf_root->ns;
+	iter.ns = qsf_ns;
+	book = params->book;
+	params->referenceList = (GList*)qof_book_get_data(book, ENTITYREFERENCE);
+	qsf_node_foreach(qsf_root, qsf_book_node_handler, &iter, params);
+	object_list = g_list_copy(params->qsf_object_list);
+	while(object_list != NULL)
+	{
+		params->object_set = object_list->data;
+		params->qsf_parameter_hash = params->object_set->parameters;
+		inst = (QofInstance*)qof_object_new_instance(params->object_set->object_type, book);
+		g_return_val_if_fail(inst != NULL, FALSE);
+		params->qsf_ent = &inst->entity;
+		qof_begin_edit(inst);
+		g_hash_table_foreach(params->qsf_parameter_hash, qsf_object_commitCB, params);
+		qof_commit_edit(inst);
+		object_list = g_list_next(object_list);
+	}
+	qof_object_foreach_type(insert_ref_cb, params);
+	qof_book_set_data(book, ENTITYREFERENCE, params->referenceList);
+	return TRUE;
+}
+
+static void
+qsf_object_sequence(QofParam *qof_param, gpointer data)
+{
+	qsf_param *params;
+	GSList *checklist, *result;
+
+	g_return_if_fail(data != NULL);
+	params = (qsf_param*) data;
+	result = NULL;
+	checklist = NULL;
+	params->knowntype = FALSE;
+	checklist = g_slist_copy(params->supported_types);
+	for(result = checklist; result != NULL; result = result->next)
+	{
+		if(0 == safe_strcmp((QofIdType)result->data, qof_param->param_type))
+		{
+			params->knowntype = TRUE;
+		}
+	}
+	g_slist_free(checklist);
+	if(0 == safe_strcmp(qof_param->param_type, params->qof_type))
+	{
+		params->qsf_sequence = g_slist_append(params->qsf_sequence, qof_param);
+		params->knowntype = TRUE;
+	}
+	/* handle params->qof_type = QOF_TYPE_GUID and qof_param->param_type != known type */
+	if(0 == safe_strcmp(params->qof_type, QOF_TYPE_GUID)
+		&& (params->knowntype == FALSE))
+	{
+		params->qsf_sequence = g_slist_append(params->qsf_sequence, qof_param);
+		params->knowntype = TRUE;
+	}
+}
+
+/* receives each entry from supported_types in sequence
+	type = qof data type from supported list
+	user_data = params. Holds object type
+*/
+static void
+qsf_supported_parameters(gpointer type, gpointer user_data)
+{
+	qsf_param *params;
+
+	g_return_if_fail(user_data != NULL);
+	params = (qsf_param*) user_data;
+	params->qof_type = (QofIdType)type;
+	params->knowntype = FALSE;
+	qof_class_param_foreach(params->qof_obj_type, qsf_object_sequence, params);
+}
+
+static KvpValueType
+qsf_to_kvp_helper(const char *type_string)
+{
+	if(0 == safe_strcmp(QOF_TYPE_STRING, type_string))  { return KVP_TYPE_STRING; }
+	if(0 == safe_strcmp(QOF_TYPE_GUID, type_string))    { return KVP_TYPE_GUID; }
+	if(0 == safe_strcmp(QOF_TYPE_INT64, type_string))   { return KVP_TYPE_GINT64; }
+	if(0 == safe_strcmp(QOF_TYPE_DOUBLE, type_string))  { return KVP_TYPE_DOUBLE; }
+	if(0 == safe_strcmp(QOF_TYPE_NUMERIC, type_string)) { return KVP_TYPE_NUMERIC; }
+	if(0 == safe_strcmp(QSF_TYPE_BINARY, type_string))  { return KVP_TYPE_BINARY; }
+	if(0 == safe_strcmp(QSF_TYPE_GLIST, type_string))   { return KVP_TYPE_GLIST; }
+	if(0 == safe_strcmp(QSF_TYPE_FRAME, type_string))   { return KVP_TYPE_FRAME; }
+	return 0;
+}
+
+static void
+qsf_from_kvp_helper(const char *path, KvpValue *content, gpointer data)
+{
+	qsf_param *params;
+	QofParam *qof_param;
+	xmlNodePtr node;
+
+	params = (qsf_param*)data;
+	qof_param = params->qof_param;
+	g_return_if_fail(params && path && content);
+	ENTER (" path=%s", path);
+	switch(kvp_value_get_type(content))
+	{
+		case KVP_TYPE_STRING:
+		{
+			node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+				BAD_CAST qof_param->param_type));
+			xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QOF_TYPE_STRING);
+			break;
+		}
+		case KVP_TYPE_GUID:
+		{
+			node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+				BAD_CAST qof_param->param_type));
+			xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QOF_TYPE_GUID);
+			break;
+		}
+		case KVP_TYPE_BINARY:
+		{
+			node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+				BAD_CAST qof_param->param_type));
+			xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QSF_TYPE_BINARY);
+			break;
+		}
+		case KVP_TYPE_GLIST:
+		{
+			node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+				BAD_CAST qof_param->param_type));
+			xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QSF_TYPE_GLIST);
+			break;
+		}
+		case KVP_TYPE_FRAME:
+		{
+			node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+				BAD_CAST qof_param->param_type));
+			xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QSF_TYPE_FRAME);
+			break;
+		}
+		case KVP_TYPE_GINT64:
+		{
+			node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+				BAD_CAST qof_param->param_type));
+			xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QOF_TYPE_INT64);
+			break;
+		}
+		case KVP_TYPE_DOUBLE:
+		{
+			node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+				BAD_CAST qof_param->param_type));
+			xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QOF_TYPE_DOUBLE);
+			break;
+		}
+		case KVP_TYPE_NUMERIC:
+		{
+			node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+				BAD_CAST qof_param->param_type));
+			xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QOF_TYPE_NUMERIC);
+			break;
+		}
+		default:
+		{ break; }
+	}
+	LEAVE (" ");
+}
+
+static void
+qsf_from_coll_cb (QofEntity *ent, gpointer user_data)
+{
+	qsf_param *params;
+	QofParam *qof_param;
+	xmlNodePtr node;
+	gchar qsf_guid[GUID_ENCODING_LENGTH + 1];
+
+	params = (qsf_param*)user_data;
+	if(!ent || !params) { return; }
+	qof_param = params->qof_param;
+	node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+		BAD_CAST qof_param->param_type));
+	guid_to_string_buff(qof_entity_get_guid(ent), qsf_guid);
+	xmlNodeAddContent(node, BAD_CAST qsf_guid);
+	xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+}
+
+/******* reference handling ***********/
+
+static gint
+qof_reference_list_cb(gconstpointer a, gconstpointer b)
+{
+	const QofEntityReference *aa;
+	const QofEntityReference *bb;
+
+	aa = (QofEntityReference*) a;
+	bb = (QofEntityReference*) b;
+	if(aa == NULL) { return 1; }
+	g_return_val_if_fail((bb != NULL), 1);
+	g_return_val_if_fail((aa->type != NULL), 1);
+	if((0 == guid_compare(bb->ent_guid, aa->ent_guid))
+		&&(0 == safe_strcmp(bb->type, aa->type))
+		&&(0 == safe_strcmp(bb->param->param_name, aa->param->param_name)))
+	{
+		return 0;
+	}
+	return 1;
+}
+
+static QofEntityReference*
+qof_reference_lookup(GList *referenceList, QofEntityReference *find)
+{
+	GList *single_ref;
+	QofEntityReference *ent_ref;
+
+	if(referenceList == NULL) { return NULL; }
+	g_return_val_if_fail(find != NULL, NULL);
+	single_ref = NULL;
+	ent_ref = NULL;
+	single_ref = g_list_find_custom(referenceList, find, qof_reference_list_cb);
+	if(single_ref == NULL) { return ent_ref; }
+	ent_ref = (QofEntityReference*)single_ref->data;
+	g_list_free(single_ref);
+	return ent_ref;
+}
+
+static void
+reference_list_lookup(gpointer data, gpointer user_data)
+{
+	QofEntity *ent;
+	QofParam *ref_param;
+	QofEntityReference *reference, *starter;
+	qsf_param  *params;
+	xmlNodePtr node, object_node;
+	xmlNsPtr ns;
+	GList *copy_list;
+	gchar qsf_guid[GUID_ENCODING_LENGTH + 1], *ref_name;
+
+	params = (qsf_param*)user_data;
+	ref_param = (QofParam*)data;
+	object_node = params->output_node;
+	ent = params->qsf_ent;
+	ns = params->qsf_ns;
+	starter = g_new(QofEntityReference, 1);
+	starter->ent_guid = qof_entity_get_guid(ent);
+	starter->type = g_strdup(ent->e_type);
+	starter->param = ref_param;
+	starter->ref_guid = NULL;
+	copy_list = g_list_copy(params->referenceList);
+	reference = qof_reference_lookup(copy_list, starter);
+	g_free(starter);
+	if(reference != NULL) {
+		if((ref_param->param_getfcn == NULL)||(ref_param->param_setfcn == NULL))
+		{
+			return;
+		}
+		ref_name = g_strdup(reference->param->param_name);
+		node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST QOF_TYPE_GUID));
+		guid_to_string_buff(reference->ref_guid, qsf_guid);
+		xmlNodeAddContent(node, BAD_CAST qsf_guid);
+		xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST ref_name);
+		g_free(ref_name);
+	}
+}
+
+/*=====================================
+	Convert QofEntity to QSF XML node
+qof_param holds the parameter sequence.
+=======================================*/
+static void
+qsf_entity_foreach(QofEntity *ent, gpointer data)
+{
+	qsf_param  *params;
+	GSList     *param_list, *supported;
+	GList      *ref;
+	xmlNodePtr node, object_node;
+	xmlNsPtr   ns;
+	gchar      *string_buffer;
+	GString    *buffer;
+	QofParam   *qof_param;
+	QofEntity  *choice_ent;
+	KvpFrame   *qsf_kvp;
+	QofCollection *qsf_coll;
+	int        param_count;
+	gboolean   own_guid;
+	const GUID *cm_guid;
+	char       cm_sa[GUID_ENCODING_LENGTH + 1];
+
+	g_return_if_fail(data != NULL);
+	params = (qsf_param*)data;
+	param_count = ++params->count;
+	ns = params->qsf_ns;
+	qsf_kvp = kvp_frame_new();
+	own_guid = FALSE;
+	choice_ent = NULL;
+	object_node = xmlNewChild(params->book_node, params->qsf_ns,
+		BAD_CAST QSF_OBJECT_TAG, NULL);
+	xmlNewProp(object_node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST ent->e_type);
+	buffer = g_string_new(" ");
+	g_string_printf(buffer, "%i", param_count);
+	xmlNewProp(object_node, BAD_CAST QSF_OBJECT_COUNT, BAD_CAST buffer->str);
+	param_list = g_slist_copy(params->qsf_sequence);
+	while(param_list != NULL) {
+		qof_param = (QofParam*)param_list->data;
+		g_return_if_fail(qof_param != NULL);
+		if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_GUID))
+		{
+			if(!own_guid)
+			{
+				cm_guid = qof_entity_get_guid(ent);
+				node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST QOF_TYPE_GUID));
+				guid_to_string_buff(cm_guid, cm_sa);
+				string_buffer = g_strdup(cm_sa);
+				xmlNodeAddContent(node, BAD_CAST string_buffer);
+				xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE , BAD_CAST QOF_PARAM_GUID);
+				own_guid = TRUE;
+			}
+			params->qsf_ent = ent;
+			params->output_node = object_node;
+			ref = qof_class_get_referenceList(ent->e_type);
+			if(ref != NULL) {
+				g_list_foreach(ref, reference_list_lookup, params);
+			}
+		}
+		if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_COLLECT))
+		{
+			qsf_coll = qof_param->param_getfcn(ent, qof_param);
+			params->qof_param = qof_param;
+			params->output_node = object_node;
+			qof_collection_foreach(qsf_coll, qsf_from_coll_cb, params);
+		}
+		if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_CHOICE))
+		{
+			/** \todo use the reference list here. */
+			/* repeats due to use of reference list for GUID and CHOICE */
+			/* fix to allow COLLECT too */
+			choice_ent = (QofEntity*)qof_param->param_getfcn(ent, qof_param);
+			if(!choice_ent) {
+				param_list = g_slist_next(param_list);
+				continue;
+			}
+			node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST qof_param->param_type));
+			cm_guid = qof_entity_get_guid(choice_ent);
+			guid_to_string_buff(cm_guid, cm_sa);
+			string_buffer = g_strdup(cm_sa);
+			xmlNodeAddContent(node, BAD_CAST string_buffer);
+			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+			xmlNewProp(node, BAD_CAST "name", BAD_CAST choice_ent->e_type);
+			param_list = g_slist_next(param_list);
+			continue;
+		}
+		if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_KVP))
+		{
+			qsf_kvp = kvp_frame_copy(qof_param->param_getfcn(ent,qof_param));
+			params->qof_param = qof_param;
+			params->output_node = object_node;
+			kvp_frame_for_each_slot(qsf_kvp, qsf_from_kvp_helper, params);
+		}
+		if((qof_param->param_setfcn != NULL) && (qof_param->param_getfcn != NULL))
+		{
+			for( supported = g_slist_copy(params->supported_types); 
+				supported != NULL; supported = g_slist_next(supported))
+			{
+				if(0 == safe_strcmp((const char*)supported->data, (const char*)qof_param->param_type))
+				{
+					node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST qof_param->param_type));
+					string_buffer = g_strdup(qof_book_merge_param_as_string(qof_param, ent));
+					xmlNodeAddContent(node, BAD_CAST string_buffer);
+					xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+				}
+			}
+		}
+		param_list = g_slist_next(param_list);
+	}
+}
+
+static void
+qsf_foreach_obj_type(QofObject *qsf_obj, gpointer data)
+{
+	qsf_param *params;
+	QofBook *book;
+	GSList *support;
+	
+	g_return_if_fail(data != NULL);
+	params = (qsf_param*) data;
+	/* Skip unsupported objects */
+	if((qsf_obj->create == NULL)||(qsf_obj->foreach == NULL)){
+		PINFO (" qsf_obj QOF support failed %s", qsf_obj->e_type);
+		return;
+	}
+	params->qof_obj_type = qsf_obj->e_type;
+	params->qsf_sequence = NULL;
+	book = params->book;
+	support = g_slist_copy(params->supported_types);
+	g_slist_foreach(support,qsf_supported_parameters, params);
+	qof_object_foreach(qsf_obj->e_type, book, qsf_entity_foreach, params);
+}
+
+/*=====================================================
+	Take a QofBook and prepare a QSF XML doc in memory
+=======================================================*/
+/*	QSF only uses one QofBook per file - count may be removed later. */
+static xmlDocPtr
+qofbook_to_qsf(QofBook *book, qsf_param *params)
+{
+	xmlNodePtr top_node, node;
+	xmlDocPtr doc;
+	gchar buffer[GUID_ENCODING_LENGTH + 1];
+	const GUID *book_guid;
+	
+	g_return_val_if_fail(book != NULL, NULL);
+	params->book = book;
+	params->referenceList = g_list_copy((GList*)qof_book_get_data(book, ENTITYREFERENCE));
+	doc = xmlNewDoc(BAD_CAST QSF_XML_VERSION);
+	top_node = xmlNewNode(NULL, BAD_CAST QSF_ROOT_TAG);
+	xmlDocSetRootElement(doc, top_node);
+	xmlSetNs(top_node, xmlNewNs(top_node, BAD_CAST QSF_DEFAULT_NS, NULL));
+	params->qsf_ns = top_node->ns;
+	node = xmlNewChild(top_node, params->qsf_ns, BAD_CAST QSF_BOOK_TAG, NULL);
+	params->book_node = node;
+	xmlNewProp(node, BAD_CAST QSF_BOOK_COUNT, BAD_CAST "1");
+	book_guid = qof_book_get_guid(book);
+	guid_to_string_buff(book_guid, buffer);
+	xmlNewChild(params->book_node, params->qsf_ns, BAD_CAST QSF_BOOK_GUID, BAD_CAST buffer);
+	params->output_doc = doc;
+	params->book_node = node;
+	qof_object_foreach_type(qsf_foreach_obj_type, params);
+	return params->output_doc;
+}
+
+static void
+write_qsf_from_book(const char *path, QofBook *book, qsf_param *params)
+{
+	xmlDocPtr qsf_doc;
+	gint write_result;
+	QofBackend *be;
+
+	be = qof_book_get_backend(book);
+	qsf_doc = qofbook_to_qsf(book, params);
+	write_result = 0;
+	if((use_gz_level > 0) && (use_gz_level <= 9)) 
+	{
+		xmlSetDocCompressMode(qsf_doc, use_gz_level); 
+	}
+	g_return_if_fail(qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, qsf_doc) == TRUE);
+	write_result = xmlSaveFormatFileEnc(path, qsf_doc, "UTF-8", 1);
+	if(write_result < 0) 
+	{
+		qof_backend_set_error(be, ERR_FILEIO_WRITE_ERROR);
+		return;
+	}
+	xmlFreeDoc(qsf_doc);
+}
+
+static void
+write_qsf_to_stdout(QofBook *book, qsf_param *params)
+{
+	xmlDocPtr qsf_doc;
+
+	qsf_doc = qofbook_to_qsf(book, params);
+	g_return_if_fail(qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, qsf_doc) == TRUE);
+	xmlSaveFormatFileEnc("-", qsf_doc, "UTF-8", 1);
+	fprintf(stdout, "\n");
+	xmlFreeDoc(qsf_doc);
+	LEAVE (" ");
+}
+
+void
+qsf_write_file(QofBackend *be, QofBook *book)
+{
+	QSFBackend *qsf_be;
+	qsf_param *params;
+	char *path;
+
+	qsf_be = (QSFBackend*)be;
+	params = qsf_be->params;
+	/* if fullpath is blank, book_id was set to QOF_STDOUT */
+	if (!qsf_be->fullpath || (*qsf_be->fullpath == '\0')) {
+		write_qsf_to_stdout(book, params);
+		return;
+	}
+	path = strdup(qsf_be->fullpath);
+	write_qsf_from_book(path, book, params);
+	g_free(path);
+}
+
+/* QofBackend routine to load from file - needs a map.
+*/
+gboolean
+load_qsf_object(QofBook *book, const char *fullpath, qsf_param *params)
+{
+	xmlNodePtr qsf_root, map_root;
+	xmlDocPtr mapDoc, foreign_doc;
+	gchar *map_path, *map_file;
+
+	map_file = g_strdup("pilot-qsf-GnuCashInvoice.xml");
+	foreign_doc = xmlParseFile(fullpath);
+	if (foreign_doc == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	qsf_root = NULL;
+	qsf_root = xmlDocGetRootElement(foreign_doc);
+	params->qsf_ns = qsf_root->ns;
+	params->book = book;
+	map_path = g_strdup_printf("%s/%s", QSF_SCHEMA_DIR, map_file);
+	if(map_path == NULL) { return FALSE; }
+	mapDoc = xmlParseFile(map_path);
+	map_root = xmlDocGetRootElement(mapDoc);
+	params->map_ns = map_root->ns;
+	params->input_doc = qsf_object_convert(mapDoc, qsf_root, params);
+	qsfdoc_to_qofbook(params->input_doc, params);
+	return TRUE;
+}
+
+gboolean
+load_our_qsf_object(QofBook *book, const char *fullpath, qsf_param *params)
+{
+	xmlNodePtr qsf_root;
+	
+	params->input_doc = xmlParseFile(fullpath);
+	if (params->input_doc == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	qsf_root = NULL;
+	qsf_root = xmlDocGetRootElement(params->input_doc);
+	params->qsf_ns = qsf_root->ns;
+	return qsfdoc_to_qofbook(params->input_doc, params);
+}
+
+KvpValue*
+string_to_kvp_value(const char *content, KvpValueType type)
+{
+	char        *tail;
+	gint64      cm_i64;
+	double      cm_double;
+	gnc_numeric cm_numeric;
+	GUID        *cm_guid;
+	struct tm   kvp_time;
+	time_t      kvp_time_t;
+	Timespec    cm_date;
+	
+	switch(type) {
+	  case KVP_TYPE_GINT64:
+		errno = 0;
+		cm_i64 = strtoll(content, &tail, 0);
+		if(errno == 0) {
+			return kvp_value_new_gint64(cm_i64);
+		}
+		break;
+	  case KVP_TYPE_DOUBLE:
+  		errno = 0;
+		cm_double = strtod(content, &tail);
+		if(errno == 0) {
+			return kvp_value_new_double(cm_double);
+		}
+		break;
+	  case KVP_TYPE_NUMERIC:
+		string_to_gnc_numeric(content, &cm_numeric);
+		return kvp_value_new_gnc_numeric(cm_numeric);
+		break;
+	  case KVP_TYPE_STRING:
+		return kvp_value_new_string(content);
+		break;
+	  case KVP_TYPE_GUID:
+		cm_guid = g_new(GUID, 1);
+		if(TRUE == string_to_guid(content, cm_guid))
+		{
+			return kvp_value_new_guid(cm_guid);
+		}
+		break;
+	  case KVP_TYPE_TIMESPEC:
+		strptime(content, QSF_XSD_TIME, &kvp_time);
+		kvp_time_t = mktime(&kvp_time);
+		timespecFromTime_t(&cm_date, kvp_time_t);
+		return kvp_value_new_timespec(cm_date);
+		break;
+	  case KVP_TYPE_BINARY:
+//		return kvp_value_new_binary(value->value.binary.data,
+//									value->value.binary.datasize);
+		break;
+	  case KVP_TYPE_GLIST:
+//		return kvp_value_new_glist(value->value.list);
+		break;
+	  case KVP_TYPE_FRAME:
+//		return kvp_value_new_frame(value->value.frame);
+		break;
+	}
+	return NULL;
+}
+
+/*======================================================
+	Commit XML data from file to QofEntity in a QofBook
+========================================================*/
+void
+qsf_object_commitCB(gpointer key, gpointer value, gpointer data)
+{
+	qsf_param          *params;
+	qsf_objects        *object_set;
+	xmlNodePtr         node;
+	QofEntityReference *reference;
+	QofEntity          *qsf_ent;
+	QofBook            *targetBook;
+	const char         *qof_type, *parameter_name, *timechk;
+	QofIdType          obj_type, reference_type;
+	struct tm          qsf_time;
+	time_t             qsf_time_t;
+	char               *tail;
+	/* cm_ prefix used for variables that hold the data to commit */
+	gnc_numeric    cm_numeric;
+	double         cm_double;
+	gboolean       cm_boolean;
+	gint32         cm_i32;
+	gint64         cm_i64;
+	Timespec       cm_date;
+	char           cm_char,    (*char_getter)  (xmlNodePtr);
+	GUID           *cm_guid;
+	KvpFrame       *cm_kvp;
+	KvpValue       *cm_value;
+	KvpValueType   cm_type;
+	QofSetterFunc  cm_setter;
+	const QofParam *cm_param;
+	void (*string_setter)    (QofEntity*, const char*);
+	void (*date_setter)      (QofEntity*, Timespec);
+	void (*numeric_setter)   (QofEntity*, gnc_numeric);
+	void (*double_setter)    (QofEntity*, double);
+	void (*boolean_setter)   (QofEntity*, gboolean);
+	void (*i32_setter)       (QofEntity*, gint32);
+	void (*i64_setter)       (QofEntity*, gint64);
+	void (*char_setter)      (QofEntity*, char);
+	void (*kvp_frame_setter) (QofEntity*, KvpFrame*);
+
+	g_return_if_fail(data != NULL);
+	g_return_if_fail(value != NULL);
+	params = (qsf_param*)data;
+	node = (xmlNodePtr)value;
+	parameter_name = (const char*)key;
+	qof_type = (char*)node->name;
+	qsf_ent = params->qsf_ent;
+	targetBook = params->book;
+	memset (&qsf_time, '\0', sizeof(qsf_time));
+	cm_date.tv_nsec = 0;
+	cm_date.tv_sec = 0;
+	obj_type = (char*)xmlGetProp(node->parent, BAD_CAST QSF_OBJECT_TYPE);
+	if(0 == safe_strcasecmp(obj_type, parameter_name)) { return; }
+	cm_setter = qof_class_get_parameter_setter(obj_type, parameter_name);
+	cm_param = qof_class_get_parameter(obj_type, parameter_name);
+	object_set = params->object_set;
+	if(safe_strcmp(qof_type, QOF_TYPE_STRING) == 0)  { 
+		string_setter = (void(*)(QofEntity*, const char*))cm_setter;
+		if(string_setter != NULL) { string_setter(qsf_ent, (char*)xmlNodeGetContent(node)); }
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_DATE) == 0) { 
+		date_setter = (void(*)(QofEntity*, Timespec))cm_setter;
+		timechk = NULL;
+		timechk = strptime((char*)xmlNodeGetContent(node), QSF_XSD_TIME, &qsf_time);
+		g_return_if_fail(timechk != NULL);
+		qsf_time_t = mktime(&qsf_time);
+		timespecFromTime_t(&cm_date, qsf_time_t);
+		if(date_setter != NULL) { date_setter(qsf_ent, cm_date); }
+	}
+	if((safe_strcmp(qof_type, QOF_TYPE_NUMERIC) == 0)  ||
+	(safe_strcmp(qof_type, QOF_TYPE_DEBCRED) == 0)) { 
+		numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_setter;
+		string_to_gnc_numeric((char*)xmlNodeGetContent(node), &cm_numeric);
+		if(numeric_setter != NULL) { numeric_setter(qsf_ent, cm_numeric); }
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_GUID) == 0) { 
+		cm_guid = g_new(GUID, 1);
+		if(TRUE != string_to_guid((char*)xmlNodeGetContent(node), cm_guid))
+		{
+			qof_backend_set_error(params->be, ERR_QSF_BAD_OBJ_GUID);
+			PINFO (" string to guid conversion failed for %s:%s:%s",
+				xmlNodeGetContent(node), obj_type, qof_type);
+			return;
+		}
+		reference_type = (char*)xmlGetProp(node, BAD_CAST QSF_OBJECT_TYPE);
+		if(0 == safe_strcmp(QOF_PARAM_GUID, reference_type)) 
+		{
+			qof_entity_set_guid(qsf_ent, cm_guid);
+		}
+		else {
+			reference = qof_entity_get_reference_from(qsf_ent, cm_param);
+			if(reference) {
+				params->referenceList = g_list_append(params->referenceList, reference);
+			}
+		}
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_INT32) == 0) { 
+		errno = 0;
+		cm_i32 = (gint32)strtol ((char*)xmlNodeGetContent(node), &tail, 0);
+		if(errno == 0) {
+			i32_setter = (void(*)(QofEntity*, gint32))cm_setter;
+			if(i32_setter != NULL) { i32_setter(qsf_ent, cm_i32); }
+		}
+		else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_INT64) == 0) { 
+		errno = 0;
+		cm_i64 = strtoll((char*)xmlNodeGetContent(node), &tail, 0);
+		if(errno == 0) {
+			i64_setter = (void(*)(QofEntity*, gint64))cm_setter;
+			if(i64_setter != NULL) { i64_setter(qsf_ent, cm_i64); }
+		}
+		else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_DOUBLE) == 0) { 
+		errno = 0;
+		cm_double = strtod((char*)xmlNodeGetContent(node), &tail);
+		if(errno == 0) {
+			double_setter = (void(*)(QofEntity*, double))cm_setter;
+			if(double_setter != NULL) { double_setter(qsf_ent, cm_double); }
+		}
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_BOOLEAN) == 0){ 
+		if(0 == safe_strcasecmp((char*)xmlNodeGetContent(node), QSF_XML_BOOLEAN_TEST)) {
+			cm_boolean = TRUE;
+		}
+		else { cm_boolean = FALSE; }
+		boolean_setter = (void(*)(QofEntity*, gboolean))cm_setter;
+		if(boolean_setter != NULL) { boolean_setter(qsf_ent, cm_boolean); }
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_KVP) == 0) { 
+		cm_type = qsf_to_kvp_helper((char*)xmlGetProp(node, BAD_CAST QSF_OBJECT_VALUE));
+		if(!cm_type) { return; }
+		cm_value = string_to_kvp_value((char*)xmlNodeGetContent(node), cm_type);
+		cm_kvp = kvp_frame_copy(cm_param->param_getfcn(qsf_ent, cm_param));
+		cm_kvp = kvp_frame_set_value(cm_kvp, (char*)xmlGetProp(node, BAD_CAST QSF_OBJECT_KVP), cm_value);
+		kvp_frame_setter = (void(*)(QofEntity*, KvpFrame*))cm_setter;
+		if(kvp_frame_setter != NULL) { kvp_frame_setter(qsf_ent, cm_kvp); }
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_COLLECT) == 0) {
+		QofCollection *qsf_coll;
+		QofIdType type;
+		QofEntityReference *reference;
+		QofParam *copy_param;
+		/* retrieve the *type* of the collection, ignore any contents. */
+		qsf_coll = cm_param->param_getfcn(qsf_ent, cm_param);
+		type = qof_collection_get_type(qsf_coll);
+		cm_guid = g_new(GUID, 1);
+		if(TRUE != string_to_guid((char*)xmlNodeGetContent(node), cm_guid))
+		{
+			qof_backend_set_error(params->be, ERR_QSF_BAD_OBJ_GUID);
+			PINFO (" string to guid collect failed for %s", xmlNodeGetContent(node));
+			return;
+		}
+		// create a QofEntityReference with this type and GUID.
+		// there is only one entity each time.
+		// cm_guid contains the GUID of the reference.
+		// type is the type of the reference.
+		reference = g_new0(QofEntityReference, 1);
+		reference->type = g_strdup(qsf_ent->e_type);
+		reference->ref_guid = cm_guid;
+		reference->ent_guid = &qsf_ent->guid;
+		copy_param = g_new0(QofParam, 1);
+		copy_param->param_name = g_strdup(cm_param->param_name);
+		copy_param->param_type = g_strdup(cm_param->param_type);
+		reference->param = copy_param;
+		params->referenceList = g_list_append(params->referenceList, reference);
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_CHAR) == 0) { 
+		char_getter = (char (*)(xmlNodePtr))xmlNodeGetContent;
+		cm_char = char_getter(node);
+		char_setter = (void(*)(QofEntity*, char))cm_setter;
+		if(char_setter != NULL) { char_setter(qsf_ent, cm_char); }
+	}
+}
+
+QofBackend*
+qsf_backend_new(void)
+{
+	QSFBackend *qsf_be;
+	QofBackend *be;
+	
+	qsf_be = g_new0(QSFBackend, 1);
+	be = (QofBackend*) qsf_be;
+	qof_backend_init(be);
+	qsf_be->params = g_new(qsf_param, 1);
+	qsf_be->params->be = be;
+	qsf_param_init(qsf_be->params);
+	qsf_be->be.session_begin = qsf_session_begin;
+
+	be->session_end = qsf_session_end;
+	be->destroy_backend = qsf_destroy_backend;
+	be->load = qsf_file_type;
+	be->save_may_clobber_data = NULL;
+	/* The QSF backend will always load and save the entire QSF XML file. */
+	be->begin = NULL;
+	be->commit = NULL;
+	be->rollback = NULL;
+	/* QSF uses the built-in SQL, not a dedicated SQL server. */
+	be->compile_query = NULL;
+	be->free_query = NULL;
+	be->run_query = NULL;
+	be->counter = NULL;
+	/* The QSF backend is not multi-user. */
+	be->events_pending = NULL;
+	be->process_events = NULL;
+	
+	be->sync = qsf_write_file;
+	/* use for maps, later. */
+	be->load_config = qsf_load_config;
+	be->get_config = qsf_get_config;
+
+	qsf_be->fullpath = NULL;
+	return be;
+}
+
+/** \brief The QOF method of loading each backend.
+
+QSF does not use a GnuCash module, it is loaded using the QOF
+method - QofBackendProvider.
+*/
+static void 
+qsf_provider_free (QofBackendProvider *prov)
+{
+	prov->provider_name = NULL;
+	prov->access_method = NULL;
+	g_free (prov);
+}
+
+/* although we call gettext here, none of the
+QofBackendProvider strings are translatable. */
+void
+qsf_provider_init(void)
+{
+	QofBackendProvider *prov;
+
+	#ifdef ENABLE_NLS
+	setlocale (LC_ALL, "");
+	bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+	textdomain (GETTEXT_PACKAGE);
+	#endif
+	prov = g_new0 (QofBackendProvider, 1);
+	prov->provider_name = "QSF Backend Version 0.1";
+	prov->access_method = "file";
+	prov->partial_book_supported = TRUE;
+	prov->backend_new = qsf_backend_new;
+	prov->check_data_type = qsf_determine_file_type;
+	prov->provider_free = qsf_provider_free;
+	qof_backend_register_provider (prov);
+}

Added: gnucash/trunk/lib/libqof/backend/file/qsf-dir.h.in
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-dir.h.in	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-dir.h.in	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,26 @@
+/***************************************************************************
+ *            qsf-xml.h.in
+ *
+ *  Mon Dec 20 20:27:48 2004
+ *  Copyright  2004  Neil Williams
+ *  linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define QSF_SCHEMA_DIR "@-QSF_SCHEMA_DIR-@"
+

Added: gnucash/trunk/lib/libqof/backend/file/qsf-map.xsd.xml
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-map.xsd.xml	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-map.xsd.xml	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,116 @@
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+targetNamespace="http://qof.sourceforge.net/" 
+xmlns:qsf-map="http://qof.sourceforge.net/">
+<xsd:annotation>
+    <xsd:documentation xml:lang="en">
+	Query Object Framework Serialization Format (QSF)
+	Copyright 2004 Neil Williams linux at codehelp.co.uk
+	QSF is part of QOF.
+	QOF is free software; you can redistribute it and/or modify it
+	under the terms of the GNU General Public License as published by the
+	Free Software Foundation; either version 2 of the License, or (at your
+	option) any later version.
+	This program is distributed in the hope that it will be useful, but
+	WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+	General Public License for more details.
+	You should have received a copy of the GNU General Public License along
+	with this program; if not, write to the Free Software Foundation, Inc., 59
+	Temple	Place, Suite 330, Boston, MA 02111-1307 USA
+    </xsd:documentation>
+  </xsd:annotation>
+<xsd:element name="qsf-map" qsf-map:type="map-type"/>
+<xsd:complexType name="map-type">
+ <xsd:sequence>
+   <xsd:element name="definition" qsf-map:type="qsfdefinition"/>
+   <xsd:element name="object" qsf-map:type="mapobject" minOccurs="1" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+<xsd:complexType name="qsfdefinition">
+ <xsd:sequence>
+   <xsd:element name="define" qsf-map:type="qsfdefine" minOccurs="2" maxOccurs="unbounded"/>
+   <xsd:element name="default" qsf-map:type="qsfdefault" minOccurs="0" maxOccurs="unbounded"/>
+   <xsd:element name="variable" qsf-map:type="qsfvariable" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="qof_version" type="xsd:positiveInteger"/>
+</xsd:complexType>
+<xsd:complexType name="qsfdefine">
+ <xsd:simpleContent>
+   <xsd:extension base="xsd:string">
+     <xsd:attribute name="e_type" type="xsd:string"/>
+   </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="qsfdefault">
+ <xsd:simpleContent>
+   <xsd:extension base="xsd:string">
+     <xsd:attribute name="name" type="xsd:string"/>
+     <xsd:attribute name="type" type="xsd:string"/>
+     <xsd:attribute name="value" type="xsd:string"/>
+   </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="qsfvariable">
+ <xsd:simpleContent>
+   <xsd:extension base="xsd:string">
+     <xsd:attribute name="name" type="xsd:string"/>
+     <xsd:attribute name="type" type="xsd:string"/>
+     <xsd:attribute name="value" type="xsd:string"/>
+   </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="mapobject">
+ <xsd:sequence>
+   <xsd:element name="calculate" qsf-map:type="qsfcalculate" minOccurs="1" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+</xsd:complexType>
+<xsd:complexType name="qsfcalculate">
+ <xsd:sequence>
+   <xsd:element name="set" qsf-map:type="qsf_set" minOccurs="0" maxOccurs="1"/>
+   <xsd:element name="if" qsf-map:type="qsf_if" minOccurs="0" maxOccurs="unbounded"/>
+   <xsd:element name="equals" qsf-map:type="qsf_equal" minOccurs="0" maxOccurs="unbounded"/>
+   <xsd:element name="else" qsf-map:type="qsf_else" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+</xsd:complexType>
+<xsd:complexType name="qsf_set">
+ <xsd:simpleContent>
+   <xsd:extension base="xsd:string">
+     <xsd:attribute name="option" type="xsd:string" use="optional"/>
+     <xsd:attribute name="format" type="xsd:string" use="optional"/>
+     <xsd:attribute name="object" type="xsd:string" use="optional"/>
+   </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="qsf_if">
+ <xsd:sequence>
+   <xsd:element name="set" qsf-map:type="qsf_set" minOccurs="0" maxOccurs="1"/>
+   <xsd:element name="equals" qsf-map:type="qsf_equal" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="boolean" type="xsd:string" use="optional"/>
+ <xsd:attribute name="type" type="xsd:string" use="optional"/>
+ <xsd:attribute name="value" type="xsd:string" use="optional"/>
+ <xsd:attribute name="string" type="xsd:string" use="optional"/>
+</xsd:complexType>
+<xsd:complexType name="qsf_else">
+ <xsd:sequence>
+   <xsd:element name="set" qsf-map:type="qsf_set" minOccurs="0" maxOccurs="1"/>
+   <xsd:element name="equals" qsf-map:type="qsf_equal" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+</xsd:complexType>
+<xsd:complexType name="qsf_equal">
+ <xsd:sequence>
+   <xsd:element name="set" qsf-map:type="qsf_set" minOccurs="0" maxOccurs="1"/>
+   <xsd:element name="equals" qsf-map:type="qsf_equal" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+</xsd:complexType>
+
+</xsd:schema>
+

Added: gnucash/trunk/lib/libqof/backend/file/qsf-object.xsd.xml
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-object.xsd.xml	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-object.xsd.xml	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,154 @@
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+targetNamespace="http://qof.sourceforge.net/"
+xmlns:qof-qsf="http://qof.sourceforge.net/"
+elementFormDefault="qualified"
+attributeFormDefault="unqualified" xml:lang="en-GB">
+ <xsd:annotation>
+    <xsd:documentation xml:lang="en">
+	Query Object Framework Serialization Format (QSF)
+	Copyright 2004-5 Neil Williams linux at codehelp.co.uk
+	QSF is part of QOF.
+	QOF is free software; you can redistribute it and/or modify it
+	under the terms of the GNU General Public License as published by the
+	Free Software Foundation; either version 2 of the License, or (at your
+	option) any later version.
+	This program is distributed in the hope that it will be useful, but
+	WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+	General Public License for more details.
+	You should have received a copy of the GNU General Public License along
+	with this program; if not, write to the Free Software Foundation, Inc., 59
+	Temple	Place, Suite 330, Boston, MA 02111-1307 USA
+    </xsd:documentation>
+  </xsd:annotation>
+<xsd:element name="qof-qsf" type="qof-qsf:qsftype"/>
+<xsd:complexType name="qsftype">
+ <xsd:sequence>
+   <xsd:element name="book" type="qof-qsf:qofbook" minOccurs="1" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+<xsd:complexType name="qofbook">
+ <xsd:sequence>
+   <xsd:element name="book-guid" type="xsd:string" minOccurs="1" maxOccurs="1"/>
+   <xsd:element name="object" type="qof-qsf:qsfobject" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="count" type="xsd:positiveInteger" use="optional"/>
+</xsd:complexType>
+<xsd:complexType name="qsfobject">
+ <xsd:sequence>
+   <xsd:element name="string" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:string">
+       <xsd:attribute name="type" type="xsd:string" use="required"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="guid" minOccurs="0" maxOccurs="unbounded">
+   <xsd:complexType>
+   <xsd:simpleContent>
+     <xsd:extension base="xsd:hexBinary">
+       <xsd:attribute name="type" type="xsd:string" use="required"/>
+     </xsd:extension>
+    </xsd:simpleContent>
+   </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="boolean" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:boolean">
+       <xsd:attribute name="type" type="xsd:string" use="required"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="numeric" minOccurs="0" maxOccurs="unbounded">
+   <xsd:complexType>
+    <xsd:simpleContent>
+     <xsd:extension base="xsd:string">
+      <xsd:attribute name="type" type="xsd:string" use="required"/>
+     </xsd:extension>
+    </xsd:simpleContent>
+   </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="date" minOccurs="0" maxOccurs="unbounded" >
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:dateTime">
+       <xsd:attribute name="type" type="xsd:string" use="required"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="gint32" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:int">
+       <xsd:attribute name="type" type="xsd:string" use="required"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="gint64" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:long">
+       <xsd:attribute name="type" type="xsd:string" use="required"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="double" minOccurs="0" maxOccurs="unbounded">
+    <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:double">
+       <xsd:attribute name="type" type="xsd:string" use="required"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+    </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="character" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:string">
+       <xsd:attribute name="type" type="xsd:string" use="required"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="kvp" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:string">
+       <xsd:attribute name="type" type="xsd:string" use="required"/>
+       <xsd:attribute name="path" type="xsd:string" use="required"/>
+       <xsd:attribute name="value" type="xsd:string" use="required"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="collection" minOccurs="0" maxOccurs="unbounded">
+  <xsd:complexType>
+  <xsd:simpleContent>
+   <xsd:extension base="xsd:hexBinary">
+     <xsd:attribute name="type" type="xsd:string" use="required"/>
+   </xsd:extension>
+  </xsd:simpleContent>
+  </xsd:complexType>
+  </xsd:element>
+  <xsd:element name="choice" minOccurs="0" maxOccurs="unbounded">
+  <xsd:complexType>
+  <xsd:simpleContent>
+   <xsd:extension base="xsd:hexBinary">
+     <xsd:attribute name="type" type="xsd:string" use="required"/>
+     <xsd:attribute name="name" type="xsd:string" use="required"/>
+   </xsd:extension>
+  </xsd:simpleContent>
+  </xsd:complexType>
+  </xsd:element>
+</xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ <xsd:attribute name="count" type="xsd:positiveInteger" use="optional"/>
+</xsd:complexType>
+</xsd:schema>

Added: gnucash/trunk/lib/libqof/backend/file/qsf-xml-map.c
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-xml-map.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-xml-map.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,711 @@
+/***************************************************************************
+ *            qsf-xml-map.c
+ *
+ *  Sat Jan  1 07:31:55 2005
+ *  Copyright  2005  Neil Williams
+ *  linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+
+#include <libxml/xmlversion.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlschemas.h>
+#include "qof-backend-qsf.h"
+#include "qsf-xml.h"
+#include "qsf-dir.h"
+
+static void
+qsf_date_default_handler(const char *default_name, GHashTable *qsf_default_hash,
+	xmlNodePtr parent_tag, xmlNodePtr import_node, xmlNsPtr ns)
+{
+	xmlNodePtr output_parent;
+	time_t *qsf_time;
+	char date_as_string[QSF_DATE_LENGTH];
+
+	output_parent = xmlAddChild(parent_tag, xmlNewNode(ns,
+		xmlGetProp(import_node, BAD_CAST QSF_OBJECT_TYPE)));
+	xmlNewProp(output_parent, BAD_CAST QSF_OBJECT_TYPE,
+		xmlGetProp(import_node, BAD_CAST MAP_VALUE_ATTR));
+	qsf_time = (time_t*)g_hash_table_lookup(qsf_default_hash, default_name);
+	strftime(date_as_string, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(qsf_time));
+	xmlNodeAddContent(output_parent, BAD_CAST date_as_string);
+}
+
+static void
+qsf_string_default_handler(const char *default_name, GHashTable *qsf_default_hash,
+	xmlNodePtr parent_tag, xmlNodePtr import_node, xmlNsPtr ns)
+{
+	xmlNodePtr node;
+	xmlChar *output;
+
+	node = xmlAddChild(parent_tag,
+		xmlNewNode(ns, xmlGetProp(import_node, BAD_CAST QSF_OBJECT_TYPE)));
+	xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE,
+		xmlGetProp(import_node, BAD_CAST MAP_VALUE_ATTR));
+	output = (xmlChar *)g_hash_table_lookup(qsf_default_hash, default_name);
+	xmlNodeAddContent(node, output);
+}
+
+void
+qsf_map_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid)
+{
+	xmlChar *qof_version, *match;
+	GString *buff;
+	xmlNodePtr child_node;
+
+	if (qsf_is_element(child, ns, MAP_DEFINITION_TAG)) {
+		qof_version = xmlGetProp(child, BAD_CAST MAP_QOF_VERSION);
+		buff = g_string_new(" ");
+		g_string_printf(buff, "%i", QSF_QOF_VERSION);
+		if(xmlStrcmp(qof_version, BAD_CAST buff->str) != 0)
+		{
+			valid->error_state = ERR_QSF_BAD_QOF_VERSION;
+			return;
+		}
+		for(child_node = child->children; child_node != NULL;
+			child_node = child_node->next)
+		{
+			if (qsf_is_element(child_node, ns, MAP_DEFINE_TAG)) {
+				g_hash_table_insert(valid->validation_table,
+					xmlGetProp(child_node, BAD_CAST MAP_E_TYPE),
+					xmlNodeGetContent(child_node));
+			}
+		}
+	}
+	if(qsf_is_element(child, ns, MAP_OBJECT_TAG)) {
+		match = NULL;
+		match = BAD_CAST  g_hash_table_lookup( valid->validation_table,
+			xmlGetProp(child, BAD_CAST MAP_TYPE_ATTR));
+		if(match) {
+			valid->map_calculated_count++;
+		}
+	}
+}
+
+gboolean is_qsf_object_with_map_be(char *map_file, qsf_param *params)
+{
+	xmlDocPtr doc, map_doc;
+	int valid_count;
+	struct qsf_node_iterate iter;
+	xmlNodePtr map_root, object_root;
+	xmlNsPtr map_ns;
+	qsf_validator valid;
+	char *path;
+	gchar *map_path;
+
+	g_return_val_if_fail((params != NULL),FALSE);
+	path = g_strdup(params->filepath);
+	map_path = g_strdup_printf("%s/%s", QSF_SCHEMA_DIR, map_file);
+	if(path == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+		return FALSE;
+	}
+	doc = xmlParseFile(path);
+	if(doc == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) {
+		qof_backend_set_error(params->be, ERR_QSF_INVALID_OBJ);
+		return FALSE;
+	}
+	object_root = xmlDocGetRootElement(doc);
+	if(map_path == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+		return FALSE;
+	}
+	valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+	map_doc = xmlParseFile(map_path);
+	if(map_doc == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, map_doc)) {
+		qof_backend_set_error(params->be, ERR_QSF_INVALID_MAP);
+		return FALSE;
+	}
+	map_root = xmlDocGetRootElement(map_doc);
+	valid.map_calculated_count = 0;
+	valid.valid_object_count = 0;
+	valid.qof_registered_count = 0;
+	valid.error_state = ERR_BACKEND_NO_ERR;
+	map_ns = map_root->ns;
+	iter.ns = object_root->ns;
+	qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+	iter.ns = map_ns;
+	qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+	if (valid.error_state != ERR_BACKEND_NO_ERR) {
+		qof_backend_set_error(params->be, valid.error_state);
+		g_hash_table_destroy(valid.validation_table);
+		return FALSE;
+	}
+	valid_count = 0 - g_hash_table_size(valid.validation_table);
+	valid_count += valid.map_calculated_count;
+	valid_count += valid.valid_object_count;
+	g_hash_table_destroy(valid.validation_table);
+	if(valid_count == 0) {
+		qof_backend_get_error(params->be);
+		return TRUE;
+	}
+	qof_backend_set_error(params->be, ERR_QSF_WRONG_MAP);
+	/* the object is OK, only the map is wrong. */
+	return TRUE;
+}
+
+gboolean is_qsf_object_with_map(const char *path, char *map_file)
+{
+	xmlDocPtr doc, map_doc;
+	int valid_count;
+	struct qsf_node_iterate iter;
+	xmlNodePtr map_root, object_root;
+	xmlNsPtr map_ns;
+	qsf_validator valid;
+	gchar *map_path;
+
+	map_path = g_strdup_printf("%s/%s", QSF_SCHEMA_DIR, map_file);
+	if(path == NULL) {
+		return FALSE;
+	}
+	doc = xmlParseFile(path);
+	if(doc == NULL) {
+		return FALSE;
+	}
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) {
+		return FALSE;
+	}
+	object_root = xmlDocGetRootElement(doc);
+	if(map_path == NULL) {
+		return FALSE;
+	}
+	valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+	map_doc = xmlParseFile(map_path);
+	if(map_doc == NULL) {
+		return FALSE;
+	}
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, map_doc)) {
+		return FALSE;
+	}
+	map_root = xmlDocGetRootElement(map_doc);
+	valid.map_calculated_count = 0;
+	valid.valid_object_count = 0;
+	valid.error_state = ERR_BACKEND_NO_ERR;
+	map_ns = map_root->ns;
+	iter.ns = map_ns;
+	qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+	iter.ns = object_root->ns;
+	qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+	if (valid.error_state != ERR_BACKEND_NO_ERR) {
+		g_hash_table_destroy(valid.validation_table);
+		return FALSE;
+	}
+	valid_count = 0 - g_hash_table_size(valid.validation_table);
+	valid_count += valid.map_calculated_count;
+	valid_count += valid.valid_object_count;
+	g_hash_table_destroy(valid.validation_table);
+	if(valid_count == 0) {
+		return TRUE;
+	}
+	return FALSE;
+}
+
+gboolean is_qsf_map_be(qsf_param *params)
+{
+	xmlDocPtr doc;
+	struct qsf_node_iterate iter;
+	qsf_validator valid;
+	xmlNodePtr map_root;
+	xmlNsPtr map_ns;
+	char *path;
+
+	g_return_val_if_fail((params != NULL),FALSE);
+	qof_backend_get_error(params->be);
+	path = g_strdup(params->filepath);
+	if(path == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+		return FALSE;
+	}
+	doc = xmlParseFile(path);
+	if(doc == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, doc)) {
+		qof_backend_set_error(params->be, ERR_QSF_INVALID_MAP);
+		return FALSE;
+	}
+	map_root = xmlDocGetRootElement(doc);
+	map_ns = map_root->ns;
+	iter.ns = map_ns;
+	valid.error_state = ERR_BACKEND_NO_ERR;
+	qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+	if (valid.error_state != ERR_BACKEND_NO_ERR) {
+		qof_backend_set_error(params->be, valid.error_state);
+		g_hash_table_destroy(valid.validation_table);
+		return FALSE;
+	}
+	qof_backend_get_error(params->be);
+	g_hash_table_destroy(valid.validation_table);
+	return TRUE;
+}
+
+gboolean is_qsf_map(const char *path)
+{
+	xmlDocPtr doc;
+	struct qsf_node_iterate iter;
+	qsf_validator valid;
+	xmlNodePtr map_root;
+	xmlNsPtr map_ns;
+
+	g_return_val_if_fail((path != NULL),FALSE);
+	if(path == NULL) { return FALSE; }
+	doc = xmlParseFile(path);
+	if(doc == NULL) { return FALSE; }
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, doc)) {
+		return FALSE;
+	}
+	map_root = xmlDocGetRootElement(doc);
+	map_ns = map_root->ns;
+	iter.ns = map_ns;
+	valid.error_state = ERR_BACKEND_NO_ERR;
+	qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+	if (valid.error_state != ERR_BACKEND_NO_ERR) {
+		g_hash_table_destroy(valid.validation_table);
+		return FALSE;
+	}
+	g_hash_table_destroy(valid.validation_table);
+	return TRUE;
+}
+
+
+static void
+qsf_map_default_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params )
+{
+	xmlChar *qsf_enum;
+
+	g_return_if_fail(params->qsf_define_hash != NULL);
+	if (qsf_is_element(child, ns, MAP_DEFINE_TAG)) {
+		if(NULL == g_hash_table_lookup(params->qsf_define_hash,
+			xmlGetProp(child, BAD_CAST MAP_E_TYPE)))
+		{
+			g_hash_table_insert(params->qsf_define_hash,
+				xmlGetProp(child, BAD_CAST MAP_E_TYPE), params->child_node);
+		}
+		else {
+			qof_backend_set_error(params->be, ERR_QSF_BAD_MAP);
+			return;
+		}
+	}
+	if(qsf_is_element(child, ns, MAP_DEFAULT_TAG)) {
+		if(qsf_strings_equal(xmlGetProp(child, BAD_CAST MAP_TYPE_ATTR), MAP_ENUM_TYPE))
+		{
+			qsf_enum = xmlNodeGetContent(child);
+			/** Use content to discriminate enums in QOF */
+			/** \todo FIXME: the default enum value is not used
+			implemented properly or fully handled.
+			*/
+			if(NULL == g_hash_table_lookup(params->qsf_default_hash,
+				xmlNodeGetContent(child)))
+			{
+				g_hash_table_insert(params->qsf_default_hash,
+					xmlNodeGetContent(child), child);
+			}
+			else
+			{
+				qof_backend_set_error(params->be, ERR_QSF_BAD_MAP);
+				return;
+			}
+		}
+		/** Non-enum defaults */
+		else {
+			if(NULL == g_hash_table_lookup(params->qsf_default_hash,
+					xmlGetProp(child, BAD_CAST MAP_NAME_ATTR)))
+			{
+				g_hash_table_insert(params->qsf_default_hash,
+					xmlGetProp(child, BAD_CAST MAP_NAME_ATTR), child);
+			}
+			else
+/*					if(0 != xmlHashAddEntry(params->default_map,
+				xmlGetProp(child_node, MAP_NAME_ATTR), child_node))*/
+			{
+				qof_backend_set_error(params->be, ERR_QSF_BAD_MAP);
+				return;
+			}
+		}
+	}
+}
+
+void
+qsf_map_top_node_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params)
+{
+	xmlChar	*qof_version;
+	GString *buff;
+	struct qsf_node_iterate iter;
+
+	if(!params->qsf_define_hash) return;
+	if(!params->qsf_default_hash) return;
+	if(qsf_is_element(child, ns, MAP_DEFINITION_TAG)) {
+		qof_version = xmlGetProp(child, BAD_CAST MAP_QOF_VERSION);
+		buff = g_string_new(" ");
+		g_string_printf(buff, "%i", QSF_QOF_VERSION);
+		if(xmlStrcmp(qof_version, BAD_CAST buff->str) != 0) {
+			qof_backend_set_error(params->be, ERR_QSF_BAD_QOF_VERSION);
+			return;
+		}
+		iter.ns = ns;
+		qsf_node_foreach(child, qsf_map_default_handler, &iter, params);
+	}
+}
+
+static char*
+qsf_else_set_value(xmlNodePtr parent, GHashTable *default_hash,
+		char *content, xmlNsPtr map_ns)
+{
+	xmlNodePtr cur_node;
+
+	content = NULL;
+	for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+	{
+		if(qsf_is_element(cur_node, map_ns, QSF_CONDITIONAL_SET)) {
+			content = (char*)xmlNodeGetContent(cur_node);
+			return content;
+		}
+	}
+	return NULL;
+}
+
+/* Handles the set tag in the map.
+This function will be overhauled once inside QOF
+QOF hook required for "Lookup in the receiving application"
+*/
+static char*
+qsf_set_handler(xmlNodePtr parent, GHashTable *default_hash,
+	char *content, qsf_param *params)
+{
+	xmlNodePtr cur_node, lookup_node;
+
+	content = NULL;
+	for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+	{
+		if(qsf_is_element(cur_node, params->map_ns, QSF_CONDITIONAL_SET))
+		{
+			content = (char*)xmlGetProp(cur_node, BAD_CAST QSF_OPTION);
+			if(qsf_strings_equal(xmlGetProp(cur_node, BAD_CAST QSF_OPTION), "qsf_lookup_string"))
+			{
+				lookup_node = (xmlNodePtr) g_hash_table_lookup(default_hash,
+					xmlNodeGetContent(cur_node));
+				content = (char*)xmlGetProp(lookup_node, BAD_CAST MAP_VALUE_ATTR);
+				/** \todo FIXME: do the lookup. type is defined by output object. */
+				/* Find by name, get GUID, return GUID as string. */
+				g_message("Lookup %s in the receiving application\n", content );
+				return content;
+			}
+			if(content)
+			{
+				lookup_node = (xmlNodePtr) g_hash_table_lookup(default_hash,
+					xmlNodeGetContent(cur_node));
+				content = (char*)xmlGetProp(lookup_node, BAD_CAST "value");
+				return content;
+			}
+			content = (char*)xmlGetProp(parent, BAD_CAST "boolean");
+			if(!content) {
+				/** \todo Check qsf_parameter_hash arguments */
+				lookup_node = (xmlNodePtr) g_hash_table_lookup(params->qsf_parameter_hash,
+					xmlGetProp(parent->parent, BAD_CAST MAP_TYPE_ATTR));
+				if(lookup_node) { return (char*)xmlNodeGetContent(lookup_node); }
+				return (char*)xmlNodeGetContent(cur_node);
+			}
+		}
+	}
+	return NULL;
+}
+
+static void
+qsf_calculate_else(xmlNodePtr param_node, xmlNodePtr child, qsf_param *params)
+{
+	xmlNodePtr export_node;
+	xmlChar *output_content, *object_data;
+
+	if(qsf_is_element(param_node, params->map_ns, QSF_CONDITIONAL_ELSE)) {
+		if(params->boolean_calculation_done == 0) {
+			output_content = object_data = NULL;
+			output_content = BAD_CAST qsf_set_handler(param_node,
+				params->qsf_default_hash, (char*)output_content, params);
+			if(output_content == NULL) {
+				output_content = xmlGetProp(param_node, BAD_CAST MAP_TYPE_ATTR);
+				object_data = BAD_CAST qsf_else_set_value(param_node, params->qsf_default_hash,
+					(char*)output_content, params->map_ns);
+				output_content = BAD_CAST xmlGetProp( (xmlNodePtr) g_hash_table_lookup(
+					params->qsf_default_hash, object_data), BAD_CAST MAP_VALUE_ATTR);
+			}
+			if(object_data != NULL) {
+				export_node =(xmlNodePtr) g_hash_table_lookup(
+					params->qsf_parameter_hash,
+					xmlGetProp(params->child_node, BAD_CAST QSF_OBJECT_TYPE));
+				object_data = xmlNodeGetContent(export_node);
+			}
+			if(output_content != NULL) { object_data = output_content; }
+			export_node = xmlAddChild(params->lister, xmlNewNode(params->qsf_ns,
+				xmlGetProp(child, BAD_CAST QSF_OBJECT_TYPE)));
+			xmlNewProp(export_node, BAD_CAST QSF_OBJECT_TYPE,
+				xmlGetProp(child, BAD_CAST MAP_VALUE_ATTR));
+			xmlNodeAddContent(export_node, object_data);
+			params->boolean_calculation_done = 1;
+		}
+	}
+}
+
+static void
+qsf_set_format_value(xmlChar *format, char *qsf_time_now_as_string,
+	xmlNodePtr cur_node, qsf_param *params)
+{
+	int result;
+	xmlChar *content;
+	time_t *output;
+	struct tm *tmp;
+	time_t tester;
+	xmlNodePtr kl;
+	regex_t reg;
+
+	/** Comments retained - this behaves a little strangely */
+
+	result = 0;
+	if(format == NULL) { return; }
+	content = xmlNodeGetContent(cur_node);
+	output = (time_t*) g_hash_table_lookup(params->qsf_default_hash, content);
+	if(!output) {
+		/** No default time set, use the object time */
+		/** fill the time structs with temp data */
+		tester = time(NULL);
+		tmp = gmtime(&tester);
+		/** Lookup the object value to read */
+		/** \todo qsf_parameter_hash check correct arguments */
+		kl = (xmlNodePtr) g_hash_table_lookup(params->qsf_parameter_hash, content);
+		if(!kl) {
+			printf("no suitable date set.\n");
+			return;
+		}
+		/** Read the object value as a dateTime  */
+		strptime((char*)xmlNodeGetContent(kl), QSF_XSD_TIME, tmp);
+		if(!tmp) {
+			printf("empty date field in QSF object.\n");
+			return;
+		}
+		tester = mktime(tmp);
+		output = &tester;
+	}
+	result = regcomp(&reg, "%[a-zA-Z]", REG_EXTENDED|REG_NOSUB);
+	result = regexec(&reg, (char*)format,(size_t)0,NULL,0);
+	if(result == REG_NOMATCH) { format = BAD_CAST "%F"; }
+	regfree(&reg);
+	/** QSF_DATE_LENGTH preset for all internal and QSF_XSD_TIME string formats.
+	 */
+	strftime(qsf_time_now_as_string, QSF_DATE_LENGTH, (char*)format, gmtime(output));
+}
+
+static void
+qsf_boolean_set_value(xmlNodePtr parent, qsf_param *params,
+		char *content, xmlNsPtr map_ns)
+{
+	xmlNodePtr cur_node;
+	xmlChar *boolean_name;
+
+	boolean_name = NULL;
+	for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next) {
+		if(qsf_is_element(cur_node, map_ns, QSF_CONDITIONAL_SET)) {
+			boolean_name = xmlGetProp(cur_node, BAD_CAST QSF_FORMATTING_OPTION);
+			qsf_set_format_value(boolean_name, content, cur_node, params);
+		}
+	}
+}
+
+static void
+qsf_calculate_conditional(xmlNodePtr param_node, xmlNodePtr child, qsf_param *params)
+{
+	xmlNodePtr export_node;
+	xmlChar *output_content;
+
+	output_content = NULL;
+	if(qsf_is_element(param_node, params->map_ns, QSF_CONDITIONAL)) {
+		printf("param_node=%s\n", param_node->name);
+		if(params->boolean_calculation_done == 0) {
+		/* set handler */
+		output_content = BAD_CAST qsf_set_handler(param_node, params->qsf_default_hash,
+				(char*)output_content, params);
+		/* If the 'if' contains a boolean that has a default value */
+		if(output_content == NULL) {
+			if(NULL != xmlGetProp(param_node, BAD_CAST QSF_BOOLEAN_DEFAULT)) {
+			output_content = xmlGetProp( (xmlNodePtr) g_hash_table_lookup(
+				params->qsf_default_hash, xmlGetProp(param_node,
+				BAD_CAST QSF_BOOLEAN_DEFAULT) ), BAD_CAST MAP_VALUE_ATTR);
+			}
+			/* Is the default set to true? */
+			if( 0 == qsf_compare_tag_strings(output_content, QSF_XML_BOOLEAN_TEST))
+			{
+				qsf_boolean_set_value(param_node, params, (char*)output_content, params->map_ns);
+				export_node = xmlAddChild(params->lister, xmlNewNode(params->qsf_ns,
+					xmlGetProp(child, BAD_CAST QSF_OBJECT_TYPE)));
+				xmlNewProp(export_node, BAD_CAST QSF_OBJECT_TYPE,
+					xmlGetProp(child, BAD_CAST MAP_VALUE_ATTR));
+				xmlNodeAddContent(export_node, output_content);
+				params->boolean_calculation_done = 1;
+				}
+			}
+		}
+	}
+}
+
+static xmlNodePtr
+qsf_add_object_tag(qsf_param *params, int count)
+{
+	xmlNodePtr extra_node;
+	GString *str;
+	xmlChar *property;
+
+	str = g_string_new (" ");
+	g_string_printf(str, "%i", count);
+	extra_node = NULL;
+	extra_node = xmlAddChild(params->output_node,
+		xmlNewNode(params->qsf_ns, BAD_CAST QSF_OBJECT_TAG));
+	xmlNewProp(extra_node, BAD_CAST QSF_OBJECT_TYPE,
+		xmlGetProp(params->cur_node, BAD_CAST QSF_OBJECT_TYPE));
+	property = xmlCharStrdup(str->str);
+	xmlNewProp(extra_node, BAD_CAST QSF_OBJECT_COUNT, property);
+	return extra_node;
+}
+
+void
+qsf_map_object_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params)
+{
+	xmlNodePtr param_node, export_node;
+	xmlNsPtr map_ns, qsf_ns;
+	xmlChar *output_content;
+	int result, count;
+
+	map_ns = ns;
+	qsf_ns = params->qsf_ns;
+	param_node = NULL;
+	result = 0;
+	count = params->count;
+	if(child == NULL) { return; }
+	if(ns == NULL) { return; }
+	params->boolean_calculation_done = 0;
+
+	if(qsf_is_element(child, map_ns, MAP_CALCULATE_TAG)) {
+		params->boolean_calculation_done = 0;
+		for(param_node = child->children; param_node != NULL;
+			param_node = param_node->next)
+		{
+			output_content = NULL;
+			export_node = NULL;
+			if(!params->lister) { params->lister = qsf_add_object_tag(params, count); }
+			if(qsf_is_element(param_node, map_ns, "set"))
+			{
+				/* Map the pre-defined defaults */
+				if(0 == qsf_compare_tag_strings(
+					xmlNodeGetContent(param_node), "qsf_enquiry_date"))
+				{
+					qsf_string_default_handler("qsf_enquiry_date",
+						params->qsf_default_hash, params->lister, child, qsf_ns);
+				}
+				if(0 == qsf_compare_tag_strings(
+					xmlNodeGetContent(param_node), "qsf_time_now"))
+				{
+					qsf_date_default_handler("qsf_time_now",
+						params->qsf_default_hash, params->lister, child, qsf_ns);
+				}
+				if(0 == qsf_compare_tag_strings(
+					xmlNodeGetContent(param_node), "qsf_time_string"))
+				{
+					qsf_string_default_handler("qsf_time_string",
+						params->qsf_default_hash, params->lister, child, qsf_ns);
+				}
+				output_content = xmlNodeGetContent(param_node);
+				if(output_content != NULL) {
+					output_content = xmlNodeGetContent((xmlNodePtr) g_hash_table_lookup(
+						params->qsf_parameter_hash, xmlGetProp(child, BAD_CAST MAP_TYPE_ATTR)));
+					if(output_content != NULL) {
+						export_node = xmlAddChild(params->lister, xmlNewNode(qsf_ns,
+							xmlGetProp(child, BAD_CAST QSF_OBJECT_TYPE)));
+						xmlNewProp(export_node, BAD_CAST QSF_OBJECT_TYPE,
+							xmlGetProp(child, BAD_CAST MAP_VALUE_ATTR));
+						xmlNodeAddContent(export_node, output_content);
+						xmlFree(output_content);
+					}
+				}
+			}
+
+			qsf_calculate_conditional( param_node, child, params);
+			qsf_calculate_else(param_node, child, params);
+		}
+
+		/* calculate_map currently not in use */
+		/* ensure uniqueness of the key before re-instating */
+/*		result = xmlHashAddEntry2(calculate_map,
+			xmlGetProp(child_node, MAP_TYPE_ATTR),
+			xmlGetProp(child_node, MAP_VALUE_ATTR), child_node);
+		if(result != 0) {
+			printf("add entry to calculate hash failed. %s\t%s\t%s.\n",
+				xmlGetProp(child_node, MAP_TYPE_ATTR),
+			xmlGetProp(child_node, MAP_VALUE_ATTR), child_node->name);
+			return;
+		}
+
+		is_qsf_object_with_map(path, map_path);
+*/
+	}
+}
+xmlDocPtr
+qsf_object_convert(xmlDocPtr mapDoc, xmlNodePtr qsf_root, qsf_param *params)
+{
+	struct qsf_node_iterate iter;
+	xmlDocPtr output_doc;
+	xmlNode *cur_node;
+	xmlNode *map_root, *output_root, *output_node;
+
+	output_doc = xmlNewDoc(BAD_CAST QSF_XML_VERSION);
+	output_root = xmlDocCopyNode(qsf_root,output_doc,2);
+	xmlSetNs(output_root, params->qsf_ns);
+	output_node = NULL;
+
+	qsf_node_foreach(qsf_root, qsf_object_node_handler, &iter, params);
+	map_root = xmlDocGetRootElement(mapDoc);
+
+	iter.ns = params->map_ns;
+	qsf_node_foreach(map_root, qsf_map_top_node_handler, &iter, params);
+
+//	iter.ns = qsf_ns;
+//	qsf_node_foreach(qsf_root, qsf_map_object_handler, &iter, params);
+	for(cur_node = map_root->children; cur_node != NULL; cur_node = cur_node->next)
+	{
+		params->cur_node = cur_node;
+		params->count = 0;
+		if(qsf_is_element(cur_node, params->map_ns, MAP_OBJECT_TAG))
+		{
+			params->lister = NULL;
+			params->count++;
+			iter.ns = params->map_ns;
+			qsf_node_foreach(cur_node, qsf_map_object_handler, &iter, params);
+		}
+	}
+	params->file_type = OUR_QSF_OBJ;
+	return output_doc;
+}

Added: gnucash/trunk/lib/libqof/backend/file/qsf-xml.c
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-xml.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-xml.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,349 @@
+/***************************************************************************
+ *            qsf-xml.c
+ *
+ *  Fri Nov 26 19:29:47 2004
+ *  Copyright  2004-2005  Neil Williams  <linux at codehelp.co.uk>
+ *
+ ****************************************************************************/
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+
+#include <libxml/xmlversion.h>
+#include "qof-backend-qsf.h"
+#include "qsf-dir.h"
+#include "qsf-xml.h"
+
+static QofLogModule log_module = QOF_MOD_QSF;
+
+void qsf_free_params(qsf_param *params)
+{
+	g_hash_table_destroy(params->qsf_calculate_hash);
+	g_hash_table_destroy(params->qsf_default_hash);
+	if(params->referenceList) {
+		g_list_free(params->referenceList);
+	}
+	g_slist_free(params->supported_types);
+	if(params->map_ns) { xmlFreeNs(params->map_ns); }
+}
+
+int
+qsf_compare_tag_strings(const xmlChar *node_name, char *tag_name)
+{
+	return xmlStrcmp(node_name, (const xmlChar *)tag_name);
+}
+
+int
+qsf_strings_equal(const xmlChar *node_name, char *tag_name)
+{
+	if(0 == qsf_compare_tag_strings(node_name, tag_name)) { return 1; }
+	return 0;
+}
+
+int
+qsf_is_element(xmlNodePtr a, xmlNsPtr ns, char *c)
+{
+	g_return_val_if_fail(a != NULL, 0);
+	g_return_val_if_fail(ns != NULL, 0);
+	g_return_val_if_fail(c != NULL, 0);
+	if ((a->ns == ns) && (a->type == XML_ELEMENT_NODE) &&
+		qsf_strings_equal(a->name, c)) { return 1; }
+	return 0;
+}
+
+int
+qsf_check_tag(qsf_param *params, char *qof_type)
+{
+	return qsf_is_element(params->child_node, params->qsf_ns, qof_type);
+}
+
+gboolean
+qsf_is_valid(const char *schema_dir, const char* schema_filename, xmlDocPtr doc)
+{
+	xmlSchemaParserCtxtPtr qsf_schema_file;
+	xmlSchemaPtr qsf_schema;
+	xmlSchemaValidCtxtPtr qsf_context;
+	gchar *schema_path;
+	gint result;
+
+	g_return_val_if_fail(doc || schema_filename, FALSE);
+	schema_path = g_strdup_printf("%s/%s", schema_dir, schema_filename);
+	qsf_schema_file = xmlSchemaNewParserCtxt(schema_path);
+	qsf_schema = xmlSchemaParse(qsf_schema_file);
+	qsf_context = xmlSchemaNewValidCtxt(qsf_schema);
+	result = xmlSchemaValidateDoc(qsf_context, doc);
+	xmlSchemaFreeParserCtxt(qsf_schema_file);
+	xmlSchemaFreeValidCtxt(qsf_context);
+	xmlSchemaFree(qsf_schema);
+	if(result == 0) { return TRUE; }
+	return FALSE;
+}
+
+void
+qsf_valid_foreach(xmlNodePtr parent, qsf_validCB cb,
+	struct qsf_node_iterate *iter, qsf_validator *valid)
+{
+	xmlNodePtr cur_node;
+
+	iter->v_fcn = &cb;
+	for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+	{
+		cb(cur_node, iter->ns, valid);
+	}
+}
+
+void
+qsf_node_foreach(xmlNodePtr parent, qsf_nodeCB cb,
+	struct qsf_node_iterate *iter, qsf_param *params)
+{
+	xmlNodePtr cur_node;
+
+	iter->fcn = &cb;
+	for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+	{
+		cb(cur_node, iter->ns, params);
+	}
+}
+
+void
+qsf_object_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid)
+{
+	xmlNodePtr cur_node;
+	xmlChar *object_declaration;
+	guint count;
+
+	count = 0;
+	for(cur_node = child->children; cur_node != NULL;
+		cur_node = cur_node->next)
+	{
+		if(qsf_is_element(cur_node, ns, QSF_OBJECT_TAG)) {
+			object_declaration = xmlGetProp(cur_node, BAD_CAST QSF_OBJECT_TYPE);
+			count = g_hash_table_size(valid->validation_table);
+			g_hash_table_insert(valid->validation_table, object_declaration, xmlNodeGetContent(cur_node));
+			if(g_hash_table_size(valid->validation_table) > count)
+			{
+				valid->valid_object_count++;
+			if(TRUE == qof_class_is_registered((QofIdTypeConst) object_declaration))
+			{
+				valid->qof_registered_count++;
+			}
+		}
+	}
+	}
+}
+
+gboolean is_our_qsf_object(const char *path)
+{
+	xmlDocPtr doc;
+	struct qsf_node_iterate iter;
+	xmlNodePtr object_root;
+	qsf_validator valid;
+	gint table_count;
+
+	g_return_val_if_fail((path != NULL),FALSE);
+	doc = xmlParseFile(path);
+	if(doc == NULL)  { return FALSE; }
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) { 
+		PINFO (" validation failed %s %s %s", QSF_SCHEMA_DIR, 
+			QSF_OBJECT_SCHEMA, path);
+		return FALSE; 
+	}
+	object_root = xmlDocGetRootElement(doc);
+	valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+	valid.qof_registered_count = 0;
+	valid.valid_object_count = 0;
+	iter.ns = object_root->ns;
+	qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+	table_count = g_hash_table_size(valid.validation_table);
+	g_hash_table_destroy(valid.validation_table);
+	if(table_count == valid.qof_registered_count) { return TRUE; }
+	return FALSE;
+}
+
+gboolean is_qsf_object(const char *path)
+{
+	xmlDocPtr doc;
+
+	g_return_val_if_fail((path != NULL),FALSE);
+	if(path == NULL) { return FALSE; }
+	doc = xmlParseFile(path);
+	if(doc == NULL) { return FALSE; }
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) { return FALSE; }
+	/** \todo implement a way of finding more than one map */
+	return is_qsf_object_with_map(path, "pilot-qsf-GnuCashInvoice.xml");
+}
+
+gboolean is_our_qsf_object_be(qsf_param *params)
+{
+	xmlDocPtr doc;
+	struct qsf_node_iterate iter;
+	xmlNodePtr object_root;
+	qsf_validator valid;
+	gint table_count;
+	char *path;
+
+	g_return_val_if_fail((params != NULL),FALSE);
+	path = g_strdup(params->filepath);
+	if(path == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+		return FALSE;
+	}
+	if(params->file_type != QSF_UNDEF) { return FALSE; }
+	doc = xmlParseFile(path);
+	if(doc == NULL)  {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc))
+	{
+		qof_backend_set_error(params->be, ERR_QSF_INVALID_OBJ);
+		return FALSE;
+	}
+	params->file_type = IS_QSF_OBJ;
+	object_root = xmlDocGetRootElement(doc);
+	valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+	valid.qof_registered_count = 0;
+	iter.ns = object_root->ns;
+	qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+	table_count = g_hash_table_size(valid.validation_table);
+	if(table_count == valid.qof_registered_count)
+	{
+		g_hash_table_destroy(valid.validation_table);
+		qof_backend_set_error(params->be, ERR_BACKEND_NO_ERR);
+		return TRUE;
+	}
+	g_hash_table_destroy(valid.validation_table);
+	qof_backend_set_error(params->be, ERR_QSF_NO_MAP);
+	return FALSE;
+}
+
+gboolean is_qsf_object_be(qsf_param *params)
+{
+	xmlDocPtr doc;
+	char *path;
+
+	g_return_val_if_fail((params != NULL),FALSE);
+	path = g_strdup(params->filepath);
+	if(path == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+		return FALSE;
+	}
+	/* skip validation if is_our_qsf_object has already been called. */
+	if(ERR_QSF_INVALID_OBJ == qof_backend_get_error(params->be)) { return FALSE; }
+	if(params->file_type == QSF_UNDEF)
+	{
+		doc = xmlParseFile(path);
+		if(doc == NULL) {
+			qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+			return FALSE;
+		}
+		if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc))
+		{
+			qof_backend_set_error(params->be, ERR_QSF_INVALID_OBJ);
+			return FALSE;
+		}
+	}
+	/** \todo implement a way of finding more than one map */
+	return is_qsf_object_with_map_be("pilot-qsf-GnuCashInvoice.xml", params);
+}
+
+static void
+qsf_supported_data_types(gpointer type, gpointer user_data)
+{
+	qsf_param *params;
+
+	g_return_if_fail(user_data != NULL);
+	g_return_if_fail(type != NULL);
+	params = (qsf_param*) user_data;
+	if(qsf_is_element(params->param_node, params->qsf_ns, (char*)type))
+	{
+		g_hash_table_insert(params->qsf_parameter_hash,
+			xmlGetProp(params->param_node, BAD_CAST QSF_OBJECT_TYPE), params->param_node);
+	}
+}
+
+static void
+qsf_parameter_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params)
+{
+	params->param_node = child;
+	g_slist_foreach(params->supported_types, qsf_supported_data_types, params);
+}
+
+/* Despite the name, this function handles the QSF object book tag
+AND the object tags. */
+void
+qsf_object_node_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params)
+{
+	struct qsf_node_iterate iter;
+	qsf_objects *object_set;
+	char *tail, *object_count_s;
+	int c;
+
+	g_return_if_fail(child != NULL);
+	g_return_if_fail(qsf_ns != NULL);
+	params->qsf_ns = qsf_ns;
+	if(qsf_is_element(child, qsf_ns, QSF_OBJECT_TAG)) {
+		params->qsf_parameter_hash = NULL;
+		object_set = g_new(qsf_objects, 1);
+		params->object_set = object_set;
+		object_set->parameters = g_hash_table_new(g_str_hash, g_str_equal);
+		object_set->object_type = g_strdup((char*)xmlGetProp(child, BAD_CAST QSF_OBJECT_TYPE));
+		object_count_s = g_strdup((char*)xmlGetProp(child, BAD_CAST QSF_OBJECT_COUNT));
+		c = (int)strtol(object_count_s, &tail, 0);
+		g_free(object_count_s);
+		params->qsf_object_list = g_list_prepend(params->qsf_object_list, object_set);
+		iter.ns = qsf_ns;
+		params->qsf_parameter_hash = object_set->parameters;
+		qsf_node_foreach(child, qsf_parameter_handler, &iter, params);
+	}
+}
+
+void
+qsf_book_node_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params)
+{
+	char *book_count_s, *tail;
+	int book_count;
+	xmlNodePtr child_node;
+	struct qsf_node_iterate iter;
+	gchar *buffer;
+	GUID book_guid;
+
+	if(qsf_is_element(child, ns, QSF_BOOK_TAG)) {
+		book_count_s = (char*)xmlGetProp(child, BAD_CAST QSF_BOOK_COUNT);
+		if(book_count_s) {
+			book_count = (int)strtol(book_count_s, &tail, 0);
+			/* More than one book not currently supported. */
+			g_return_if_fail(book_count == 1);
+		}
+		iter.ns = ns;
+		qsf_node_foreach(child, qsf_object_node_handler, &iter, params);
+	}
+	for(child_node = child->children; child_node != NULL;
+		child_node = child_node->next)
+		{
+		if(qsf_is_element(child_node, ns, QSF_BOOK_GUID)) {
+			buffer = g_strdup((char*)xmlNodeGetContent(child_node));
+			g_return_if_fail(TRUE == string_to_guid(buffer, &book_guid));
+			qof_entity_set_guid((QofEntity*)params->book, &book_guid);
+			g_free(buffer);
+		}
+		if(qsf_is_element(child_node, ns, QSF_OBJECT_TAG)) {
+			iter.ns = ns;
+			qsf_node_foreach(child_node, qsf_object_node_handler, &iter, params);
+		}
+	}
+}

Added: gnucash/trunk/lib/libqof/backend/file/qsf-xml.h
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-xml.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-xml.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,852 @@
+/***************************************************************************
+ *            qsf-xml.h
+ *
+ *  Fri Nov 26 19:29:47 2004
+ *  Copyright  2004-2005  Neil Williams  <linux at codehelp.co.uk>
+ *
+ ****************************************************************************/
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+ #ifndef QSF_XML_H
+ #define QSF_XML_H
+
+/** @file qsf-xml.h
+    @brief  Private QSF header
+    @author Copyright (C) 2004-2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <regex.h>
+#include <time.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlschemas.h>
+#include "gnc-date.h"
+#include "qof_book_merge.h"
+#include "qofbook.h"
+#include "qofclass.h"
+#include "qofobject.h"
+#include "kvp_frame.h"
+#include "qofbackend-p.h"
+#include "qofsession-p.h"
+#include "qofbook-p.h"
+
+#if defined(HAVE_GETTEXT)             /* HAVE_GETTEXT */
+
+#include <libintl.h>
+#include <locale.h>
+
+#undef _
+#undef Q_
+
+#ifdef DISABLE_GETTEXT_UNDERSCORE
+#define _(String) (String)
+#define Q_(String) gnc_qualifier_prefix_noop(String)
+#else                                 /* ENABLE_GETTEXT_UNDERSCORE */
+#define _(String) gettext(String)
+#define Q_(String) gnc_qualifier_prefix_gettext(String)
+#endif                                /* End ENABLE_GETTEXT_UNDERSCORE */
+
+#else                                 /* Not HAVE_GETTEXT */
+#if !defined(__USE_GNU_GETTEXT)
+
+#undef _
+#undef Q_
+#define _(String)       (String)
+#define Q_(String) gnc_qualifier_prefix_noop(String)
+#define gettext(String) (String)
+#define ngettext(msgid, msgid_plural, n) (((n)==1) ? \
+                                            (msgid) : (msgid_plural))
+
+#endif                                /* End not__USE_GNU_GETTEXT */
+#endif                                /* End Not HAVE_GETTEXT */
+
+#undef  N_
+#define N_(String) (String)
+
+
+typedef enum  {
+	QSF_UNDEF = 0, /**< Initial undefined value. */
+	IS_QSF_MAP,   /**< A QSF map */
+	IS_QSF_OBJ,   /**< A QSF object without a map - it may or may not need one. */
+	HAVE_QSF_MAP, /**< A QSF object with the map it needs. */
+	OUR_QSF_OBJ,  /**< A QSF object that can be loaded without a map. */
+}qsf_type;
+
+/** \brief Holds a description of the QofObject.
+
+Used when converting QOF objects from another application. The incoming,
+\b unknown, objects need to be stored prior to conversion. This allows 
+many-to-many conversions where an invoice can receive data from an incoming
+expense AND datebook and use data from an incoming contacts object to lookup
+the customer for the invoice.
+*/
+typedef struct qsf_object_set
+{
+	GHashTable *parameters;
+	QofIdType object_type;
+	int object_count;
+}qsf_objects;
+
+#define QSF_QOF_VERSION QOF_OBJECT_VERSION /**< QOF Version check.
+
+Make sure the same version of QOF is in use in both applications.
+*/
+/** @name QSF Object XML
+
+@{ */
+#define QSF_ROOT_TAG	"qof-qsf" /**< The top level root tag */
+#define QSF_DEFAULT_NS	"http://qof.sourceforge.net/" /**< Default namespace for QSF root tag
+
+The map namespace is not included as maps are not currently written out by QOF.
+*/
+#define QSF_DATE_LENGTH 31 /**< Max length of QSF_XSD_TIME */
+#define QSF_BOOK_TAG	"book" /**< First level child: book tag - the ::QofBook. */
+#define QSF_BOOK_GUID	"book-guid" /**< QOF GUID tag for the QofBook described by this QSF object file */
+#define QSF_BOOK_COUNT	"count" /**< Sequential counter of each book in this file */
+#define QSF_OBJECT_TAG	"object" /**< Second level child: object tag */
+#define QSF_OBJECT_TYPE	"type" /**< QSF parameter name for object type specifiers */
+#define QSF_OBJECT_COUNT "count" /**< Sequential counter for each QSF object in this file */
+#define QSF_XML_VERSION  "1.0"  /**< The current XML version. */
+
+/** @} */
+/** @name Representing KVP as XML
+
+&lt;kvp type="kvp" path="/from-sched-xaction" value="guid"&gt;c858b9a3235723b55bc1179f0e8c1322&lt;/kvp&gt;
+A kvp type KVP parameter located at $path containing a GUID $value.
+
+The relevance of type="kvp" won't be evident in GnuCash, they all use "kvp".
+
+A non-GnuCash example helps:
+&lt;kvp type="pilot_addr_kvp" path="/user/name" value="guid"&gt;c858b9a3235723b55bc1179f0e8c1322&lt;/kvp&gt;
+A pilot_addr_kvp type KVP parameter located at /user/name containing a guid value.
+@{ */
+
+#define QSF_OBJECT_KVP   "path" /**< The path to this KVP value in the entity frame. */
+#define QSF_OBJECT_VALUE "value" /**< The KVP Value. */
+/** @} */
+/** @name QSF Map XML
+
+@{ */
+#define MAP_ROOT_TAG     "qsf-map" /**< Top level root tag for QSF Maps */
+#define MAP_DEFINITION_TAG "definition" /**< Second level container for defined objects 
+
+Attributes: qof_version - Taken from the QOF_OBJECT_VERSION macro in QOF,
+At the time of QSF development, QOF_OBJECT_VERSION is defined as 3. All
+QSF maps and QSF objects must use the same qof_version which in turn must
+match the QOF_OBJECT_VERSION for the QOF library in use by the calling process.
+
+No text content allowed.
+*/
+#define MAP_DEFINE_TAG	"define" /**< defines each object supported by this QSF map 
+
+Attributes: e_type Copied directly from the QofObject definition.
+Content: The full QofObject description for the defined QOF object.
+*/
+#define MAP_DEFAULT_TAG	"default"  /**< User editable defaults for data not available within the
+available QSF objects.
+
+Some defaults will relate to how to format descriptive dates, whether discount should be considered,
+which account to use for certain QSF data from applications that don't use accounts.
+
+Some defaults are pre-defined and cannot be over-written:
+- qsf_time_now
+- qsf_time_string
+
+Attributes (All are mandatory): 
+
+\a name The text name for this default. Certain pre-defined defaults exist but user- or
+map-defined defaults can have any unique text name. Spaces are \b NOT allowed, use undersccores
+instead. The value of name must not duplicate any existing default, define, object or parameter
+unless the special type, enum, is used.
+
+\a type QOF_TYPE - must be one of the recognised QOF data types for the
+qof_version in use or the special type, enum.
+
+\a value Text representation of the required value. For numeric, use the format
+[0-9]?/[0-9]?
+
+\attention Using boolean defaults 
+
+A boolean default is not output in the QSF directly, instead the value is
+used in the calculations to modify certain values. If the boolean default
+is set to true, the if statement containing the boolean name will be evaluated. If the boolean
+default is set to false, the corresponding else will be evaluted. Make sure your
+calculations contain an appropriate else statement so that the boolean value
+can be adjusted without invalidating the map!
+
+QSF deals with partial QofBooks - each object is fully described but the
+book does not have to contain any specific object types or have any
+particular structure. To merge partial books into usual QofBook data
+sources, the map must deal with entities that need to be referenced in
+the target QofBook but which simply don't exist in the QofBook used to generate
+the QSF. e.g. pilot-link knows nothing of Accounts yet when QSF creates
+a gncInvoice from qof-datebook, gncInvoice needs to know the GUID of 
+certain accounts in the target QofBook. This is handled in the map 
+by specifying the name of the account as a default for that map. When imported,
+the QSF QofBackend looks up the object required using the name of
+the parameter to obtain the parameter type. This is the only situation
+where QSF converts between QOF data types. A string description of the
+required object is converted to the GUID for that specific entity. The
+map cannot contain the GUID as it is generic and used by multiple users.
+
+\attention Using enumerators
+- enum types are the only defaults that are allowed to use the same name value more than once. 
+- enum types are used to increase the readability of a QSF map.
+- The enum name acts to group the enum values together - in a similar fashion to radio buttons in HTML forms. 
+- enum types are used only where the QOF object itself uses an enum type. 
+
+e.g. the tax_included enum type allows maps to use the full name of the enum value GNC_TAXINCLUDED_YES,
+instead of the cryptic digit value, 1.
+
+*/
+#define MAP_OBJECT_TAG	"object" /**< Contains all the calculations to make one object from others.
+
+Note that creating an object for the import application can involve using data from more than one
+QSF object, as well as defaults and lookups in the import application itself. Conditionals, simple
+arithmetic and date/time formatting options are also available.
+*/
+#define MAP_CALCULATE_TAG	"calculate" /**< One calculation for every parameter that needs to be set.
+
+QSF follows the same rule as qof_book_merge. Only if a getter and a setter function are defined for
+a parameter is it available to QSF. If a ::QofAccessFunc and ::QofSetterFunc are both defined
+for any QofObject parameter, that parameter \b MUST be calculated in any map that defines that object.
+*/
+#define MAP_QOF_VERSION	"qof_version" /**< This is the QOF_OBJECT_VERSION from QOF.
+
+QSF maps may need to be updated if QOF itself is upgraded. This setting is coded into QOF and 
+maps for one version cannot necessarily be used by other versions. At the first release of QSF,
+QOF_OBJECT_VERSION = 3.
+*/
+#define MAP_NAME_ATTR	"name" /**< The name of the default setting.
+
+Use this name to refer to the value of this default in the map calculations.
+
+Make sure that the type of this default matches the type of the parameter being set by the 
+parent calculation!
+*/
+#define MAP_TYPE_ATTR	"type" /**< QSF will NOT convert between QOF types.
+
+QSF will allow a conditional to use a parameter of one type to determine the value from a parameter of
+another type, but the final value assigned \b MUST be of the same type as the parent calculation.
+*/
+#define MAP_VALUE_ATTR	"value" /**< The value of the tag, used in defaults and calculations.
+
+The value of a default is a string representation of the value to be inserted into
+the calculation where the default is used.
+
+The value of a calculation is the name of the parameter that will be set by that calculation.
+*/
+#define MAP_E_TYPE	"e_type" /**< Validates the objects defined in the map 
+
+The e_type will be used to match incoming QSF objects with the relevant QSF map.
+The value of the e_type must be the value of the e_type for that object in the
+originating QOF application. The define tag must contain the value of the description
+of the same object in the same originating QOF application.
+*/
+/** \todo enum is an attempt to make enumerator values descriptive in the maps
+and QSF (possibly). Not working yet. */
+#define MAP_ENUM_TYPE "enum"
+
+/** \brief A specific boolean default for this map.
+*/
+#define QSF_BOOLEAN_DEFAULT "boolean"
+
+#define QSF_CONDITIONAL "if"  /**< child of calculate.
+
+Conditionals can reference objects as if within the original application. In operation,
+the map is overlaid across both sets of defined objects, an import object in the source 
+application and an output object for the destination object. The current import and output 
+QSF objects are therefore always available to the map. 
+Conditionals can reference parameter as well as object values.
+*/
+#define QSF_CONDITIONAL_SET "set" /**< Assignment statement
+
+Map assignments can use the native values within the output object. The output object
+must support setting the relevant parameter using the value exactly as given in the map 
+because the relevant set() function will be called using this value. This may reduce the 
+readability of the map but the relevant application could also be modified to support a more 
+readable set function.
+*/
+#define QSF_CONDITIONAL_ELSE "else" /**< Alternative
+
+if(){} else{} is also supported. Nesting of conditionals causes problems for validating the
+final map against any sensible XML Schema and a map that doesn't validate will be rejected. 
+When editing conditionals in a QSF map, ALWAYS validate the map using xmllint. If necessary, 
+define a variable at the foot of the definitions block, using a similar syntax to a default, 
+then use that variable in another conditional
+
+\a variable \a name="my_rate" \a type="numeric" \a value="0/1"
+
+The syntax for xmllint is:
+
+\a xmllint \a --schema \a &lt;schema file&gt; \a &lt;qsf-file&gt;
+
+Use the qsf-object.xsd.xml schema for objects and qsf-map.xsd.xml for map files.
+
+e.g. xmllint --schema qsf-object.xsd.xml --noout qof-qsf.xml
+
+*/
+#define QSF_OPTION "option" /**< enum operator
+
+Not implemented yet - may need to change once work starts.
+Theoretically, option will specify when an enumerator value is in use -
+it is quite possible that it will be unnecessary.
+*/
+
+#define QSF_FORMATTING_OPTION "format" /**< How to format dates/times
+
+When the QSF map uses a date/time value as a \b string, the formatting
+can be adjusted to personal preference. \a format will only be evaluated
+if the calculated parameter is a QOF_TYPE_STRING - any format attributes
+on other data types will be ignored.
+ */
+
+/** @} */
+
+#define QSF_XSD_TIME  QOF_UTC_DATE_FORMAT /**< xsd:dateTime format in coordinated universal time, UTC.
+
+You can reproduce the string from the GNU/Linux command line using the date utility: 
+
+date -u +%Y-%m-%dT%H:%M:%SZ
+
+2004-12-12T23:39:11Z
+
+The datestring must be timezone independent and include all specified fields.
+
+Remember to use gmtime() NOT localtime()!. From the command line, use the -u switch with the 
+date command: date -u
+
+To generate a timestamp based on a real time, use the qsf_time_now and qsf_time_string defaults.
+
+qsf_time_now : Format: QOF_TYPE_DATE. The current time taken from the moment the default
+is read into a QSF object at runtime.
+
+qsf_time_string : Format: QOF_TYPE_STRING. The current timestamp taken from the moment the
+default is read into a QSF object at runtime. This form is used when the output parameter 
+needs a formatted date string, not an actual date object. The format is determined by the 
+optional format attribute of the set tag which takes the same operators as the GNU C Library 
+for strftime() and output may therefore be dependent on the locale of the calling process - 
+\b take \b care. Default value is %F, used when qsf_time_string is set without the format
+attribute.
+
+Both defaults use UTC.
+
+*/
+#define QSF_XML_BOOLEAN_TEST "true" /**< needs to be lowercase for XML validation */
+
+#define QSF_OBJECT_SCHEMA "qsf-object.xsd.xml" /**< Name of the QSF Object Schema. */
+#define QSF_MAP_SCHEMA "qsf-map.xsd.xml" /**< Name of the QSF Map Schema. */
+/** \brief QSF Parameters
+
+This struct is a catch-all for all parameters required
+for various stages of the process. There are lots of elements
+here that will finally be removed.
+*/
+typedef struct qsf_metadata
+{
+	qsf_type file_type;          /**< what type of file is being handled */
+	qsf_objects *object_set;     /**< current object set for qsf_object_list. */
+	int count;                   /**< sequential counter for each object in the book */
+	GList *qsf_object_list;      /**< list of qsf_objects */
+	GSList *qsf_sequence;        /**< Parameter list sorted into QSF order */
+	GList *referenceList;        /**< Table of references, ::QofEntityReference. */
+	GHashTable *qsf_parameter_hash; /**< Hashtable of parameters for each object */
+	GHashTable *qsf_calculate_hash, *qsf_default_hash, *qsf_define_hash;
+	GSList *supported_types;     /**< The partial list of QOF types currently supported, in QSF order. */
+	xmlDocPtr input_doc, output_doc; /**< Pointers to the input and output xml document(s). */
+	/** \todo Review the list of xml nodes in qsf_param and rationalise. */
+	xmlNodePtr child_node, cur_node, param_node, output_node, output_root, book_node, lister;
+	xmlNsPtr qsf_ns, map_ns;     /**< Separate namespaces for QSF objects and QSF maps. */
+	const char *qof_type;        /**< Holds details of the QOF_TYPE */
+	QofIdType qof_obj_type;	     /**< current QofObject type (e_type) for the parameters. */
+	QofEntity *qsf_ent;          /**< Current entity in the book. */
+	QofBackend *be;              /**< the current QofBackend for this operation. */
+	gboolean knowntype;          /**< detect references by comparing with known QOF types. */
+	QofParam *qof_param;         /**< used by kvp to handle the frame hash table */
+	QofBook *book;	             /**< the current QofBook.
+
+		Theoretically, QSF can handle multiple QofBooks - currently limited to 1.
+	*/
+	int boolean_calculation_done; /**< simple trip once this boolean is complete. */
+	char *filepath; /**< Path to the QSF file. */
+}qsf_param;
+
+/** \brief Free the QSF context.
+
+Frees the two GHashTables, the GSList, the output xmlDoc
+and the two xmlNs namespaces.
+*/
+void qsf_free_params(qsf_param *params);
+
+/** \brief Validation metadata
+
+The validation is a separate parse with separate data.
+This may change but it currently saves workload.
+
+\todo Examine ways of making the Validation metadata
+into a sub-set of the main code, not an island on it's own.
+*/
+typedef struct qsf_validates
+{
+	QofBackendError error_state;
+	const char *object_path;
+	const char *map_path;
+	GHashTable *validation_table;
+	int valid_object_count;
+	int map_calculated_count;
+	int qof_registered_count;
+}qsf_validator;
+
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+as well as in a block.
+*/
+int
+qsf_compare_tag_strings(const xmlChar *node_name, char *tag_name);
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+as well as in a block.
+*/
+int
+qsf_strings_equal(const xmlChar *node_name, char *tag_name);
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+as well as in a block.
+*/
+int
+qsf_is_element(xmlNodePtr a, xmlNsPtr ns, char *c);
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+as well as in a block.
+*/
+int
+qsf_check_tag(qsf_param *params, char *qof_type);
+
+/** \brief Checks all incoming objects for QOF registration.
+
+Sums all existing objects in the QSF and counts the number of those
+objects that are also registered with QOF in the host application.
+*/
+void
+qsf_object_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid);
+
+/** @name Map Checks
+@{
+Check that the map is sufficient for this object. 
+
+Map is usable if all input objects are defined in the object file.
+Count define tags, subtract those calculated in the map (defined as objects)
+Check each remaining object e_type and description against the objects
+declared in the object file. Fail if some map objects remain undefined.
+
+not finished - expect noticeable changes.
+
+*/
+void
+qsf_map_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid);
+
+void
+qsf_map_top_node_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params);
+
+void
+qsf_map_object_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params);
+/** @} */
+
+/** \brief Compares an xmlDoc in memory against the schema file.
+
+ at param	schema_dir  set at compile time to $prefix/share/qsf/
+ at param schema_filename Either the QSF Object Schema or the QSF Map Schema.
+ at param doc 	The xmlDoc read from the file using libxml2.
+
+Ensure that you call the right schema_filename for the doc in question!
+
+Incorrect validation will result in output to the terminal window.
+
+ at return TRUE if the doc validates against the assigned schema, otherwise FALSE.
+*/
+gboolean
+qsf_is_valid(const char *schema_dir, const char* schema_filename, xmlDocPtr doc);
+
+/** \brief Validate a QSF file and identify a suitable QSF map
+
+ at param	params	Pointer to qsf_param context
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the 
+corresponding function in other code.
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to find out if a suitable QSF
+map exists. Map files are accepted if all objects described in the QSF object
+file are defined in the QSF map.
+
+ at return TRUE if the file validates and a QSF map can be found,
+otherwise FALSE.
+*/
+gboolean is_qsf_object_be(qsf_param *params);
+
+/** \brief Validate a QSF file and determine type.
+
+ at param	params	Pointer to qsf_param context
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to see if it is registered
+with QOF within the QOF environment of the calling process.
+
+Files that pass the test can be imported into the QOF appliction without the need
+for a QSF map.
+
+ at return TRUE if the file validates and all objects pass,
+otherwise FALSE.
+*/
+gboolean is_our_qsf_object_be(qsf_param *params);
+
+/** \brief Validate a QSF map file.
+
+ at param	params	Pointer to qsf_param context
+
+The file is validated aginst the QSF map schema, qsf-map.xsd.xsml. This
+function is called by ::is_qsf_object. If called directly, the map file
+is validated and closed with a QofBackend error. QSF maps do not contain
+user data and are used to import QSF object files.
+
+ at return TRUE if the map validates, otherwise FALSE.
+*/
+gboolean is_qsf_map_be(qsf_param *params);
+
+/** \brief Validate a QSF file and a selected QSF map
+
+ at param	map_path	Absolute or relative path to the selected QSF map file
+ at param	params	Pointer to qsf_param context
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to find out if the supplied QSF
+map is suitable. Map files are accepted if all objects described in the QSF object
+file are defined in the QSF map.
+
+\todo Need to code for how to find these files.
+
+
+ at return TRUE if the file validates and the supplied QSF map is usable,
+otherwise FALSE.
+*/
+gboolean is_qsf_object_with_map_be(char *map_path, qsf_param *params);
+
+gboolean is_qsf_object_with_map(const char *path, char *map_file);
+
+/**	\brief QOF processing routine.
+
+Called by ::qof_session_load if a map is required.
+Accepts QSF_OBJECT.
+
+Checks available QSF maps for match. Only succeeds if a suitable map exists.
+
+*/
+gboolean
+load_qsf_object(QofBook *book, const char *fullpath, qsf_param *params);
+
+/**	\brief QOF processing routine.
+
+Called using ::qof_session_load when all objects defined in the
+XML are registered in the current instance of QOF.
+*/
+gboolean
+load_our_qsf_object(QofBook *book, const char *fullpath, qsf_param *params);
+
+/** \brief Book and book-guid node handler.
+
+Reads the book count="" attribute (currently only 1 QofBook is supported per QSF object file)
+Sets the book-guid as the GUID of the current QofBackend QofBook in qsf_param.
+Calls the next handler, qsf_object_node_handler, with the child of the book tag.
+*/
+void qsf_book_node_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params);
+
+/** \brief Commit the QSF object data to a new QofBook.
+
+The parentage of qof_book_merge should be obvious in this function.
+
+Large chunks were just lifted directly from the commit loop and adjusted
+to obtain the data to commit from the xmlNodePtr instead of qof_book_mergeRule. If
+anything, it's easier here because all entities are new, there are no targets.
+
+Unlike qof_book_merge, this routine runs once per parameter within a loop
+that iterates over objects - it does not have a loop of it's own.
+
+All entities are new.
+
+Using the parent of the current node to 
+retrieve the type parameter of the parent provides the type parameter of
+the object tag - the e_type of the current QofObject which allows 
+qof_class_get_parameter_setter(obj_type, key);
+
+ at param	key		name of the parameter: QofIdType
+ at param	value	xmlNodePtr value->name == QOF_TYPE, content(value) = data to commit.
+ at param	data	qsf_param* - inevitably.
+
+*/
+void qsf_object_commitCB(gpointer key, gpointer value, gpointer data);
+
+/** \brief Convert a string value into KvpValue
+
+Partner to ::kvp_value_to_string. Given the type of KvpValue
+required, attempts to convert the string into that type of
+value.
+
+ at param content A string representation of the value, ideally as
+		output by kvp_value_to_string.
+ at param type KvpValueType of the intended KvpValue
+
+ at return KvpValue* or NULL on failure.
+*/
+KvpValue*
+string_to_kvp_value(const char *content, KvpValueType type);
+
+/** \brief Backend init routine.
+
+Sets the sequence of parameters to match the schema and provide a reliable
+parse. Sets the default strings for qsf_enquiry_date, qsf_time_now and
+qsf_time_string.
+
+Filters the parameter list to set each type in this order:
+- QOF_TYPE_STRING
+- QOF_TYPE_GUID
+- QOF_TYPE_BOOLEAN
+- QOF_TYPE_NUMERIC
+- QOF_TYPE_DATE
+- QOF_TYPE_INT32
+- QOF_TYPE_INT64
+- QOF_TYPE_DOUBLE
+- QOF_TYPE_CHAR
+- QOF_TYPE_KVP
+- QOF_TYPE_COLLECT
+- QOF_TYPE_CHOICE
+
+*/
+void qsf_param_init(qsf_param *params);
+
+
+/** \brief map callback
+
+Investigate ways to get the map callback to do both
+the map and the validation tasks.
+**/
+typedef void (* qsf_nodeCB)(xmlNodePtr, xmlNsPtr, qsf_param*);
+
+/** \brief validator callback
+
+\todo The need for separate metadata means a separate callback typedef
+	is needed for the validator, but this should be fixed to only need one.
+*/
+typedef void (* qsf_validCB)(xmlNodePtr, xmlNsPtr, qsf_validator*);
+
+
+/** \brief One iterator, two typedefs
+
+\todo resolve the two callbacks in ::qsf_node_iterate into one.
+*/
+struct qsf_node_iterate {
+	qsf_nodeCB *fcn;
+	qsf_validCB *v_fcn;
+	xmlNsPtr ns;
+};
+
+/** \brief Validate a QSF file and identify a suitable QSF map
+
+ at param	path	Absolute or relative path to the file to be validated.
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the 
+corresponding function in other code.
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to find out if a suitable QSF
+map exists. Map files are accepted if all objects described in the QSF object
+file are defined in the QSF map.
+
+ at return TRUE if the file validates and a QSF map can be found,
+otherwise FALSE.
+*/
+gboolean is_qsf_object(const char *path);
+
+/** \brief Validate a QSF file and determine type.
+
+ at param	path	Absolute or relative path to the file to be validated
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the 
+corresponding function in other code.
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to see if it is registered
+with QOF within the QOF environment of the calling process.
+
+Files that pass the test can be imported into the QOF appliction without the need
+for a QSF map.
+
+ at return TRUE if the file validates and all objects pass,
+otherwise FALSE.
+*/
+gboolean is_our_qsf_object(const char *path);
+
+/** \brief Validate a QSF map file.
+
+ at param	path	Absolute or relative path to the file to be validated
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the 
+corresponding function in other code.
+
+The file is validated aginst the QSF map schema, qsf-map.xsd.xsml. This
+function is called by ::is_qsf_object. If called directly, the map file
+is validated and closed, no data is retrieved. QSF maps do not contain
+user data but are used to import QSF object files from other applications.
+
+ at return TRUE if the map validates, otherwise FALSE.
+*/
+gboolean is_qsf_map(const char *path);
+
+/** \brief Determine the type of QSF and load it into the QofBook
+
+- is_our_qsf_object, OUR_QSF_OBJ, QSF object file using only QOF objects known to the calling process.
+	No map is required.
+- is_qsf_object, IS_QSF_OBJ, QSF object file that may or may not have a QSF map
+	to convert external objects. This temporary type will be set to HAVE_QSF_MAP if a suitable
+	map exists, or an error value returned: ERR_QSF_NO_MAP, ERR_QSF_BAD_MAP or ERR_QSF_WRONG_MAP
+	This allows the calling process to inform the user that the QSF itself is valid but a
+	suitable map cannot be found.
+- is_qsf_map, IS_QSF_MAP, QSF map file. In the backend, this generates ERR_QSF_MAP_NOT_OBJ but
+	it can be used internally when processing maps to match a QSF object.
+
+ at return NULL on error, otherwise a pointer to the QofBook. Use
+	the qof_book_merge API to merge the new data into the current
+	QofBook. 
+*/
+void
+qsf_file_type (QofBackend *be, QofBook *book);
+
+void
+qsf_valid_foreach(xmlNodePtr parent, qsf_validCB cb,
+	struct qsf_node_iterate *iter, qsf_validator *valid);
+
+void
+qsf_node_foreach(xmlNodePtr parent, qsf_nodeCB cb,
+	struct qsf_node_iterate *iter, qsf_param *params);
+
+/** \brief Loads the QSF into a QofSession, ready to merge.
+ 
+Loads a QSF object file containing only GnuCash objects
+into a second QofSession.
+ 
+ at param first_session A QofSession pointer to the original session. This
+will become the target of the subsequent qof_book_merge.
+
+ at param path	Absolute or relative path to the file to be loaded
+ 
+ at return ERR_BACKEND_NO_ERR == 0 on success, otherwise the QofBackendError
+	set by the QSFBackend.
+  			
+\todo Build the qof_book_merge code onto this function if session loads
+  properly.  	
+*/
+QofBackendError 
+qof_session_load_our_qsf_object(QofSession *first_session, const char *path);
+
+/** \brief Placeholder so far.
+
+\todo Determine the map to use and convert the QOF objects
+ 
+ Much of the map code is written but there is still work to do.
+*/
+QofBackendError 
+qof_session_load_qsf_object(QofSession *first_session, const char *path);
+
+/** \brief Convert between QSF objects
+
+This is the main workhorse of the conversion between QSF objects using
+maps.
+
+ at param mapDoc The map document, parsed by libxml2.
+ at param qsf_root The top node of the QSF object to be converted using the map.
+ at param params The QSF backend parameters.
+
+Each calculation in the map is performed over the child nodes of the 
+object tree. A new xmlDoc is created and this is made available to QOF to
+be loaded into the book.
+
+*/
+xmlDocPtr
+qsf_object_convert(xmlDocPtr mapDoc, xmlNodePtr qsf_root, qsf_param *params);
+
+void
+qsf_object_node_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params);
+
+/** \brief Backend routine to write a file or stdout.
+
+This function is used by ::qof_session_save to write any QofBook to QSF,
+any process that can create a new QofSession and populate the QofBook 
+with QOF objects can write the data as QSF XML - the book does not need
+an AccountGroup. Remember that only fully \b QOF-compliant objects
+are supported by QSF.
+
+Your QOF objects must have:
+	- a create: function in the QofObject definition
+	- a foreach: function in the QofObject definition
+	- QofParam params[] registered with QOF using
+		qof_class_register and containing all necessary parameters
+		to reconstruct this object without any further information.
+	- Logical distinction between those parameters that should be
+		set (have a QofAccessFunc and QofSetterFunc) and those that 
+		should only be calculated (only a QofAccessFunc).
+
+If you begin your QSF session with ::QOF_STDOUT as the book_id,
+QSF will write to STDOUT - usually a terminal. This is used by QOF
+applications to provide data streaming. If you don't want terminal
+output, take care to check the path given to 
+::qof_session_begin - don't try to change it later!
+
+The XML is validated against the QSF object schema before being
+written (to file or stdout).
+
+Check the QofBackendError - don't assume the file is OK.
+
+*/
+void qsf_write_file(QofBackend *be, QofBook *book);
+
+/** \brief Create a new QSF backend.
+*/
+QofBackend* qsf_backend_new(void);
+
+/** @} */
+/** @} */
+
+#endif /* QSF_XML_H */

Added: gnucash/trunk/lib/libqof/qof/Makefile.am
===================================================================
--- gnucash/trunk/lib/libqof/qof/Makefile.am	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/Makefile.am	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,86 @@
+lib_LTLIBRARIES = libqof.la
+
+libqof_la_LDFLAGS= -version-info $(LIBQOF_LIBRARY_VERSION)
+
+libqof_la_SOURCES =  \
+   gnc-date.c        \
+   gnc-engine-util.c \
+   gnc-numeric.c     \
+   gnc-event.c       \
+   gnc-trace.c       \
+   guid.c            \
+   kvp_frame.c       \
+   kvp-util.c        \
+   md5.c             \
+   qofbackend.c      \
+   qofclass.c        \
+   qofchoice.c       \
+   qofgobj.c         \
+   qofid.c           \
+   qofinstance.c     \
+   qofquery.c        \
+   qofbook.c         \
+   qofobject.c       \
+   qofquerycore.c    \
+   qofsession.c      \
+   qof_book_merge.c
+
+qofincludedir = ${pkgincludedir}
+
+qofinclude_HEADERS = \
+   gnc-date.h        \
+   gnc-engine-util.h \
+   gnc-numeric.h     \
+   gnc-event.h       \
+   gnc-trace.h       \
+   guid.h            \
+   kvp_frame.h       \
+   kvp-util.h        \
+   kvp-util-p.h      \
+   qof.h             \
+   qof-be-utils.h    \
+   qofbackend.h      \
+   qofbackend-p.h    \
+   qofclass.h        \
+   qofchoice.h       \
+   qofgobj.h         \
+   qofid.h           \
+   qofid-p.h         \
+   qofinstance-p.h   \
+   qofinstance.h     \
+   qofquery.h        \
+   qofbook.h         \
+   qofobject.h       \
+   qofquerycore.h    \
+   qofsession.h      \
+   qofla-dir.h       \
+   qof_book_merge.h
+
+noinst_HEADERS += \
+   gnc-event-p.h \
+   md5.h  \
+   qofclass-p.h  \
+   qofmath128.h  \
+   qofquery-p.h  \
+   qofquery-deserial.h \
+   qofquery-serialize.h \
+   qofbook-p.h  \
+   qofobject-p.h  \
+   qofquerycore-p.h \
+   qofsession-p.h
+
+QOFLIBdir = $(libdir)
+
+EXTRA_DIST += \
+  qofla-dir.h.in \
+  qofmath128.c
+
+qofla-dir.h: qofla-dir.h.in
+        rm -f $@.tmp
+        sed < $< > $@.tmp \
+                -e 's:@-libdir-@:${QOFLIBdir}:g'
+        mv $@.tmp $@
+
+BUILT_SOURCES += qofla-dir.h
+
+

Added: gnucash/trunk/lib/libqof/qof/gnc-date.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-date.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-date.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1391 @@
+/********************************************************************\
+ * gnc-date.c -- misc utility functions to handle date and time     * 
+ *                                                                  *
+ * Copyright (C) 1997 Robin D. Clark <rclark at cs.hmc.edu>            *
+ * Copyright (C) 1998-2000, 20003 Linas Vepstas <linas at linas.org>   *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#define _GNU_SOURCE
+#define __EXTENSIONS__
+
+#include "config.h"
+
+#include <ctype.h>
+
+#ifdef HAVE_LANGINFO_D_FMT
+#include <langinfo.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <glib.h>
+
+#include "gnc-date.h"
+#include "gnc-trace.h"
+
+#ifndef HAVE_STRPTIME
+#include "strptime.h"
+#endif
+#ifndef HAVE_LOCALTIME_R
+#include "localtime_r.h"
+#endif
+
+#define NANOS_PER_SECOND 1000000000
+
+#ifdef HAVE_LANGINFO_D_FMT
+#  define GNC_D_FMT (nl_langinfo (D_FMT))
+#  define GNC_D_T_FMT (nl_langinfo (D_T_FMT))
+#  define GNC_T_FMT (nl_langinfo (T_FMT))
+#else
+#  define GNC_D_FMT "%Y-%m-%d"
+#  define GNC_D_T_FMT "%Y-%m-%d %r"
+#  define GNC_T_FMT "%r"
+#endif
+
+
+/* This is now user configured through the gnome options system() */
+static QofDateFormat dateFormat = QOF_DATE_FORMAT_LOCALE;
+static QofDateFormat prevQofDateFormat = QOF_DATE_FORMAT_LOCALE;
+
+/* This static indicates the debugging module that this .o belongs to. */
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+/********************************************************************\
+\********************************************************************/
+
+const char*
+gnc_date_dateformat_to_string(QofDateFormat format)
+{
+  switch (format) {
+  case QOF_DATE_FORMAT_US:
+    return "us";
+  case QOF_DATE_FORMAT_UK:
+    return "uk";
+  case QOF_DATE_FORMAT_CE:
+    return "ce";
+  case QOF_DATE_FORMAT_ISO:
+    return "iso";
+  case QOF_DATE_FORMAT_UTC:
+   return "utc";
+  case QOF_DATE_FORMAT_LOCALE:
+    return "locale";
+  case QOF_DATE_FORMAT_CUSTOM:
+    return "custom";
+  default:
+    return NULL;    
+  }
+}
+
+gboolean
+gnc_date_string_to_dateformat(const char* fmt_str, QofDateFormat *format)
+{
+  if (!fmt_str)
+    return TRUE;
+
+  if (!strcmp(fmt_str, "us"))
+    *format = QOF_DATE_FORMAT_US;
+  else if (!strcmp(fmt_str, "uk"))
+    *format = QOF_DATE_FORMAT_UK;
+  else if (!strcmp(fmt_str, "ce"))
+    *format = QOF_DATE_FORMAT_CE;
+  else if (!strcmp(fmt_str, "utc"))
+    *format = QOF_DATE_FORMAT_UTC;
+  else if (!strcmp(fmt_str, "iso"))
+    *format = QOF_DATE_FORMAT_ISO;
+  else if (!strcmp(fmt_str, "locale"))
+    *format = QOF_DATE_FORMAT_LOCALE;
+  else if (!strcmp(fmt_str, "custom"))
+    *format = QOF_DATE_FORMAT_CUSTOM;
+  else
+    return TRUE;
+
+  return FALSE;
+}
+
+
+const char*
+gnc_date_monthformat_to_string(GNCDateMonthFormat format)
+{
+  switch (format) {
+  case GNCDATE_MONTH_NUMBER:
+    return "number";
+  case GNCDATE_MONTH_ABBREV:
+    return "abbrev";
+  case GNCDATE_MONTH_NAME:
+    return "name";
+  default:
+    return NULL;
+  }
+}
+
+gboolean
+gnc_date_string_to_monthformat(const char *fmt_str, GNCDateMonthFormat *format)
+{
+  if (!fmt_str)
+    return TRUE;
+
+  if (!strcmp(fmt_str, "number"))
+    *format = GNCDATE_MONTH_NUMBER;
+  else if (!strcmp(fmt_str, "abbrev"))
+    *format = GNCDATE_MONTH_ABBREV;
+  else if (!strcmp(fmt_str, "name"))
+    *format = GNCDATE_MONTH_NAME;
+  else
+    return TRUE;
+
+  return FALSE;
+}
+
+/********************************************************************\
+\********************************************************************/
+
+static void
+timespec_normalize(Timespec *t)
+{
+  if(t->tv_nsec > NANOS_PER_SECOND)
+  {
+    t->tv_sec+= (t->tv_nsec / NANOS_PER_SECOND);
+    t->tv_nsec= t->tv_nsec % NANOS_PER_SECOND;
+  }
+
+  if(t->tv_nsec < - NANOS_PER_SECOND)
+  {
+    t->tv_sec+= - (-t->tv_nsec / NANOS_PER_SECOND);
+    t->tv_nsec = - (-t->tv_nsec % NANOS_PER_SECOND);
+  }
+
+  if (t->tv_sec > 0 && t->tv_nsec < 0)
+  {
+    t->tv_sec--;
+    t->tv_nsec = NANOS_PER_SECOND + t->tv_nsec;
+  }
+  
+  if (t->tv_sec < 0 && t->tv_nsec > 0)
+  {
+    t->tv_sec++;
+    t->tv_nsec = - NANOS_PER_SECOND + t->tv_nsec;
+  }
+  return;
+}
+  
+
+gboolean
+timespec_equal (const Timespec *ta, const Timespec *tb)
+{
+  if(ta == tb) return TRUE;
+  if(ta->tv_sec != tb->tv_sec) return FALSE;
+  if(ta->tv_nsec != tb->tv_nsec) return FALSE;
+  return TRUE;
+}
+
+gint
+timespec_cmp(const Timespec *ta, const Timespec *tb)
+{
+  if(ta == tb) return 0;
+  if(ta->tv_sec < tb->tv_sec) return -1;
+  if(ta->tv_sec > tb->tv_sec) return 1;
+  if(ta->tv_nsec < tb->tv_nsec) return -1;
+  if(ta->tv_nsec > tb->tv_nsec) return 1;
+  return 0;
+}
+
+Timespec
+timespec_diff(const Timespec *ta, const Timespec *tb)
+{
+  Timespec retval;
+  retval.tv_sec = ta->tv_sec - tb->tv_sec;
+  retval.tv_nsec = ta->tv_nsec - tb->tv_nsec;
+  timespec_normalize(&retval);
+  return retval;
+}
+
+Timespec
+timespec_abs(const Timespec *t)
+{
+  Timespec retval = *t;
+
+  timespec_normalize(&retval);
+  if (retval.tv_sec < 0)
+  {
+    retval.tv_sec = - retval.tv_sec;
+    retval.tv_nsec = - retval.tv_nsec;
+  }
+  
+  return retval;
+}
+
+/** \brief Converts any time on a day to midday that day.
+
+ * given a timepair contains any time on a certain day (local time)
+ * converts it to be midday that day.
+ */
+
+Timespec
+timespecCanonicalDayTime(Timespec t)
+{
+  struct tm tm, *result;
+  Timespec retval;
+  time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
+  result = localtime(&t_secs);
+  tm = *result;
+  gnc_tm_set_day_middle(&tm);
+  retval.tv_sec = mktime(&tm);
+  retval.tv_nsec = 0;
+  return retval;
+}
+
+int gnc_date_my_last_mday (int month, int year)
+{
+  gboolean is_leap;
+  static int days_in_month[2][12] =
+    {/* non leap */ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+     /*   leap   */ {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
+
+  /* Is this a leap year? */
+  if (year % 2000 == 0)
+    is_leap = TRUE;
+  else if (year % 400 == 0)
+      is_leap = FALSE;
+  else
+    is_leap = (year % 4 == 0);
+
+  return days_in_month[is_leap][month-1];
+}
+
+/* Retrieve the last numerical day of the month
+
+ Retrieve the last numerical day for the month specified in the
+ tm_year and tm_mon fields.
+
+param  tm: the time value in question
+return the last day of the month, integer.
+*/
+int date_get_last_mday(struct tm *tm)
+{
+  return gnc_date_my_last_mday (tm->tm_mon+1, tm->tm_year+1900);
+}
+
+/* Determines if tm_mday is the last day of the month.
+
+ Determines whether the tm_mday field contains the last day of the
+ month as specified in the tm_year and tm_mon fields.
+param  tm: the time value in question
+return TRUE if tm_mday matches the last day of the month, else FALSE.
+*/
+gboolean date_is_last_mday(struct tm *tm)
+{
+  return(tm->tm_mday == date_get_last_mday(tm));
+}
+
+/* Add a number of months to a time value
+
+ Add a number of months to a time value, and normalize.  Optionally
+ also track the last day of the month, i.e. 1/31 -> 2/28 -> 3/30.
+
+param  tm: base time value
+param  months: The number of months to add to this time
+param  track_last_day: Coerce the date value if necessary.
+
+return void
+*/
+void date_add_months (struct tm *tm, int months, gboolean track_last_day)
+{
+  gboolean was_last_day;
+  int new_last_mday;
+
+  /* Have to do this now */
+  was_last_day = date_is_last_mday(tm);
+
+  /* Add in the months and normalize */
+  tm->tm_mon += months;
+  while (tm->tm_mon > 11) {
+    tm->tm_mon -= 12;
+    tm->tm_year++;
+  }
+
+  if (!track_last_day)
+    return;
+
+  /* Track last day of the month, i.e. 1/31 -> 2/28 -> 3/30 */
+  new_last_mday = date_get_last_mday(tm);
+  if (was_last_day || (tm->tm_mday > new_last_mday))
+    tm->tm_mday = new_last_mday;
+}
+
+/* Return the set dateFormat.
+
+return QofDateFormat: enumeration indicating preferred format
+
+Global: dateFormat
+*/
+QofDateFormat qof_date_format_get (void)
+{
+  return dateFormat;
+}
+
+/* set date format
+
+set date format to one of US, UK, CE, ISO OR UTC
+checks to make sure it's a legal value
+
+param QofDateFormat: enumeration indicating preferred format
+
+return void
+
+Globals: dateFormat
+*/
+void qof_date_format_set(QofDateFormat df)
+{
+  if(df >= DATE_FORMAT_FIRST && df <= DATE_FORMAT_LAST)
+  {
+    prevQofDateFormat = dateFormat;
+    dateFormat = df;
+  }
+  else
+  {    /* hack alert - Use a neutral default. */
+    PERR("non-existent date format set attempted. Setting ISO default");
+    prevQofDateFormat = dateFormat;
+    dateFormat = QOF_DATE_FORMAT_ISO;
+  }
+
+  return;
+}
+
+/*
+ qof_date_format_get_string
+ get the date format string for the current format
+ returns: string
+
+ Globals: dateFormat
+*/
+const gchar *qof_date_format_get_string(QofDateFormat df)
+{
+  switch(df) {
+   case QOF_DATE_FORMAT_US:
+    return "%m/%d/%y";
+   case QOF_DATE_FORMAT_UK:
+    return "%d/%m/%y";
+   case QOF_DATE_FORMAT_CE:
+    return "%d.%m.%y";
+   case QOF_DATE_FORMAT_UTC:
+    return "%Y-%m-%dT%H:%M:%SZ";
+   case QOF_DATE_FORMAT_ISO:
+    return "%y-%m-%d";
+   case QOF_DATE_FORMAT_LOCALE:
+   default:
+    return GNC_D_FMT;
+  };
+}
+
+/* get the date format string for the current format
+
+get the date format string for the current format
+
+param df Required date format.
+return string
+
+Globals: dateFormat
+*/
+const gchar *qof_date_text_format_get_string(QofDateFormat df)
+{
+  switch(df) {
+   case QOF_DATE_FORMAT_US:
+    return "%b %d, %y";
+   case QOF_DATE_FORMAT_UK:
+   case QOF_DATE_FORMAT_CE:
+    return "%d %b, %y";
+   case QOF_DATE_FORMAT_UTC:
+    return "%Y-%m-%dT%H:%M:%SZ";
+   case QOF_DATE_FORMAT_ISO:
+    return "%y-%b-%d";
+   case QOF_DATE_FORMAT_LOCALE:
+   default:
+    return GNC_D_FMT;
+  };
+}
+
+/* Convert day, month and year values to a date string
+
+  Convert a date as day / month / year integers into a localized string
+  representation
+
+param   buff - pointer to previously allocated character array; its size
+         must be at lease MAX_DATE_LENTH bytes.
+param   day - value to be set with the day of the month as 1 ... 31
+param   month - value to be set with the month of the year as 1 ... 12
+param   year - value to be set with the year (4-digit)
+
+return void
+
+Globals: global dateFormat value
+*/
+size_t
+qof_print_date_dmy_buff (char * buff, size_t len, int day, int month, int year)
+{
+  int flen;
+  if (!buff) return 0;
+
+  /* Note that when printing year, we use %-4d in format string;
+   * this causes a one, two or three-digit year to be left-adjusted
+   * when printed (i.e. padded with blanks on the right).  This is 
+   * important while the user is editing the year, since erasing a 
+   * digit can temporarily cause a three-digit year, and having the 
+   * blank on the left is a real pain for the user.  So pad on the 
+   * right.
+   */
+  switch(dateFormat)
+  {
+    case QOF_DATE_FORMAT_UK:
+      flen = g_snprintf (buff, len, "%2d/%2d/%-4d", day, month, year);
+      break;
+    case QOF_DATE_FORMAT_CE:
+      flen = g_snprintf (buff, len, "%2d.%2d.%-4d", day, month, year);
+      break;
+    case QOF_DATE_FORMAT_LOCALE:
+      {
+        struct tm tm_str;
+	time_t t;
+
+        tm_str.tm_mday = day;
+        tm_str.tm_mon = month - 1;    /* tm_mon = 0 through 11 */
+        tm_str.tm_year = year - 1900; /* this is what the standard 
+                                       * says, it's not a Y2K thing */
+
+        gnc_tm_set_day_start (&tm_str);
+	t = mktime (&tm_str);
+	localtime_r (&t, &tm_str);
+        flen = strftime (buff, len, GNC_D_FMT, &tm_str);
+       if (flen != 0)
+         break;
+      }
+      /* FALLTHROUGH */
+    case QOF_DATE_FORMAT_ISO:
+    case QOF_DATE_FORMAT_UTC:
+      flen = g_snprintf (buff, len, "%04d-%02d-%02d", year, month, day);
+      break;
+    case QOF_DATE_FORMAT_US:
+    default:
+      flen = g_snprintf (buff, len, "%2d/%2d/%-4d", month, day, year);
+      break;
+  }
+
+  return flen;
+}
+
+size_t
+qof_print_date_buff (char * buff, size_t len, time_t t)
+{
+  struct tm *theTime;
+
+  if (!buff) return 0 ;
+
+  theTime = localtime (&t);
+
+  return qof_print_date_dmy_buff (buff, len,
+                   theTime->tm_mday, 
+                   theTime->tm_mon + 1,
+                   theTime->tm_year + 1900);
+}
+
+size_t
+qof_print_gdate( char *buf, size_t len, GDate *gd )
+{
+  return qof_print_date_dmy_buff( buf, len,
+             g_date_day(gd),
+             g_date_month(gd),
+             g_date_year(gd) );
+}
+
+char * 
+qof_print_date (time_t t)
+{
+   char buff[MAX_DATE_LENGTH];
+   qof_print_date_buff (buff, MAX_DATE_LENGTH, t);
+   return g_strdup (buff);
+}
+
+const char *
+gnc_print_date (Timespec ts)
+{
+  static char buff[MAX_DATE_LENGTH];
+  time_t t;
+
+  t = ts.tv_sec + (ts.tv_nsec / 1000000000.0);
+
+  qof_print_date_buff (buff, MAX_DATE_LENGTH, t);
+
+  return buff;
+}
+
+/* ============================================================== */
+
+size_t
+qof_print_hours_elapsed_buff (char * buff, size_t len, int secs, gboolean show_secs)
+{
+	size_t flen;
+	if (0 <= secs)
+	{
+		if (show_secs)
+		{
+			flen = g_snprintf(buff, len,
+			   "%02d:%02d:%02d", (int)(secs / 3600),
+			   (int)((secs % 3600) / 60), (int)(secs % 60));
+		}
+		else
+		{
+			flen = g_snprintf(buff, len, 
+			   "%02d:%02d", (int)(secs / 3600),
+			   (int)((secs % 3600) / 60));
+		}
+	} 
+	else 
+	{
+		if (show_secs)
+		{
+			flen = g_snprintf(buff, len,
+			   "-%02d:%02d:%02d", (int)(-secs / 3600),
+			   (int)((-secs % 3600) / 60), (int)(-secs % 60));
+		}
+		else
+		{
+			flen = g_snprintf(buff, len,
+			   "-%02d:%02d", (int)(-secs / 3600),
+			   (int)((-secs % 3600) / 60));
+		}
+	}
+	return flen;
+}
+
+/* ============================================================== */
+
+size_t
+qof_print_minutes_elapsed_buff (char * buff, size_t len, int secs, gboolean show_secs)
+{
+	size_t flen;
+	if (0 <= secs)
+	{
+		if (show_secs)
+		{
+			flen = g_snprintf(buff, len,
+			   "%02d:%02d", 
+				(int)(secs / 60), (int)(secs % 60));
+		}
+		else
+		{
+			flen = g_snprintf(buff, len, 
+			   "%02d", (int)(secs / 60));
+		}
+	} 
+	else 
+	{
+		if (show_secs)
+		{
+			flen = g_snprintf(buff, len,
+			   "-%02d:%02d", (int)(-secs / 60), (int)(-secs % 60));
+		}
+		else
+		{
+			flen = g_snprintf(buff, len,
+			   "-%02d", (int)(-secs / 60));
+		}
+	}
+	return flen;
+}
+
+/* ============================================================== */
+
+size_t
+qof_print_date_time_buff (char * buff, size_t len, time_t secs)
+{
+  int flen;
+  int day, month, year, hour, min, sec;
+  struct tm ltm, gtm;
+  
+  if (!buff) return 0;
+
+  /* Note that when printing year, we use %-4d in format string;
+   * this causes a one, two or three-digit year to be left-adjusted
+   * when printed (i.e. padded with blanks on the right).  This is 
+   * important while the user is editing the year, since erasing a 
+   * digit can temporarily cause a three-digit year, and having the 
+   * blank on the left is a real pain for the user.  So pad on the 
+   * right.
+   */
+  ltm = *localtime (&secs);
+  day = ltm.tm_mday;
+  month = ltm.tm_mon +1;
+  year = ltm.tm_year +1900;
+  hour = ltm.tm_hour;
+  min = ltm.tm_min;
+  sec = ltm.tm_sec;
+  
+  switch(dateFormat)
+  {
+    case QOF_DATE_FORMAT_UK:
+      flen = g_snprintf (buff, len, "%2d/%2d/%-4d %2d:%02d", day, month, year, hour, min);
+      break;
+    case QOF_DATE_FORMAT_CE:
+      flen = g_snprintf (buff, len, "%2d.%2d.%-4d %2d:%02d", day, month, year, hour, min);
+      break;
+    case QOF_DATE_FORMAT_ISO:
+      flen = g_snprintf (buff, len, "%04d-%02d-%02d %02d:%02d", year, month, day, hour, min);
+      break;
+	case QOF_DATE_FORMAT_UTC:
+	{
+		gtm = *gmtime (&secs);
+		flen = strftime (buff, len, QOF_UTC_DATE_FORMAT, &gtm);
+		break;
+	}
+    case QOF_DATE_FORMAT_LOCALE:
+      {
+        flen = strftime (buff, len, GNC_D_T_FMT, &ltm);
+      }
+      break;
+
+    case QOF_DATE_FORMAT_US:
+    default:
+      flen = g_snprintf (buff, len, "%2d/%2d/%-4d %2d:%02d", month, day, year, hour, min);
+      break;
+  }
+  return flen;
+}
+
+size_t 
+qof_print_time_buff (char * buff, size_t len, time_t secs)
+{
+	int flen;
+	struct tm ltm, gtm;
+	
+	if (!buff) return 0;
+	if(dateFormat == QOF_DATE_FORMAT_UTC)
+	{
+		gtm = *gmtime (&secs);
+		flen = strftime(buff, len, QOF_UTC_DATE_FORMAT, &gtm);
+		return flen;
+	}
+	ltm = *localtime (&secs);
+	flen = strftime (buff, len, GNC_T_FMT, &ltm);
+	
+	return flen;
+}
+
+/* ============================================================== */
+
+int
+qof_is_same_day (time_t ta, time_t tb)
+{
+  struct tm lta, ltb;
+  lta = *localtime (&ta);
+  ltb = *localtime (&tb);
+  if (lta.tm_year == ltb.tm_year)
+  {
+    return (ltb.tm_yday - lta.tm_yday);
+  }
+  return (ltb.tm_year - lta.tm_year)*365;  /* very approximate */
+}
+
+/* ============================================================== */
+
+/* Convert a string into  day, month and year integers
+
+    Convert a string into  day / month / year integers according to
+    the current dateFormat value.
+
+    This function will always parse a single number as the day of
+    the month, regardless of the ordering of the dateFormat value.
+    Two numbers will always be parsed as the day and the month, in
+    the same order that they appear in the dateFormat value.  Three
+    numbers are parsed exactly as specified in the dateFormat field.
+
+    Fully formatted UTC timestamp strings are converted separately.
+
+param   buff - pointer to date string
+param     day -  will store day of the month as 1 ... 31
+param     month - will store month of the year as 1 ... 12
+param     year - will store the year (4-digit)
+
+return TRUE if date appeared to be valid.
+
+ Globals: global dateFormat value
+*/
+static gboolean
+qof_scan_date_internal (const char *buff, int *day, int *month, int *year,
+                  QofDateFormat which_format)
+{
+   char *dupe, *tmp, *first_field, *second_field, *third_field;
+   int iday, imonth, iyear;
+   struct tm *now, utc;
+   time_t secs;
+
+   if (!buff) return(FALSE);
+
+	if(which_format == QOF_DATE_FORMAT_UTC)
+	{
+		if(strptime(buff, QOF_UTC_DATE_FORMAT, &utc)) {
+			*day = utc.tm_mday;
+			*month = utc.tm_mon + 1;
+			*year = utc.tm_year + 1900;
+			return TRUE;
+		}
+		else { return FALSE; }
+	}
+   dupe = g_strdup (buff);
+
+   tmp = dupe;
+   first_field = NULL;
+   second_field = NULL;
+   third_field = NULL;
+
+   /* Use strtok to find delimiters */
+   if (tmp) {
+     static char *delims = ".,-+/\\() ";
+
+      first_field = strtok (tmp, delims);
+      if (first_field) {
+         second_field = strtok (NULL, delims);
+         if (second_field) {
+            third_field = strtok (NULL, delims);
+         }
+      }
+   }
+
+   /* If any fields appear to be blank, use today's date */
+   time (&secs);
+   now = localtime (&secs);
+   iday = now->tm_mday; 
+   imonth = now->tm_mon+1;
+   iyear = now->tm_year+1900;
+
+   /* get numeric values */
+   switch (which_format)
+   {
+     case QOF_DATE_FORMAT_LOCALE:
+       if (buff[0] != '\0')
+       {
+         struct tm thetime;
+
+         /* Parse time string. */
+         memset(&thetime, -1, sizeof(struct tm));
+         strptime (buff, GNC_D_FMT, &thetime);
+
+         if (third_field) {
+           /* Easy.  All three values were parsed. */
+           iyear = thetime.tm_year + 1900;
+           iday = thetime.tm_mday;
+           imonth = thetime.tm_mon + 1;
+         } else if (second_field) {
+           /* Hard. Two values parsed.  Figure out the ordering. */
+           if (thetime.tm_year == -1) {
+             /* %m-%d or %d-%m. Don't care. Already parsed correctly. */
+             iday = thetime.tm_mday;
+             imonth = thetime.tm_mon + 1;
+           } else if (thetime.tm_mon != -1) {
+             /* Must be %Y-%m-%d. Reparse as %m-%d.*/
+             imonth = atoi(first_field);
+             iday = atoi(second_field);
+           } else {
+             /* Must be %Y-%d-%m. Reparse as %d-%m. */
+             iday = atoi(first_field);
+             imonth = atoi(second_field);
+           }
+         } else if (first_field) {
+           iday = atoi(first_field);
+         }
+       }
+       break;
+     case QOF_DATE_FORMAT_UK:
+     case QOF_DATE_FORMAT_CE:
+       if (third_field) {
+         iday = atoi(first_field);
+         imonth = atoi(second_field);
+         iyear = atoi(third_field);
+       } else if (second_field) {
+         iday = atoi(first_field);
+         imonth = atoi(second_field);
+       } else if (first_field) {
+         iday = atoi(first_field);
+       }
+       break;
+     case QOF_DATE_FORMAT_ISO:
+       if (third_field) {
+         iyear = atoi(first_field);
+         imonth = atoi(second_field);
+         iday = atoi(third_field);
+       } else if (second_field) {
+         imonth = atoi(first_field);
+         iday = atoi(second_field);
+       } else if (first_field) {
+         iday = atoi(first_field);
+       }
+       break;
+    case QOF_DATE_FORMAT_US:
+    default:
+       if (third_field) {
+         imonth = atoi(first_field);
+         iday = atoi(second_field);
+         iyear = atoi(third_field);
+       } else if (second_field) {
+         imonth = atoi(first_field);
+         iday = atoi(second_field);
+       } else if (first_field) {
+         iday = atoi(first_field);
+       }
+       break;
+   }
+
+   g_free (dupe);
+
+   if ((12 < imonth) || (31 < iday)) 
+   {
+     /* 
+      * Ack! Thppfft!  Someone just fed this routine a string in the
+      * wrong date format.  This is known to happen if a register
+      * window is open when changing the date format.  Try the
+      * previous date format.  If that doesn't work, see if we can
+      * exchange month and day. If that still doesn't work,
+      * bail and give the caller what they asked for (garbage) 
+      * parsed in the new format.
+      *
+      * Note: This test cannot detect any format change that only
+      * swaps month and day field, if the day is 12 or less.  This is
+      * deemed acceptable given the obscurity of this bug.
+      */
+     if ((which_format != prevQofDateFormat) &&
+         qof_scan_date_internal(buff, day, month, year, prevQofDateFormat))
+     {
+       return(TRUE);
+     }
+     if ((12 < imonth) && (12 >= iday))
+     {
+        int tmp = imonth; imonth = iday; iday = tmp;
+     } 
+	  else
+	  {
+        return FALSE;
+	  }
+   }
+
+   /* If the year entered is smaller than 100, assume we mean the current
+      century (and are not revising some roman emperor's books) */
+   if (iyear < 100)
+     iyear += ((int) ((now->tm_year+1950-iyear)/100)) * 100;
+
+   if (year) *year=iyear;
+   if (month) *month=imonth;
+   if (day) *day=iday;
+   return(TRUE);
+}
+
+gboolean
+qof_scan_date (const char *buff, int *day, int *month, int *year)
+{
+  return qof_scan_date_internal(buff, day, month, year, dateFormat);
+}
+
+gboolean
+qof_scan_date_secs (const char *buff, time_t *secs)
+{
+  gboolean rc;
+  int day, month, year;
+  
+  rc = qof_scan_date_internal(buff, &day, &month, &year, dateFormat);
+  if (secs) *secs = xaccDMYToSec (day, month, year);
+
+  return rc;
+}
+
+/* Return the field separator for the current date format
+return date character
+*/
+char dateSeparator (void)
+{
+  static char locale_separator = '\0';
+
+  switch (dateFormat)
+  {
+    case QOF_DATE_FORMAT_CE:
+      return '.';
+    case QOF_DATE_FORMAT_ISO:
+    case QOF_DATE_FORMAT_UTC:
+      return '-';
+    case QOF_DATE_FORMAT_US:
+    case QOF_DATE_FORMAT_UK:
+    default:
+      return '/';
+    case QOF_DATE_FORMAT_LOCALE:
+      if (locale_separator != '\0')
+        return locale_separator;
+      else
+      { /* Make a guess */
+        char string[256];
+        struct tm *tm;
+        time_t secs;
+        char *s;
+
+        secs = time(NULL);
+        tm = localtime(&secs);
+        strftime(string, sizeof(string), GNC_D_FMT, tm);
+
+        for (s = string; s != '\0'; s++)
+          if (!isdigit(*s))
+            return (locale_separator = *s);
+      }
+  }
+
+  return '\0';
+}
+
+/********************************************************************\
+\********************************************************************/
+                                                                                
+/* Convert time in seconds to a textual.
+
+The xaccDateUtilGetStamp() routine will take the given time in
+seconds and return a buffer containing a textual for the date.
+
+param thyme The time in seconds to convert.
+return A pointer to the generated string.
+The caller owns this buffer and must free it when done.
+*/
+char *
+xaccDateUtilGetStamp (time_t thyme)
+{
+   struct tm *stm;
+                                                                                
+   stm = localtime (&thyme);
+                                                                                
+   return g_strdup_printf("%04d%02d%02d%02d%02d%02d",
+      (stm->tm_year + 1900),
+      (stm->tm_mon +1),
+      stm->tm_mday,
+      stm->tm_hour,
+      stm->tm_min,
+      stm->tm_sec
+   );
+}
+                                                                                
+                                                                                
+/* Convert textual to time in seconds.
+
+The xaccDateUtilGetStampNow() routine returns the current time in
+seconds in textual format.
+
+return A pointer to the generated string.
+
+note The caller owns this buffer and must free it when done.
+*/
+char *
+xaccDateUtilGetStampNow (void)
+{
+   time_t now;
+   time (&now);
+   return xaccDateUtilGetStamp (now);
+}
+
+/********************************************************************\
+ * iso 8601 datetimes should look like 1998-07-02 11:00:00.68-05
+\********************************************************************/
+/* hack alert -- this routine returns incorrect values for 
+ * dates before 1970 */
+
+Timespec
+gnc_iso8601_to_timespec_gmt(const char *str)
+{
+  char buf[4];
+  Timespec ts;
+  struct tm stm;
+  long int nsec =0;
+
+  ts.tv_sec=0;
+  ts.tv_nsec=0;
+  if (!str) return ts;
+
+  stm.tm_year = atoi(str) - 1900;
+  str = strchr (str, '-'); if (str) { str++; } else { return ts; }
+  stm.tm_mon = atoi(str) - 1;
+  str = strchr (str, '-'); if (str) { str++; } else { return ts; }
+  stm.tm_mday = atoi(str);
+
+  str = strchr (str, ' '); if (str) { str++; } else { return ts; }
+  stm.tm_hour = atoi(str);
+  str = strchr (str, ':'); if (str) { str++; } else { return ts; }
+  stm.tm_min = atoi(str);
+  str = strchr (str, ':'); if (str) { str++; } else { return ts; }
+  stm.tm_sec = atoi (str);
+
+  /* The decimal point, optionally present ... */
+  /* hack alert -- this algo breaks if more than 9 decimal places present */
+  if (strchr (str, '.')) 
+  { 
+     int decimals, i, multiplier=1000000000;
+     str = strchr (str, '.') +1;
+     decimals = strcspn (str, "+- ");
+     for (i=0; i<decimals; i++) multiplier /= 10;
+     nsec = atoi(str) * multiplier;
+  }
+  stm.tm_isdst = -1;
+
+  /* Timezone format can be +hh or +hhmm or +hh.mm (or -) (or not present) */
+  str += strcspn (str, "+-");
+  if (str)
+  {
+    buf[0] = str[0];
+    buf[1] = str[1];
+    buf[2] = str[2];
+    buf[3] = 0;
+    stm.tm_hour -= atoi(buf);
+
+    str +=3;
+    if ('.' == *str) str++;
+    if (isdigit (*str) && isdigit (*(str+1)))
+    {
+      int cyn;
+      /* copy sign from hour part */
+      if ('+' == buf[0]) { cyn = -1; } else { cyn = +1; } 
+      buf[0] = str[0];
+      buf[1] = str[1];
+      buf[2] = str[2];
+      buf[3] = 0;
+      stm.tm_min += cyn * atoi(buf);
+    }
+  }
+
+  /* Note that mktime returns 'local seconds' which is the true time
+   * minus the timezone offset.  We don't want to work with local 
+   * seconds, since they swim around acording to daylight savings, etc. 
+   * We want to work with universal time.  Thus, add an offset
+   * to undo the damage that mktime causes.
+   */
+  {
+    struct tm tmp_tm;
+    struct tm *tm;
+    long int tz;
+    int tz_hour;
+    time_t secs;
+
+    /* Use a temporary tm struct so the mktime call below
+     * doesn't mess up stm. */
+    tmp_tm = stm;
+    tmp_tm.tm_isdst = -1;
+
+    secs = mktime (&tmp_tm);
+
+    /* The call to localtime is 'bogus', but it forces 'timezone' to
+     * be set. Note that we must use the accurate date, since the
+     * value of 'gnc_timezone' includes daylight savings corrections
+     * for that date. */
+    tm = localtime (&secs);
+
+    tz = gnc_timezone (tm);
+
+    tz_hour = tz / 3600;
+    stm.tm_hour -= tz_hour;
+    stm.tm_min -= (tz - (3600 * tz_hour)) / 60;
+    stm.tm_isdst = tmp_tm.tm_isdst;
+  }
+
+  ts.tv_sec = mktime (&stm);
+  ts.tv_nsec = nsec;
+
+  return ts;
+}
+
+/********************************************************************\
+\********************************************************************/
+
+char * 
+gnc_timespec_to_iso8601_buff (Timespec ts, char * buff)
+{
+  int len;
+  int tz_hour, tz_min;
+  char cyn;
+  time_t tmp;
+  struct tm parsed;
+
+  tmp = ts.tv_sec;
+  localtime_r(&tmp, &parsed);
+
+  tz_hour = gnc_timezone (&parsed) / 3600;
+  tz_min = (gnc_timezone (&parsed) - 3600*tz_hour) / 60;
+  if (0>tz_min) { tz_min +=60; tz_hour --; }
+  if (60<=tz_min) { tz_min -=60; tz_hour ++; }
+
+  /* We also have to print the sign by hand, to work around a bug
+   * in the glibc 2.1.3 printf (where %+02d fails to zero-pad).
+   */
+  cyn = '-';
+  if (0>tz_hour) { cyn = '+'; tz_hour = -tz_hour; }
+
+  len = sprintf (buff, "%4d-%02d-%02d %02d:%02d:%02d.%06ld %c%02d%02d",
+                 parsed.tm_year + 1900,
+                 parsed.tm_mon + 1,
+                 parsed.tm_mday,
+                 parsed.tm_hour,
+                 parsed.tm_min,
+                 parsed.tm_sec,
+                 ts.tv_nsec / 1000,
+                 cyn,
+                 tz_hour,
+                 tz_min);
+
+  /* Return pointer to end of string. */
+  buff += len;
+  return buff;
+}
+
+int
+gnc_timespec_last_mday (Timespec t)
+{
+  struct tm *result;
+  time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
+  result = localtime(&t_secs);
+  return date_get_last_mday (result);
+}
+
+void
+gnc_timespec2dmy (Timespec t, int *day, int *month, int *year)
+{
+  struct tm *result;
+  time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
+  result = localtime(&t_secs);
+
+  if (day) *day = result->tm_mday;
+  if (month) *month = result->tm_mon+1;
+  if (year) *year = result->tm_year+1900;
+}
+
+/********************************************************************\
+\********************************************************************/
+/* hack alert -- this routine returns incorrect values for 
+ * dates before 1970 */
+
+time_t 
+xaccDMYToSec (int day, int month, int year)
+{
+  struct tm stm;
+  time_t secs;
+
+  stm.tm_year = year - 1900;
+  stm.tm_mon = month - 1;
+  stm.tm_mday = day;
+  gnc_tm_set_day_start(&stm);
+
+  /* compute number of seconds */
+  secs = mktime (&stm);
+
+  return secs;
+}
+
+
+#define THIRTY_TWO_YEARS 0x3c30fc00LL
+
+static Timespec
+gnc_dmy2timespec_internal (int day, int month, int year, gboolean start_of_day)
+{
+  Timespec result;
+  struct tm date;
+  long long secs = 0;
+  long long era = 0;
+
+  year -= 1900;
+
+  /* make a crude attempt to deal with dates outside the range of Dec
+   * 1901 to Jan 2038. Note we screw up centennial leap years here so
+   * hack alert */
+  if ((2 > year) || (136 < year)) 
+  {
+    era = year / 32;
+    year %= 32;
+    if (0 > year) { year += 32; era -= 1; } 
+  }
+
+  date.tm_year = year;
+  date.tm_mon = month - 1;
+  date.tm_mday = day;
+
+  if (start_of_day)
+    gnc_tm_set_day_start(&date);
+  else
+    gnc_tm_set_day_end(&date);
+
+  /* compute number of seconds */
+  secs = mktime (&date);
+
+  secs += era * THIRTY_TWO_YEARS;
+
+  result.tv_sec = secs;
+  result.tv_nsec = 0;
+
+  return result;
+}
+
+Timespec
+gnc_dmy2timespec (int day, int month, int year)
+{
+  return gnc_dmy2timespec_internal (day, month, year, TRUE);
+}
+
+Timespec
+gnc_dmy2timespec_end (int day, int month, int year)
+{
+  return gnc_dmy2timespec_internal (day, month, year, FALSE);
+}
+
+/********************************************************************\
+\********************************************************************/
+
+long int
+gnc_timezone (struct tm *tm)
+{
+  g_return_val_if_fail (tm != NULL, 0);
+
+#ifdef HAVE_STRUCT_TM_GMTOFF
+  /* tm_gmtoff is seconds *east* of UTC and is
+   * already adjusted for daylight savings time. */
+  return -(tm->tm_gmtoff);
+#else
+  /* timezone is seconds *west* of UTC and is
+   * not adjusted for daylight savings time.
+   * In Spring, we spring forward, wheee! */
+  return timezone - (tm->tm_isdst > 0 ? 60 * 60 : 0);
+#endif
+}
+
+
+void
+timespecFromTime_t( Timespec *ts, time_t t )
+{
+    ts->tv_sec = t;
+    ts->tv_nsec = 0;
+}
+
+time_t 
+timespecToTime_t (Timespec ts)
+{
+    return ts.tv_sec;
+}
+
+void
+gnc_tm_get_day_start (struct tm *tm, time_t time_val)
+{
+  /* Get the equivalent time structure */
+  tm = localtime_r(&time_val, tm);
+  gnc_tm_set_day_start(tm);
+}
+
+void
+gnc_tm_get_day_end (struct tm *tm, time_t time_val)
+{
+  /* Get the equivalent time structure */
+  tm = localtime_r(&time_val, tm);
+  gnc_tm_set_day_end(tm);
+}
+
+time_t
+gnc_timet_get_day_start (time_t time_val)
+{
+  struct tm tm;
+
+  gnc_tm_get_day_start(&tm, time_val);
+  return mktime(&tm);
+}
+
+time_t
+gnc_timet_get_day_end (time_t time_val)
+{
+  struct tm tm;
+
+  gnc_tm_get_day_end(&tm, time_val);
+  return mktime(&tm);
+}
+
+
+#ifndef GNUCASH_MAJOR_VERSION
+time_t
+gnc_timet_get_day_start_gdate (GDate *date)
+{
+  struct tm stm;
+  time_t secs;
+
+  stm.tm_year = g_date_year (date) - 1900;
+  stm.tm_mon = g_date_month (date) - 1;
+  stm.tm_mday = g_date_day (date);
+  gnc_tm_set_day_start(&stm);
+
+  /* Compute number of seconds */
+  secs = mktime (&stm);
+  return secs;
+}
+
+time_t
+gnc_timet_get_day_end_gdate (GDate *date)
+{
+  struct tm stm;
+  time_t secs;
+
+  stm.tm_year = g_date_year (date) - 1900;
+  stm.tm_mon = g_date_month (date) - 1;
+  stm.tm_mday = g_date_day (date);
+  gnc_tm_set_day_end(&stm);
+
+  /* Compute number of seconds */
+  secs = mktime (&stm);
+  return secs;
+}
+#endif /* GNUCASH_MAJOR_VERSION */
+
+/* ======================================================== */
+
+void
+gnc_tm_get_today_start (struct tm *tm)
+{
+  gnc_tm_get_day_start(tm, time(NULL));
+}
+
+void
+gnc_tm_get_today_end (struct tm *tm)
+{
+  gnc_tm_get_day_end(tm, time(NULL));
+}
+
+time_t
+gnc_timet_get_today_start (void)
+{
+  struct tm tm;
+
+  gnc_tm_get_day_start(&tm, time(NULL));
+  return mktime(&tm);
+}
+
+time_t
+gnc_timet_get_today_end (void)
+{
+  struct tm tm;
+
+  gnc_tm_get_day_end(&tm, time(NULL));
+  return mktime(&tm);
+}
+
+/********************** END OF FILE *********************************\
+\********************************************************************/

Added: gnucash/trunk/lib/libqof/qof/gnc-date.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-date.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-date.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,539 @@
+/********************************************************************\
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+\********************************************************************/
+/** @addtogroup Date
+    Utility functions to handle date and time (adjusting, getting 
+    the current date, printing the date and time, etc.) 
+
+    Overall, this file is quite a mess.  Note, however, that other 
+    applications, besides just GnuCash, use this file.  In particular,
+    GnoTime (gttr.sourcefore.net) uses this file, and this file is 
+    formally a part of QOF (qof.sourceforge.net).
+
+    An important note about time-keeping:  The general goal of any 
+    program that works with numeric time values SHOULD BE to always
+    stores and use UNIVERSAL TIME internally.  Universal time is the
+    'one true time' that is independent of one's location on planet
+    Earth.  It is measured in seconds from midnight January 1, 1970
+    in localtime-Grenwich (GMT).  If one wants to display the local
+    time, then the display-print routine should make all final 
+    tweaks to print the local time.   The local time *must not* be
+    kept as a numeric value anywhere in the program.   If one wants
+    to parse a user's input string as if it were local time, then
+    the output of the parse routine MUST BE universal time.  
+    A sane program must never ever store (to file or db) a time 
+    that is not Universal Time.  Break these rules, and you will 
+    rue the day...
+
+    \warning HACK ALERT -- the scan and print routines should probably be moved
+    to somewhere else. The engine really isn't involved with things
+    like printing formats. This is needed mostly by the GUI and so on.
+    If a file-io backend needs date handling, it should do it itself,
+    instead of depending on the routines here. 
+
+    @author Copyright (C) 1997 Robin D. Clark <rclark at cs.hmc.edu> 
+    @author Copyright (C) 1998-2001,2003 Linas Vepstas <linas at linas.org>
+*/
+
+/* @{ 
+    @file gnc-date.h 
+    @brief Date and Time handling routines  
+*/
+    
+#ifndef GNC_DATE_H
+#define GNC_DATE_H
+
+#include <glib.h>
+#include <time.h>
+
+/** The maximum length of a string created by the date printers */
+#define MAX_DATE_LENGTH 31
+
+/** Constants *******************************************************/
+/** \brief UTC date format string.
+
+Timezone independent, date and time inclusive, as used in the QSF backend.
+The T and Z characters are from xsd:dateTime format in coordinated universal time, UTC.
+You can reproduce the string from the GNU/Linux command line using the date utility:
+date -u +%Y-%m-%dT%H:M:SZ = 2004-12-12T23:39:11Z The datestring must be timezone independent 
+and include all specified fields. Remember to use gmtime() NOT localtime()! 
+*/
+
+#define QOF_UTC_DATE_FORMAT     "%Y-%m-%dT%H:%M:%SZ"
+
+/** Enum for determining a date format */
+typedef enum
+{
+  QOF_DATE_FORMAT_US,       /**< United states: mm/dd/yyyy */
+  QOF_DATE_FORMAT_UK,       /**< Britain: dd/mm/yyyy */
+  QOF_DATE_FORMAT_CE,       /**< Continental Europe: dd.mm.yyyy */
+  QOF_DATE_FORMAT_ISO,      /**< ISO: yyyy-mm-dd */
+  QOF_DATE_FORMAT_UTC,      /**< UTC: 2004-12-12T23:39:11Z */
+  QOF_DATE_FORMAT_LOCALE,   /**< Take from locale information */
+  QOF_DATE_FORMAT_CUSTOM    /**< Used by the check printing code */
+} QofDateFormat;
+
+#define DATE_FORMAT_FIRST QOF_DATE_FORMAT_US
+#define DATE_FORMAT_LAST  QOF_DATE_FORMAT_LOCALE
+
+
+/**
+ * This is how to format the month, as a number, an abbreviated string,
+ * or the full name.
+ */
+typedef enum {
+  GNCDATE_MONTH_NUMBER,
+  GNCDATE_MONTH_ABBREV,
+  GNCDATE_MONTH_NAME
+} GNCDateMonthFormat;
+
+
+/** \name String / DateFormat conversion. */
+//@{ 
+
+/** \brief The string->value versions return FALSE on success and TRUE on failure */
+const char* gnc_date_dateformat_to_string(QofDateFormat format);
+
+/** \brief Converts the date format to a printable string.
+
+Note the reversed return values!
+ at return FALSE on success, TRUE on failure.
+*/
+gboolean gnc_date_string_to_dateformat(const char* format_string,
+				       QofDateFormat *format);
+
+const char* gnc_date_monthformat_to_string(GNCDateMonthFormat format);
+
+/** \brief Converts the month format to a printable string.
+
+Note the reversed return values!
+ at return FALSE on success, TRUE on failure.
+*/
+gboolean gnc_date_string_to_monthformat(const char *format_string,
+					GNCDateMonthFormat *format);
+// @}
+
+/* Datatypes *******************************************************/
+
+/** \brief Use a 64-bit signed int timespec
+ *
+ * struct timespec64 is just like the unix 'struct timespec' except 
+ * that we use a 64-bit
+ * signed int to store the seconds.  This should adequately cover
+ * dates in the distant future as well as the distant past, as long as
+ * they're not more than a couple dozen times the age of the universe.
+ * Note that both gcc and the IBM Toronto xlC compiler (aka CSet,
+ * VisualAge, etc) correctly handle long long as a 64 bit quantity,
+ * even on the 32-bit Intel x86 and PowerPC architectures.  I'm
+ * assuming that all the other modern compilers are clean on this
+ * issue too. */
+
+#ifndef SWIG   /* swig 1.1p5 can't hack the long long type */
+struct timespec64
+{
+   long long int tv_sec;     
+   long int tv_nsec;
+};
+#endif /* SWIG */
+
+/** The Timespec is just like the unix 'struct timespec' 
+ * except that we use a 64-bit signed int to
+ * store the seconds.  This should adequately cover dates in the
+ * distant future as well as the distant past, as long as they're not
+ * more than a couple dozen times the age of the universe.  Note that
+ * both gcc and the IBM Toronto xlC compiler (aka CSet, VisualAge,
+ * etc) correctly handle long long as a 64 bit quantity, even on the
+ * 32-bit Intel x86 and PowerPC architectures.  I'm assuming that all
+ * the other modern compilers are clean on this issue too. */
+typedef struct timespec64 Timespec;
+
+
+/* Prototypes ******************************************************/
+
+/** \name Timespec functions */
+// @{ 
+/** strict equality */
+gboolean timespec_equal(const Timespec *ta, const Timespec *tb);
+
+/** comparison:  if (ta < tb) -1; else if (ta > tb) 1; else 0; */
+int      timespec_cmp(const Timespec *ta, const Timespec *tb);
+
+/** difference between ta and tb, results are normalised
+ * ie tv_sec and tv_nsec of the result have the same size
+ * abs(result.tv_nsec) <= 1000000000 */
+Timespec timespec_diff(const Timespec *ta, const Timespec *tb);
+
+/** absolute value, also normalised */
+Timespec timespec_abs(const Timespec *t);
+
+/** convert a timepair on a certain day (localtime) to
+ * the timepair representing midday on that day */
+Timespec timespecCanonicalDayTime(Timespec t);
+
+/** Turns a time_t into a Timespec */
+void timespecFromTime_t( Timespec *ts, time_t t );
+
+/** Turns a Timespec into a time_t */
+time_t timespecToTime_t (Timespec ts);
+
+/** Convert a day, month, and year to a Timespec */
+Timespec gnc_dmy2timespec (int day, int month, int year);
+
+/** Same as gnc_dmy2timespec, but last second of the day */
+Timespec gnc_dmy2timespec_end (int day, int month, int year);
+
+/** The gnc_iso8601_to_timespec_gmt() routine converts an ISO-8601 style 
+ *    date/time string to Timespec.  Please note that ISO-8601 strings
+ *    are a representation of Universal Time (UTC), and as such, they
+ *    'store' UTC.  To make them human readable, they show timezone 
+ *    information along with a local-time string.  But fundamentally,
+ *    they *are* UTC.  Thus, thir routine takes a UTC input, and 
+ *    returns a UTC output.
+ *
+ *    For example: 1998-07-17 11:00:00.68-0500 
+ *    is 680 milliseconds after 11 o'clock, central daylight time 
+ *    It is also 680 millisecs after 16:00:00 hours UTC.
+ *    \return The universl time.
+ *
+ * XXX Caution: this routine does not handle strings that specify
+ * times before January 1 1970.
+ */
+Timespec gnc_iso8601_to_timespec_gmt(const char *);
+
+/** The gnc_timespec_to_iso8601_buff() routine takes the input 
+ *    UTC Timespec value and prints it as an ISO-8601 style string.  
+ *    The buffer must be long enough to contain the NULL-terminated 
+ *    string (32 characters + NUL).  This routine returns a pointer 
+ *    to the null terminator (and can thus be used in the 'stpcpy' 
+ *    metaphor of string concatenation).
+ *
+ *    Please note that ISO-8601 strings are a representation of 
+ *    Universal Time (UTC), and as such, they 'store' UTC.  To make them 
+ *    human readable, they show timezone information along with a 
+ *    local-time string.  But fundamentally, they *are* UTC.  Thus,
+ *    this routine takes a UTC input, and returns a UTC output.
+ *
+ *    The string generated by this routine uses the local timezone
+ *    on the machine on which it is executing to create the timestring.
+ */
+char * gnc_timespec_to_iso8601_buff (Timespec ts, char * buff);
+
+/** DOCUMENT ME! FIXME: Probably similar to xaccDMYToSec() this date
+ * routine might return incorrect values for dates before 1970.  */
+void gnc_timespec2dmy (Timespec ts, int *day, int *month, int *year);
+
+/** Add a number of months to a time value and normalize.  Optionally
+ * also track the last day of the month, i.e. 1/31 -> 2/28 -> 3/30. */
+void date_add_months (struct tm *tm, int months, gboolean track_last_day);
+
+/** \warning hack alert XXX FIXME -- these date routines return incorrect
+ * values for dates before 1970.  Most of them are good only up 
+ * till 2038.  This needs fixing ... 
+ *
+ * XXX  This routine should be modified to assume that the 
+ * the user wanted the time at noon, localtime.  The returned 
+ * time_t should be seconds (at GMT) of the local noon-time.
+*/
+time_t xaccDMYToSec (int day, int month, int year);
+
+/** The gnc_timezone function returns the number of seconds *west*
+ * of UTC represented by the tm argument, adjusted for daylight
+ * savings time.
+ *
+ * This function requires a tm argument returned by localtime or set
+ * by mktime. This is a strange function! It requires that localtime
+ * or mktime be called before use. Subsequent calls to localtime or
+ * mktime *may* invalidate the result! The actual contents of tm *may*
+ * be used for both timezone offset and daylight savings time, or only
+ * daylight savings time! Timezone stuff under unix is not
+ * standardized and is a big mess.
+ */
+long int gnc_timezone (struct tm *tm);
+// @}
+
+/* ------------------------------------------------------------------------ */
+/** \name QofDateFormat functions */
+// @{
+/** The qof_date_format_get routine returns the date format that
+ *  the date printing will use when printing a date, and the scaning 
+ *  routines will assume when parsing a date.
+ * @returns: the one of the enumerated date formats.
+ */
+QofDateFormat qof_date_format_get(void);
+
+/** 
+ * The qof_date_format_set() routine sets date format to one of
+ *    US, UK, CE, OR ISO.  Checks to make sure it's a legal value.
+ *    Args: QofDateFormat: enumeration indicating preferred format
+ */
+void qof_date_format_set(QofDateFormat df);
+
+/** This function returns a strftime formatting string for printing an
+ *  all numeric date (e.g. 2005-09-14).  The string returned is based
+ *  upon the location specified.
+ *
+ *  @param df The date style (us, uk, iso, etc) that should be provided.
+ *
+ *  @return A formatting string that will print a date in the
+ *  requested style  */
+const gchar *qof_date_format_get_string(QofDateFormat df);
+
+/** This function returns a strftime formatting string for printing a
+ *  date using words and numbers (e.g. 2005-September-14).  The string
+ *  returned is based upon the location specified.
+ *
+ *  @param df The date style (us, uk, iso, etc) that should be provided.
+ *
+ *  @return A formatting string that will print a date in the
+ *  requested style  */
+const gchar *qof_date_text_format_get_string(QofDateFormat df);
+// @}
+
+/** dateSeparator
+ *    Return the field separator for the current date format
+ *
+ * Args:   none
+ *
+ * Return: date character
+ *
+ * Globals: global dateFormat value
+ */
+char dateSeparator(void);
+
+/** \name Date Printing/Scanning functions 
+ */
+// @{
+/**
+ * \warning HACK ALERT -- the scan and print routines should probably
+ * be moved to somewhere else. The engine really isn't involved with
+ * things like printing formats. This is needed mostly by the GUI and
+ * so on.  If a file-io thing needs date handling, it should do it
+ * itself, instead of depending on the routines here.
+ */
+
+/** qof_print_date_dmy_buff
+ *    Convert a date as day / month / year integers into a localized string
+ *    representation
+ *
+ * Args:   buff - pointer to previously allocated character array; its size
+ *                must be at lease MAX_DATE_LENTH bytes.
+ *         len - length of the buffer, in bytes.
+ *         day - day of the month as 1 ... 31
+ *         month - month of the year as 1 ... 12
+ *         year - year (4-digit)
+ *
+ * Returns: number of characters printed
+ *
+ * Globals: global dateFormat value
+ **/
+size_t qof_print_date_dmy_buff (char * buff, size_t buflen, int day, int month, int year);
+
+/** Convenience: calls through to qof_print_date_dmy_buff(). **/
+size_t qof_print_date_buff (char * buff, size_t buflen, time_t secs);
+
+/** Convenience; calls through to qof_print_date_dmy_buff(). **/
+size_t qof_print_gdate( char *buf, size_t bufflen, GDate *gd );
+
+/** Convenience; calls through to qof_print_date_dmy_buff(). 
+ *  Return: string, which should be freed when no longer needed.
+ * **/
+char * qof_print_date (time_t secs);
+
+/** Convenience; calls through to qof_print_date_dmy_buff(). 
+ *  Return: static global string.
+ *  \warning This routine is not thread-safe, because it uses a single
+ *      global buffer to store the return value.  Use qof_print_date_buff()
+ *      or qof_print_date() instead.
+ * **/
+const char * gnc_print_date(Timespec ts);
+
+/* ------------------------------------------------------------------ */
+/* time printing utilities */
+
+/** The qof_print_hours_elapsed_buff() routine will print the 'secs' argument
+ *    as HH:MM, and will print the seconds if show_secs is true.  
+ *    Thus, for example, secs=3599 will print as 0:59
+ *    Returns the number of bytes copied.
+ */
+size_t qof_print_hours_elapsed_buff (char * buff, size_t len, int secs, gboolean show_secs);
+size_t qof_print_minutes_elapsed_buff (char * buff, size_t len, int secs, gboolean show_secs);
+
+/** The qof_print_time_buff() routine prints only the hour-part of the date.
+ *    Thus, if secs is  ...
+ *    Returns the number of bytes printed.
+ */
+
+size_t qof_print_time_buff (char * buff, size_t len, time_t secs);
+size_t qof_print_date_time_buff (char * buff, size_t len, time_t secs);
+
+/** The qof_is_same_day() routine returns 0 if both times are in the 
+ * same day.
+ */
+
+gboolean qof_is_same_day (time_t, time_t);
+
+/* ------------------------------------------------------------------ */
+/** The xaccDateUtilGetStamp() routine will take the given time in
+ *  seconds and return a buffer containing a textual for the date.
+ *  @param thyme The time in seconds to convert.
+ *  @return A pointer to the generated string.
+ *  @note The caller owns this buffer and must free it when done. */
+char * xaccDateUtilGetStamp (time_t thyme);
+ 
+/** qof_scan_date
+ *    Convert a string into  day / month / year integers according to
+ *    the current dateFormat value.
+ *
+ * Args:   buff - pointer to date string
+ *         day -  will store day of the month as 1 ... 31
+ *         month - will store month of the year as 1 ... 12
+ *         year - will store the year (4-digit)
+ *
+ * Return: TRUE if the string seemed to be a valid date; else FALSE.
+ *
+ * Globals: uses global dateFormat value to assist in parsing.
+ */
+gboolean qof_scan_date (const char *buff, int *day, int *month, int *year);
+
+/** as above, but returns seconds */
+gboolean qof_scan_date_secs (const char *buff, time_t *secs);
+
+// @}
+/** \name Date Start/End Adjustment routines
+ * Given a time value, adjust it to be the beginning or end of that day.
+ */
+// @{
+
+/** The gnc_tm_set_day_start() inline routine will set the appropriate
+ *  fields in the struct tm to indicate the first second of that day.
+ *  This routine assumes that the contents of the data structure is
+ *  already in normalized form. */
+static inline
+void gnc_tm_set_day_start (struct tm *tm)
+{
+  /* First second of the day */
+  tm->tm_hour = 0;
+  tm->tm_min = 0;
+  tm->tm_sec = 0;
+  tm->tm_isdst = -1;
+}
+
+/** The gnc_tm_set_day_start() inline routine will set the appropriate
+ *  fields in the struct tm to indicate noon of that day.  This
+ *  routine assumes that the contents of the data structure is already
+ *  in normalized form.*/
+static inline
+void gnc_tm_set_day_middle (struct tm *tm)
+{
+  /* First second of the day */
+  tm->tm_hour = 12;
+  tm->tm_min = 0;
+  tm->tm_sec = 0;
+  tm->tm_isdst = -1;
+}
+
+/** The gnc_tm_set_day_start() inline routine will set the appropriate
+ *  fields in the struct tm to indicate the last second of that day.
+ *  This routine assumes that the contents of the data structure is
+ *  already in normalized form.*/
+static inline
+void gnc_tm_set_day_end (struct tm *tm)
+{
+  /* Last second of the day */
+  tm->tm_hour = 23;
+  tm->tm_min = 59;
+  tm->tm_sec = 59;
+  tm->tm_isdst = -1;
+}
+
+/** The gnc_tm_get_day_start() routine will convert the given time in
+ *  seconds to the struct tm format, and then adjust it to the
+ *  first second of that day. */
+void   gnc_tm_get_day_start(struct tm *tm, time_t time_val);
+
+/** The gnc_tm_get_day_end() routine will convert the given time in
+ *  seconds to the struct tm format, and then adjust it to the
+ *  last second of that day. */
+void   gnc_tm_get_day_end(struct tm *tm, time_t time_val);
+
+/** The gnc_timet_get_day_start() routine will take the given time in
+ *  seconds and adjust it to the last second of that day. */
+time_t gnc_timet_get_day_start(time_t time_val);
+
+/** The gnc_timet_get_day_end() routine will take the given time in
+ *  seconds and adjust it to the last second of that day. */
+time_t gnc_timet_get_day_end(time_t time_val);
+
+#ifndef GNUCASH_MAJOR_VERSION
+/** The gnc_timet_get_day_start() routine will take the given time in
+ *  GLib GDate format and adjust it to the last second of that day.
+ *
+ *  @deprecated
+ */
+time_t gnc_timet_get_day_start_gdate (GDate *date);
+
+/** The gnc_timet_get_day_end() routine will take the given time in
+ *  GLib GDate format and adjust it to the last second of that day.
+ *
+ *  @deprecated
+ */
+time_t gnc_timet_get_day_end_gdate (GDate *date);
+#endif /* GNUCASH_MAJOR_VERSION */
+
+/** Get the numerical last date of the month. (28, 29, 30, 31) */
+int date_get_last_mday(struct tm *tm);
+
+/** Is the mday field the last day of the specified month.*/
+gboolean date_is_last_mday(struct tm *tm);
+
+/** DOCUMENT ME! Probably the same as date_get_last_mday() */
+int gnc_date_my_last_mday (int month, int year);
+/** DOCUMENT ME! Probably the same as date_get_last_mday() */
+int gnc_timespec_last_mday (Timespec ts);
+// @}
+
+/* ======================================================== */
+
+/** \name Today's Date */
+// @{
+/** The gnc_tm_get_today_start() routine takes a pointer to a struct
+ *  tm and fills it in with the first second of the today. */
+void   gnc_tm_get_today_start(struct tm *tm);
+
+/** The gnc_tm_get_today_end() routine takes a pointer to a struct
+ *  tm and fills it in with the last second of the today. */
+void   gnc_tm_get_today_end(struct tm *tm);
+
+/** The gnc_timet_get_today_start() routine returns a time_t value
+ *  corresponding to the first second of today. */
+time_t gnc_timet_get_today_start(void);
+
+/** The gnc_timet_get_today_end() routine returns a time_t value
+ *  corresponding to the last second of today. */
+time_t gnc_timet_get_today_end(void);
+
+/** The xaccDateUtilGetStampNow() routine returns the current time in
+ *  seconds in textual format.
+ *  @return A pointer to the generated string.
+ *  @note The caller owns this buffer and must free it when done. */
+char * xaccDateUtilGetStampNow (void);
+
+//@}
+//@}
+#endif /* GNC_DATE_H */
+

Added: gnucash/trunk/lib/libqof/qof/gnc-engine-util.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-engine-util.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-engine-util.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,276 @@
+/********************************************************************\
+ * gnc-engine-util.c -- QOF utility functions                       *
+ * Copyright (C) 1997 Robin D. Clark                                *
+ * Copyright (C) 1997-2001,2004 Linas Vepstas <linas at linas.org>     *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ *   Author: Rob Clark (rclark at cs.hmc.edu)                          *
+ *   Author: Linas Vepstas (linas at linas.org)                        *
+\********************************************************************/
+
+#include "config.h"
+
+#include <ctype.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <string.h>
+#include "qof.h"
+#include "gnc-engine-util.h"
+
+
+/********************************************************************\
+\********************************************************************/
+
+/* Search for str2 in first nchar chars of str1, ignore case..  Return
+ * pointer to first match, or null.  */
+char *
+strncasestr(const char *str1, const char *str2, size_t len) 
+{
+  while (*str1 && len--) 
+  {
+    if (toupper(*str1) == toupper(*str2)) 
+    {
+      if (strncasecmp(str1,str2,strlen(str2)) == 0) 
+      {
+        return (char *) str1;
+      }
+    }
+    str1++;
+  }
+  return NULL;
+}
+
+/* Search for str2 in str1, ignore case.  Return pointer to first
+ * match, or null.  */
+char *
+strcasestr(const char *str1, const char *str2) 
+{
+   size_t len = strlen (str1);
+   char * retval = strncasestr (str1, str2, len);
+   return retval;
+}
+
+/********************************************************************\
+\********************************************************************/
+
+int 
+safe_strcmp (const char * da, const char * db)
+{
+   SAFE_STRCMP (da, db);
+   return 0;
+}
+
+int 
+safe_strcasecmp (const char * da, const char * db)
+{
+   SAFE_STRCASECMP (da, db);
+   return 0;
+}
+
+int 
+null_strcmp (const char * da, const char * db)
+{
+   if (da && db) return strcmp (da, db);
+   if (!da && db && 0==db[0]) return 0;
+   if (!db && da && 0==da[0]) return 0;
+   if (!da && db) return -1;
+   if (da && !db) return +1;
+   return 0;
+}
+
+/********************************************************************\
+\********************************************************************/
+
+#define MAX_DIGITS 50
+
+/* inverse of strtoul */
+char *
+ultostr (unsigned long val, int base)
+{
+  char buf[MAX_DIGITS];
+  unsigned long broke[MAX_DIGITS];
+  int i;
+  unsigned long places=0, reval;
+  
+  if ((2>base) || (36<base)) return NULL;
+
+  /* count digits */
+  places = 0;
+  for (i=0; i<MAX_DIGITS; i++) {
+     broke[i] = val;
+     places ++;
+     val /= base;
+     if (0 == val) break;
+  }
+
+  /* normalize */
+  reval = 0;
+  for (i=places-2; i>=0; i--) {
+    reval += broke[i+1];
+    reval *= base;
+    broke[i] -= reval;
+  }
+
+  /* print */
+  for (i=0; i<(int)places; i++) {
+    if (10>broke[i]) {
+       buf[places-1-i] = 0x30+broke[i];  /* ascii digit zero */
+    } else {
+       buf[places-1-i] = 0x41-10+broke[i];  /* ascii capital A */
+    }
+  }
+  buf[places] = 0x0;
+
+  return g_strdup (buf);
+}
+
+/********************************************************************\
+ * returns TRUE if the string is a number, possibly with whitespace
+\********************************************************************/
+
+gboolean
+gnc_strisnum(const char *s)
+{
+  if (s == NULL) return FALSE;
+  if (*s == 0) return FALSE;
+
+  while (*s && isspace(*s))
+    s++;
+
+  if (*s == 0) return FALSE;
+  if (!isdigit(*s)) return FALSE;
+
+  while (*s && isdigit(*s))
+    s++;
+
+  if (*s == 0) return TRUE;
+
+  while (*s && isspace(*s))
+    s++;
+
+  if (*s == 0) return TRUE;
+
+  return FALSE;
+}
+
+/********************************************************************\
+ * our own version of stpcpy
+\********************************************************************/
+
+char *
+gnc_stpcpy (char *dest, const char *src)
+{
+  strcpy (dest, src);
+  return (dest + strlen (src));
+}
+
+/* =================================================================== */
+/* Return NULL if the field is whitespace (blank, tab, formfeed etc.)  
+ * Else return pointer to first non-whitespace character. */
+
+const char *
+qof_util_whitespace_filter (const char * val)
+{
+	size_t len;
+	if (!val) return NULL;
+
+	len = strspn (val, "\a\b\t\n\v\f\r ");
+	if (0 == val[len]) return NULL;
+	return val+len;
+}
+
+/* =================================================================== */
+/* Return integer 1 if the string starts with 't' or 'T' or contains the 
+ * word 'true' or 'TRUE'; if string is a number, return that number. */
+
+int
+qof_util_bool_to_int (const char * val)
+{
+	const char * p = qof_util_whitespace_filter (val);
+	if (!p) return 0;
+	if ('t' == p[0]) return 1;
+	if ('T' == p[0]) return 1;
+	if ('y' == p[0]) return 1;
+	if ('Y' == p[0]) return 1;
+	if (strstr (p, "true")) return 1;
+	if (strstr (p, "TRUE")) return 1;
+	if (strstr (p, "yes")) return 1;
+	if (strstr (p, "YES")) return 1;
+	return atoi (val);
+}
+
+/********************************************************************\
+ * The engine string cache
+\********************************************************************/
+
+static GCache * gnc_string_cache = NULL;
+
+/* maybe better to make this function static */
+GCache*
+gnc_engine_get_string_cache(void)
+{
+    if(!gnc_string_cache) 
+    {
+        gnc_string_cache = g_cache_new(
+            (GCacheNewFunc) g_strdup, g_free,
+            (GCacheDupFunc) g_strdup, g_free, g_str_hash, 
+            g_str_hash, g_str_equal);
+    }
+    return gnc_string_cache;
+}
+
+void
+gnc_engine_string_cache_destroy (void)
+{
+  g_cache_destroy (gnc_string_cache);
+  gnc_string_cache = NULL;
+}
+
+void gnc_string_cache_remove(gpointer key)
+{
+  g_cache_remove(gnc_engine_get_string_cache(), key);
+}
+
+gpointer gnc_string_cache_insert(gpointer key)
+{
+  return g_cache_insert(gnc_engine_get_string_cache(), key);
+}
+
+void
+qof_init (void)
+{
+	gnc_engine_get_string_cache ();
+	guid_init ();
+	qof_object_initialize ();
+	qof_query_init ();
+	qof_book_register ();
+}
+
+void
+qof_close(void)
+{
+	qof_query_shutdown ();
+	qof_object_shutdown ();
+	guid_shutdown ();
+	gnc_engine_string_cache_destroy ();
+}
+
+
+/************************* END OF FILE ******************************\
+\********************************************************************/

Added: gnucash/trunk/lib/libqof/qof/gnc-engine-util.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-engine-util.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-engine-util.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,248 @@
+/********************************************************************\
+ * gnc-engine-util.h -- QOF utility functions                       *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+/** @addtogroup Utilities
+    @{ */
+/** @file gnc-engine-util.h
+    @brief QOF utility functions
+    @author Copyright (C) 1997 Robin D. Clark <rclark at cs.hmc.edu>
+    @author Copyright (C) 2000 Bill Gribble <grib at billgribble.com>
+    @author Copyright (C) 1997-2002,2004 Linas Vepstas <linas at linas.org>
+*/
+
+#ifndef QOF_UTIL_H
+#define QOF_UTIL_H
+
+#include <glib.h>
+#include <stddef.h>
+#include "config.h"
+#include "qof.h"
+
+/** Macros *****************************************************/
+
+/* CAS: Notice that this macro does nothing if pointer args are equal.
+   Otherwise, it returns an integer.  Actually, perhaps these macro
+   should be private.  They are NOT good substitutes for the function
+   versions like safe_strcmp().  Maybe external users of these 3
+   macros should be converted to use safe_strcmp().  Actually, THESE
+   MACROS AFFECT CONTROL FLOW.  YUCK!  */
+#define SAFE_STRCMP_REAL(fcn,da,db) {    \
+  if ((da) && (db)) {                    \
+    if ((da) != (db)) {                  \
+      int retval = fcn ((da), (db));     \
+      /* if strings differ, return */    \
+      if (retval) return retval;         \
+    }                                    \
+  } else                                 \
+  if ((!(da)) && (db)) {                 \
+    return -1;                           \
+  } else                                 \
+  if ((da) && (!(db))) {                 \
+    return +1;                           \
+  }                                      \
+}
+
+#define SAFE_STRCMP(da,db) SAFE_STRCMP_REAL(strcmp,(da),(db))
+#define SAFE_STRCASECMP(da,db) SAFE_STRCMP_REAL(strcasecmp,(da),(db))
+
+/** \name typedef enum as string macros
+@{
+*/
+#define ENUM_BODY(name, value)           \
+    name value,
+
+#define AS_STRING_CASE(name, value)      \
+    case name: { return #name; }
+
+#define FROM_STRING_CASE(name, value)    \
+    if (strcmp(str, #name) == 0) {       \
+        return name;  }
+
+#define DEFINE_ENUM(name, list)          \
+    typedef enum {                       \
+        list(ENUM_BODY)                  \
+    }name;
+
+#define AS_STRING_DEC(name, list)        \
+    const char* name##asString(name n);
+
+#define AS_STRING_FUNC(name, list)       \
+    const char* name##asString(name n) { \
+        switch (n) {                     \
+            list(AS_STRING_CASE)         \
+            default: return "";  } }
+
+#define FROM_STRING_DEC(name, list)      \
+    name name##fromString                \
+    (const char* str);
+
+#define FROM_STRING_FUNC(name, list)     \
+    name name##fromString                \
+    (const char* str) {                  \
+    if(str == NULL) { return 0; }        \
+        list(FROM_STRING_CASE)           \
+        return 0;  }
+
+/** @} */
+
+/** \name enum as string with no typedef
+@{
+
+  Similar but used when the enum is NOT a typedef
+ note the LACK of a define_enum macro - don't use one!
+
+ ENUM_BODY is used in both types.
+ */
+
+#define FROM_STRING_DEC_NON_TYPEDEF(name, list)   \
+   void name##fromString                          \
+   (const char* str, enum name *type);
+
+#define FROM_STRING_CASE_NON_TYPEDEF(name, value) \
+   if (strcmp(str, #name) == 0) { *type = name; }
+
+#define FROM_STRING_FUNC_NON_TYPEDEF(name, list)  \
+   void name##fromString                          \
+   (const char* str, enum name *type) {           \
+   if(str == NULL) { return; }                    \
+    list(FROM_STRING_CASE_NON_TYPEDEF) }
+
+#define AS_STRING_DEC_NON_TYPEDEF(name, list)     \
+   const char* name##asString(enum name n);
+
+#define AS_STRING_FUNC_NON_TYPEDEF(name, list)    \
+   const char* name##asString(enum name n) {     \
+       switch (n) {                              \
+           list(AS_STRING_CASE_NON_TYPEDEF)      \
+           default: return ""; } }
+
+#define AS_STRING_CASE_NON_TYPEDEF(name, value)   \
+   case name: { return #name; }
+
+/** @} */
+
+/* Define the long long int conversion for scanf */
+#if HAVE_SCANF_LLD
+# define GNC_SCANF_LLD "%lld"
+#else
+# define GNC_SCANF_LLD "%qd"
+#endif
+
+/** @name Convenience wrappers
+   @{
+*/
+   
+/** \brief Initialise the Query Object Framework 
+
+Used for non-Guile applications or test routines.
+*/
+void qof_init (void);
+
+/** \brief Safely close down the Query Object Framework 
+
+Used for non-Guile applications or test routines.
+*/
+void qof_close (void);
+
+/** @} */
+
+/** Prototypes *************************************************/
+
+/** The safe_strcmp compares strings a and b the same way that strcmp()
+ * does, except that either may be null.  This routine assumes that
+ * a non-null string is always greater than a null string.
+ */
+int safe_strcmp (const char * da, const char * db);
+int safe_strcasecmp (const char * da, const char * db);
+
+/** The null_strcmp compares strings a and b the same way that strcmp()
+ * does, except that either may be null.  This routine assumes that
+ * a null string is equal to the empty string.
+ */
+int null_strcmp (const char * da, const char * db);
+
+/** Search for str2 in first nchar chars of str1, ignore case. Return
+ * pointer to first match, or null. These are just like that strnstr
+ * and the strstr functions, except that they ignore the case. */
+extern char *strncasestr(const char *str1, const char *str2, size_t len);
+extern char *strcasestr(const char *str1, const char *str2);
+
+/** The ultostr() subroutine is the inverse of strtoul(). It accepts a
+ * number and prints it in the indicated base.  The returned string
+ * should be g_freed when done.  */
+char * ultostr (unsigned long val, int base);
+
+/** Returns true if string s is a number, possibly surrounded by
+ * whitespace. */
+gboolean gnc_strisnum(const char *s);
+
+/** Local copy of stpcpy, used wtih libc's that don't have one. */
+char * gnc_stpcpy (char *dest, const char *src);
+
+#ifndef HAVE_STPCPY
+#define stpcpy gnc_stpcpy
+#endif
+
+/** Return NULL if the field is whitespace (blank, tab, formfeed etc.)
+ *  Else return pointer to first non-whitespace character.
+ */
+const char * qof_util_whitespace_filter (const char * val);
+
+/** Return integer 1 if the string starts with 't' or 'T' or
+ *  contains the word 'true' or 'TRUE'; if string is a number,
+ *  return that number. (Leading whitespace is ignored). */
+int qof_util_bool_to_int (const char * val);
+
+/** Many strings used throughout the engine are likely to be duplicated.
+ * So we provide a reference counted cache system for the strings, which
+ * shares strings whenever possible.
+ *
+ * Use g_cache_insert to insert a string into the cache (it will return a
+ * pointer to the cached string).
+ * Basically you should use this instead of g_strdup.
+ *
+ * Use g_cache_remove (giving it a pointer to a cached string) if the string
+ * is unused.  If this is the last reference to the string it will be
+ * removed from the cache, otherwise it will just decrement the
+ * reference count.
+ * Basically you should use this instead of g_free.
+ *
+ * Note that all the work is done when inserting or removing.  Once
+ * cached the strings are just plain C strings.
+ */
+
+/** Get the gnc_string_cache.  Create it if it doesn't exist already
+
+\todo hide the gcache as a static */
+GCache* gnc_engine_get_string_cache(void);
+
+void gnc_engine_string_cache_destroy (void);
+
+/* You can use this function as a destroy notifier for a GHashTable
+   that uses common strings as keys (or values, for that matter.) */
+void gnc_string_cache_remove(gpointer key);
+
+/* You can use this function with g_hash_table_insert(), or the key
+  (or value), as long as you use the destroy notifier above. */
+gpointer gnc_string_cache_insert(gpointer key);
+
+#endif /* QOF_UTIL_H */
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/gnc-event-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-event-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-event-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,37 @@
+/********************************************************************
+ * gnc-event-p.h -- private engine event handling interface         *
+ * Copyright 2000 Dave Peticolas <dave at krondo.com>                  *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ ********************************************************************/
+
+#ifndef GNC_EVENT_P_H
+#define GNC_EVENT_P_H
+
+#include "gnc-event.h"
+#include "qofid.h"
+
+/* XXX deprecated, but still usedion on postgres backend */
+void gnc_engine_generate_event (const GUID *, QofIdType, GNCEngineEventType);
+
+/* generates an event even when events are suspended! */
+void gnc_engine_force_event (QofEntity *entity,
+			     GNCEngineEventType event_type);
+
+#endif

Added: gnucash/trunk/lib/libqof/qof/gnc-event.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-event.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-event.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,224 @@
+/********************************************************************
+ * gnc-event.c -- engine event handling implementation              *
+ * Copyright 2000 Dave Peticolas <dave at krondo.com>                  *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ ********************************************************************/
+
+#include "config.h"
+
+#include "gnc-event-p.h"
+#include "gnc-trace.h"
+
+
+/** Declarations ****************************************************/
+
+typedef struct
+{
+  GNCEngineEventHandler handler;
+  gpointer user_data;
+
+  gint handler_id;
+} HandlerInfo;
+
+
+/** Static Variables ************************************************/
+static guint  suspend_counter = 0;
+static gint   next_handler_id = 1;
+static GList *handlers = NULL;
+
+/* This static indicates the debugging module that this .o belongs to.  */
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+
+/** Implementations *************************************************/
+
+gint
+gnc_engine_register_event_handler (GNCEngineEventHandler handler,
+                                   gpointer user_data)
+{
+  HandlerInfo *hi;
+  gint handler_id;
+  GList *node;
+
+  ENTER ("(handler=%p, data=%p)", handler, user_data);
+  /* sanity check */
+  if (!handler)
+  {
+    PERR ("no handler specified");
+    return 0;
+  }
+
+  /* look for a free handler id */
+  handler_id = next_handler_id;
+  node = handlers;
+
+  while (node)
+  {
+    hi = node->data;
+
+    if (hi->handler_id == handler_id)
+    {
+      handler_id++;
+      node = handlers;
+      continue;
+    }
+
+    node = node->next;
+  }
+
+  /* Found one, add the handler */
+  hi = g_new0 (HandlerInfo, 1);
+
+  hi->handler = handler;
+  hi->user_data = user_data;
+  hi->handler_id = handler_id;
+
+  handlers = g_list_prepend (handlers, hi);
+
+  /* Update id for next registration */
+  next_handler_id = handler_id + 1;
+
+  LEAVE ("(handler=%p, data=%p) handler_id=%d", handler, user_data, handler_id);
+  return handler_id;
+}
+
+void
+gnc_engine_unregister_event_handler (gint handler_id)
+{
+  GList *node;
+
+  ENTER ("(handler_id=%d)", handler_id);
+  for (node = handlers; node; node = node->next)
+  {
+    HandlerInfo *hi = node->data;
+
+    if (hi->handler_id != handler_id)
+      continue;
+
+    /* Found it, take out of list */ 
+    handlers = g_list_remove_link (handlers, node);
+
+    LEAVE ("(handler_id=%d) handler=%p data=%p", handler_id, hi->handler, hi->user_data);
+    /* safety */
+    hi->handler = NULL;
+
+    g_list_free_1 (node);
+    g_free (hi);
+
+    return;
+  }
+
+  PERR ("no such handler: %d", handler_id);
+}
+
+void
+gnc_engine_suspend_events (void)
+{
+  suspend_counter++;
+
+  if (suspend_counter == 0)
+  {
+    PERR ("suspend counter overflow");
+  }
+}
+
+void
+gnc_engine_resume_events (void)
+{
+  if (suspend_counter == 0)
+  {
+    PERR ("suspend counter underflow");
+    return;
+  }
+
+  suspend_counter--;
+}
+
+static void
+gnc_engine_generate_event_internal (QofEntity *entity, 
+				    GNCEngineEventType event_type)
+{
+  GList *node;
+  GList *next_node = NULL;
+
+  g_return_if_fail(entity);
+
+  switch (event_type)
+  {
+    case GNC_EVENT_NONE:
+      return;
+
+    case GNC_EVENT_CREATE:
+    case GNC_EVENT_MODIFY:
+    case GNC_EVENT_DESTROY:
+    case GNC_EVENT_ADD:
+    case GNC_EVENT_REMOVE:
+      break;
+
+    default:
+      PERR ("bad event type %d", event_type);
+      return;
+  }
+
+  for (node = handlers; node; node = next_node)
+  {
+    HandlerInfo *hi = node->data;
+
+    next_node = node->next;
+    PINFO ("id=%d hi=%p han=%p", hi->handler_id, hi, hi->handler);
+    if (hi->handler)
+      hi->handler ((GUID *)&entity->guid, entity->e_type, event_type, hi->user_data);
+  }
+}
+
+void
+gnc_engine_force_event (QofEntity *entity, 
+			GNCEngineEventType event_type)
+{
+  if (!entity)
+    return;
+
+  gnc_engine_generate_event_internal (entity, event_type);
+}
+
+void
+gnc_engine_gen_event (QofEntity *entity, GNCEngineEventType event_type)
+{
+  if (!entity)
+    return;
+
+  if (suspend_counter)
+    return;
+
+  gnc_engine_generate_event_internal (entity, event_type);
+}
+
+void 
+gnc_engine_generate_event (const GUID *guid, QofIdType e_type, 
+         GNCEngineEventType event_type)
+{
+  QofEntity ent;
+  ent.guid = *guid;
+  ent.e_type = e_type;
+  if (suspend_counter) return;
+  gnc_engine_generate_event_internal (&ent, event_type);
+}
+
+/* =========================== END OF FILE ======================= */

Added: gnucash/trunk/lib/libqof/qof/gnc-event.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-event.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-event.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,118 @@
+/********************************************************************
+ * gnc-event.h -- engine event handling interface                   *
+ * Copyright 2000 Dave Peticolas <dave at krondo.com>                  *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ ********************************************************************/
+
+/** @addtogroup Event
+@{
+*/
+/** @file gnc-event.h
+    @brief engine event handling interface
+	@author Copyright 2000 Dave Peticolas <dave at krondo.com>
+*/
+
+#ifndef GNC_EVENT_H
+#define GNC_EVENT_H
+
+#include <glib.h>
+
+#include "guid.h"
+#include "qofid.h"
+
+typedef enum
+{
+  GNC_EVENT_NONE    = 0,
+  GNC_EVENT_CREATE  = 1 << 0,
+  GNC_EVENT_MODIFY  = 1 << 1,
+  GNC_EVENT_DESTROY = 1 << 2,
+  GNC_EVENT_ADD     = 1 << 3,
+  GNC_EVENT_REMOVE  = 1 << 4,
+  GNC_EVENT_ALL     = 0xff
+} GNCEngineEventType;
+
+
+/** GNCEngineEventHandler
+
+ *   Handler invoked when an engine event occurs.
+ *
+ * @param entity:      GUID of entity generating event
+ * @param type:	QofIdType of the entity generating the event
+ * @param event_type:  one of the single-bit GNCEngineEventTypes, not a combination
+ * @param user_data:   user_data supplied when handler was registered.
+ */
+typedef void (*GNCEngineEventHandler) (GUID *entity, QofIdType type,
+                                       GNCEngineEventType event_type,
+                                       gpointer user_data);
+
+/** gnc_engine_register_event_handler
+
+ *   Register a handler for engine events.
+ *
+ * @param handler:   handler to register
+ * @param user_data: data provided when handler is invoked
+ *
+ * @return id identifying handler
+ */
+gint gnc_engine_register_event_handler (GNCEngineEventHandler handler,
+                                        gpointer user_data);
+
+/** gnc_engine_unregister_event_handler
+
+ *   Unregister an engine event handler.
+ *
+ * @param handler_id: the id of the handler to unregister
+ */
+void gnc_engine_unregister_event_handler (gint handler_id);
+
+/** gnc_engine_generate_event
+
+ *   Invoke all registered event handlers using the given arguments.
+ *
+ *   GNC_EVENT_CREATE events should be generated after the object
+ *     has been created and registered in the engine entity table.
+ *   GNC_EVENT_MODIFY events should be generated whenever any data
+ *     member or submember (i.e., splits) is changed.
+ *   GNC_EVENT_DESTROY events should be called before the object
+ *     has been destroyed or removed from the entity table.
+ *
+ * @param entity:     the GUID of the entity generating the event
+ * @param event_type: the type of event -- this should be one of the
+ *             single-bit GNCEngineEventType values, not a combination.
+ */
+void gnc_engine_gen_event (QofEntity *entity,
+                                GNCEngineEventType event_type);
+/** gnc_engine_suspend_events
+
+ *   Suspend all engine events. This function may be
+ *   called multiple times. To resume event generation,
+ *   an equal number of calls to gnc_engine_resume_events
+ *   must be made.
+ */
+void gnc_engine_suspend_events (void);
+
+/** gnc_engine_resume_events
+
+ *   Resume engine event generation.
+ */
+void gnc_engine_resume_events (void);
+
+#endif
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/gnc-numeric.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-numeric.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-numeric.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1306 @@
+/********************************************************************
+ * gnc-numeric.c -- an exact-number library for accounting use      *
+ * Copyright (C) 2000 Bill Gribble                                  *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org>               *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ *******************************************************************/
+
+#define _GNU_SOURCE
+
+#include "config.h"
+
+#include <glib.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gnc-numeric.h"
+#include "qofmath128.c"
+
+/* static short module = MOD_ENGINE; */
+
+/* =============================================================== */
+
+#if 0
+static const char * _numeric_error_strings[] =
+{
+  "No error",
+  "Argument is not a valid number",
+  "Intermediate result overflow",
+  "Argument denominators differ in GNC_HOW_DENOM_FIXED operation",
+  "Remainder part in GNC_HOW_RND_NEVER operation"
+};
+#endif
+
+/* =============================================================== */
+/* This function is small, simple, and used everywhere below,
+ * lets try to inline it.
+ */
+inline GNCNumericErrorCode
+gnc_numeric_check(gnc_numeric in)
+{
+  if(in.denom != 0)
+  {
+    return GNC_ERROR_OK;
+  }
+  else if(in.num)
+  {
+    if ((0 < in.num) || (-4 > in.num))
+    {
+       in.num = (gint64) GNC_ERROR_OVERFLOW;
+    }
+    return (GNCNumericErrorCode) in.num;
+  }
+  else
+  {
+    return GNC_ERROR_ARG;
+  }
+}
+
+/**
+ *  Find the least common multiple of the denominators of a and b.
+ */
+
+static inline gint64
+gnc_numeric_lcd(gnc_numeric a, gnc_numeric b)
+{
+  qofint128 lcm;
+  if(gnc_numeric_check(a) || gnc_numeric_check(b))
+  {
+    return GNC_ERROR_ARG;
+  }
+
+  if (b.denom == a.denom) return a.denom;
+
+  /* Special case: smaller divides smoothly into larger */
+  if ((b.denom < a.denom) && ((a.denom % b.denom) == 0))
+  {
+    return a.denom;
+  }
+  if ((a.denom < b.denom) && ((b.denom % a.denom) == 0))
+  {
+    return b.denom;
+  }
+
+  lcm = lcm128 (a.denom, b.denom);
+  if (lcm.isbig) return GNC_ERROR_ARG;
+  return lcm.lo;
+}
+
+
+/** Return the ratio n/d reduced so that there are no common factors. */
+static inline gnc_numeric
+reduce128(qofint128 n, gint64 d)
+{
+  gint64   t;
+  gint64   num;
+  gint64   denom;
+  gnc_numeric out;
+  qofint128 red;
+
+  t =  rem128 (n, d);
+  num = d;
+  denom = t;
+
+  /* The strategy is to use Euclid's algorithm */
+  while (denom > 0)
+  {
+    t = num % denom;
+    num = denom;
+    denom = t;
+  }
+  /* num now holds the GCD (Greatest Common Divisor) */
+
+  red = div128 (n, num);
+  if (red.isbig)
+  {
+    return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+  }
+  out.num   = red.lo;
+  if (red.isneg) out.num = -out.num;
+  out.denom = d / num;
+  return out;
+}
+
+/********************************************************************
+ *  gnc_numeric_zero_p
+ ********************************************************************/
+
+gboolean
+gnc_numeric_zero_p(gnc_numeric a)
+{
+  if(gnc_numeric_check(a))
+  {
+    return 0;
+  }
+  else
+  {
+    if((a.num == 0) && (a.denom != 0))
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+}
+
+/********************************************************************
+ *  gnc_numeric_negative_p
+ ********************************************************************/
+
+gboolean
+gnc_numeric_negative_p(gnc_numeric a)
+{
+  if(gnc_numeric_check(a))
+  {
+    return 0;
+  }
+  else
+  {
+    if((a.num < 0) && (a.denom != 0))
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+}
+
+/********************************************************************
+ *  gnc_numeric_positive_p
+ ********************************************************************/
+
+gboolean
+gnc_numeric_positive_p(gnc_numeric a)
+{
+  if(gnc_numeric_check(a))
+  {
+    return 0;
+  }
+  else
+  {
+    if((a.num > 0) && (a.denom != 0))
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+}
+
+/********************************************************************
+ *  gnc_numeric_compare
+ *  returns 1 if a>b, -1 if b>a, 0 if a == b
+ ********************************************************************/
+
+int
+gnc_numeric_compare(gnc_numeric a, gnc_numeric b)
+{
+  gint64 aa, bb;
+  qofint128 l, r;
+
+  if(gnc_numeric_check(a) || gnc_numeric_check(b))
+  {
+    return 0;
+  }
+
+  if (a.denom == b.denom)
+  {
+    if(a.num == b.num) return 0;
+    if(a.num > b.num) return 1;
+    return -1;
+  }
+
+  if  ((a.denom > 0) && (b.denom > 0))
+  {
+    /* Avoid overflows using 128-bit intermediate math */
+    l = mult128 (a.num, b.denom);
+    r = mult128 (b.num, a.denom);
+    return cmp128 (l,r);
+  }
+
+  aa = a.num * a.denom;
+  bb = b.num * b.denom;
+
+  if(aa == bb) return 0;
+  if(aa > bb) return 1;
+  return -1;
+}
+
+
+/********************************************************************
+ *  gnc_numeric_eq
+ ********************************************************************/
+
+gboolean
+gnc_numeric_eq(gnc_numeric a, gnc_numeric b)
+{
+  return ((a.num == b.num) && (a.denom == b.denom));
+}
+
+
+/********************************************************************
+ *  gnc_numeric_equal
+ ********************************************************************/
+
+gboolean
+gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
+{
+  if ((a.denom == b.denom) && (a.denom > 0))
+  {
+    return (a.num == b.num);
+  }
+  if ((a.denom > 0) && (b.denom > 0))
+  {
+    // return (a.num*b.denom == b.num*a.denom);
+    qofint128 l = mult128 (a.num, b.denom);
+    qofint128 r = mult128 (b.num, a.denom);
+    return equal128 (l, r);
+
+#if ALT_WAY_OF_CHECKING_EQUALITY
+    gnc_numeric ra = gnc_numeric_reduce (a);
+    gnc_numeric rb = gnc_numeric_reduce (b);
+    if (ra.denom != rb.denom) return 0;
+    if (ra.num != rb.num) return 0;
+    return 1;
+#endif
+  }
+  if ((a.denom < 0) && (b.denom < 0))
+  {
+    return ((a.num * b.denom) == (a.denom * b.num));
+  }
+  else
+  {
+    return 0;
+  }
+}
+
+
+/********************************************************************
+ *  gnc_numeric_same
+ *  would a and b be equal() if they were both converted to the same
+ *  denominator?
+ ********************************************************************/
+
+int
+gnc_numeric_same(gnc_numeric a, gnc_numeric b, gint64 denom,
+                 gint how) {
+  gnc_numeric aconv, bconv;
+
+  aconv = gnc_numeric_convert(a, denom, how);
+  bconv = gnc_numeric_convert(b, denom, how);
+
+  return(gnc_numeric_equal(aconv, bconv));
+}
+
+
+
+/********************************************************************
+ *  gnc_numeric_add
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_add(gnc_numeric a, gnc_numeric b,
+                gint64 denom, gint how)
+{
+  gnc_numeric sum;
+
+  if(gnc_numeric_check(a) || gnc_numeric_check(b))
+  {
+    return gnc_numeric_error(GNC_ERROR_ARG);
+  }
+
+  if((denom == GNC_DENOM_AUTO) &&
+     (how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_FIXED)
+  {
+    if(a.denom == b.denom) {
+      denom = a.denom;
+    }
+    else if(b.num == 0) {
+      denom = a.denom;
+      b.denom = a.denom;
+    }
+    else if(a.num == 0) {
+      denom = b.denom;
+      a.denom = b.denom;
+    }
+    else {
+      return gnc_numeric_error(GNC_ERROR_DENOM_DIFF);
+    }
+  }
+
+  if(a.denom < 0)
+  {
+    a.num *= a.denom;
+    a.denom = 1;
+  }
+
+  if(b.denom < 0)
+  {
+    b.num *= b.denom;
+    b.denom = 1;
+  }
+
+  /* Get an exact answer.. same denominator is the common case. */
+  if(a.denom == b.denom)
+  {
+    sum.num = a.num + b.num;
+    sum.denom = a.denom;
+  }
+  else
+  {
+    /* We want to do this:
+     *    sum.num = a.num*b.denom + b.num*a.denom;
+     *    sum.denom = a.denom*b.denom;
+     * but the multiply could overflow.
+     * Computing the LCD minimizes likelyhood of overflow
+     */
+    gint64 lcd;
+    qofint128 ca, cb, cab;
+    lcd = gnc_numeric_lcd(a,b);
+    if (GNC_ERROR_ARG == lcd)
+    {
+       return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+    }
+    ca = mult128 (a.num, lcd/a.denom);
+    if (ca.isbig) return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+
+    cb = mult128 (b.num, lcd/b.denom);
+    if (cb.isbig) return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+
+    cab = add128 (ca, cb);
+    if (cab.isbig) return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+
+    sum.num   = cab.lo;
+    if (cab.isneg) sum.num = -sum.num;
+    sum.denom = lcd;
+  }
+
+  if((denom == GNC_DENOM_AUTO) &&
+     ((how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_LCD))
+  {
+    denom = gnc_numeric_lcd(a, b);
+    how   = how & GNC_NUMERIC_RND_MASK;
+  }
+
+  return gnc_numeric_convert(sum, denom, how);
+}
+
+/********************************************************************
+ *  gnc_numeric_sub
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
+                gint64 denom, gint how)
+{
+  gnc_numeric nb;
+  if(gnc_numeric_check(a) || gnc_numeric_check(b))
+  {
+    return gnc_numeric_error(GNC_ERROR_ARG);
+  }
+
+  nb = b;
+  nb.num = -nb.num;
+  return gnc_numeric_add (a, nb, denom, how);
+}
+
+/********************************************************************
+ *  gnc_numeric_mul
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
+                gint64 denom, gint how)
+{
+  gnc_numeric product, result;
+  qofint128 bignume, bigdeno;
+
+  if(gnc_numeric_check(a) || gnc_numeric_check(b)) {
+    return gnc_numeric_error(GNC_ERROR_ARG);
+  }
+
+  if((denom == GNC_DENOM_AUTO) &&
+     (how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_FIXED) {
+    if(a.denom == b.denom) {
+      denom = a.denom;
+    }
+    else if(b.num == 0) {
+      denom = a.denom;
+    }
+    else if(a.num == 0) {
+      denom = b.denom;
+    }
+    else {
+      return gnc_numeric_error(GNC_ERROR_DENOM_DIFF);
+    }
+  }
+
+  if((denom == GNC_DENOM_AUTO) &&
+     ((how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_LCD))
+  {
+    denom = gnc_numeric_lcd(a, b);
+    how   = how & GNC_NUMERIC_RND_MASK;
+  }
+
+  if(a.denom < 0) {
+    a.num *= a.denom;
+    a.denom = 1;
+  }
+
+  if(b.denom < 0) {
+    b.num *= b.denom;
+    b.denom = 1;
+  }
+
+  bignume = mult128 (a.num, b.num);
+  bigdeno = mult128 (a.denom, b.denom);
+  product.num   = a.num*b.num;
+  product.denom = a.denom*b.denom;
+
+  /* If it looks to be overflowing, try to reduce the fraction ... */
+  if (bignume.isbig || bigdeno.isbig)
+  {
+    gint64 tmp;
+    a = gnc_numeric_reduce (a);
+    b = gnc_numeric_reduce (b);
+    tmp = a.num;
+    a.num = b.num;
+    b.num = tmp;
+    a = gnc_numeric_reduce (a);
+    b = gnc_numeric_reduce (b);
+
+    bignume = mult128 (a.num, b.num);
+    bigdeno = mult128 (a.denom, b.denom);
+    product.num   = a.num*b.num;
+    product.denom = a.denom*b.denom;
+  }
+
+  /* If it its still overflowing, and rounding is allowed then round */
+  if (bignume.isbig || bigdeno.isbig)
+  {
+    /* If rounding allowed, then shift until there's no
+     * more overflow. The conversion at the end will fix
+     * things up for the final value. Else overflow. */
+    if ((how & GNC_NUMERIC_RND_MASK) == GNC_HOW_RND_NEVER)
+    {
+      if (bigdeno.isbig)
+      {
+        return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+      }
+      product = reduce128 (bignume, product.denom);
+      if (gnc_numeric_check (product))
+      {
+        return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+      }
+    }
+    else
+    {
+      while (bignume.isbig || bigdeno.isbig)
+      {
+         bignume = shift128 (bignume);
+         bigdeno = shift128 (bigdeno);
+      }
+      product.num = bignume.lo;
+      if (bignume.isneg) product.num = -product.num;
+
+      product.denom = bigdeno.lo;
+      if (0 == product.denom)
+      {
+        return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+      }
+    }
+  }
+
+#if 0  /* currently, product denom won't ever be zero */
+  if(product.denom < 0) {
+    product.num   = -product.num;
+    product.denom = -product.denom;
+  }
+#endif
+
+  result = gnc_numeric_convert(product, denom, how);
+  return result;
+}
+
+
+/********************************************************************
+ *  gnc_numeric_div
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_div(gnc_numeric a, gnc_numeric b,
+                gint64 denom, gint how)
+{
+  gnc_numeric quotient;
+  qofint128 nume, deno;
+
+  if(gnc_numeric_check(a) || gnc_numeric_check(b))
+  {
+    return gnc_numeric_error(GNC_ERROR_ARG);
+  }
+
+  if((denom == GNC_DENOM_AUTO) &&
+     (how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_FIXED)
+  {
+    if(a.denom == b.denom)
+    {
+      denom = a.denom;
+    }
+    else if(a.denom == 0)
+    {
+      denom = b.denom;
+    }
+    else
+    {
+      return gnc_numeric_error(GNC_ERROR_DENOM_DIFF);
+    }
+  }
+
+
+  if(a.denom < 0)
+  {
+    a.num *= a.denom;
+    a.denom = 1;
+  }
+
+  if(b.denom < 0)
+  {
+    b.num *= b.denom;
+    b.denom = 1;
+  }
+
+  if(a.denom == b.denom)
+  {
+    quotient.num = a.num;
+    quotient.denom = b.num;
+  }
+  else
+  {
+    gint64 sgn = 1;
+    if (0 > a.num)
+    {
+      sgn = -sgn;
+      a.num = -a.num;
+    }
+    if (0 > b.num)
+    {
+      sgn = -sgn;
+      b.num = -b.num;
+    }
+    nume = mult128(a.num, b.denom);
+    deno = mult128(b.num, a.denom);
+
+    /* Try to avoid overflow by removing common factors */
+    if (nume.isbig && deno.isbig)
+    {
+      gnc_numeric ra = gnc_numeric_reduce (a);
+      gnc_numeric rb = gnc_numeric_reduce (b);
+
+      gint64 gcf_nume = gcf64(ra.num, rb.num);
+      gint64 gcf_deno = gcf64(rb.denom, ra.denom);
+      nume = mult128(ra.num/gcf_nume, rb.denom/gcf_deno);
+      deno = mult128(rb.num/gcf_nume, ra.denom/gcf_deno);
+    }
+
+    if ((0 == nume.isbig) && (0 == deno.isbig))
+    {
+      quotient.num = sgn * nume.lo;
+      quotient.denom = deno.lo;
+      goto dive_done;
+    }
+    else if (0 == deno.isbig)
+    {
+      quotient = reduce128 (nume, deno.lo);
+      if (0 == gnc_numeric_check (quotient))
+      {
+        quotient.num *= sgn;
+        goto dive_done;
+      }
+    }
+
+    /* If rounding allowed, then shift until there's no
+     * more overflow. The conversion at the end will fix
+     * things up for the final value. */
+    if ((how & GNC_NUMERIC_RND_MASK) == GNC_HOW_RND_NEVER)
+    {
+      return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+    }
+    while (nume.isbig || deno.isbig)
+    {
+       nume = shift128 (nume);
+       deno = shift128 (deno);
+    }
+    quotient.num = sgn * nume.lo;
+    quotient.denom = deno.lo;
+    if (0 == quotient.denom)
+    {
+      return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+    }
+  }
+
+  if(quotient.denom < 0)
+  {
+    quotient.num   = -quotient.num;
+    quotient.denom = -quotient.denom;
+  }
+
+dive_done:
+  if((denom == GNC_DENOM_AUTO) &&
+     ((how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_LCD))
+  {
+    denom = gnc_numeric_lcd(a, b);
+    how   = how & GNC_NUMERIC_RND_MASK;
+  }
+
+  return gnc_numeric_convert(quotient, denom, how);
+}
+
+/********************************************************************
+ *  gnc_numeric_neg
+ *  negate the argument
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_neg(gnc_numeric a) {
+  if(gnc_numeric_check(a)) {
+    return gnc_numeric_error(GNC_ERROR_ARG);
+  }
+  return gnc_numeric_create(- a.num, a.denom);
+}
+
+/********************************************************************
+ *  gnc_numeric_neg
+ *  return the absolute value of the argument
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_abs(gnc_numeric a)
+{
+  if(gnc_numeric_check(a)) {
+    return gnc_numeric_error(GNC_ERROR_ARG);
+  }
+  return gnc_numeric_create(ABS(a.num), a.denom);
+}
+
+/********************************************************************
+ *  gnc_numeric_convert
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_convert(gnc_numeric in, gint64 denom, gint how)
+{
+  gnc_numeric out;
+  gnc_numeric temp;
+  gint64      temp_bc;
+  gint64      temp_a;
+  gint64      remainder;
+  gint64      sign;
+  gint        denom_neg=0;
+  double      ratio, logratio;
+  double      sigfigs;
+  qofint128 nume, newm;
+
+  temp.num   = 0;
+  temp.denom = 0;
+
+  if(gnc_numeric_check(in)) {
+    return gnc_numeric_error(GNC_ERROR_ARG);
+  }
+
+  if(denom == GNC_DENOM_AUTO)
+  {
+    switch(how & GNC_NUMERIC_DENOM_MASK)
+    {
+    default:
+    case GNC_HOW_DENOM_LCD:   /* LCD is meaningless with AUTO in here */
+    case GNC_HOW_DENOM_EXACT:
+      return in;
+      break;
+
+    case GNC_HOW_DENOM_REDUCE:
+      /* reduce the input to a relatively-prime fraction */
+      return gnc_numeric_reduce(in);
+      break;
+
+    case GNC_HOW_DENOM_FIXED:
+      if(in.denom != denom) {
+        return gnc_numeric_error(GNC_ERROR_DENOM_DIFF);
+      }
+      else {
+        return in;
+      }
+      break;
+
+    case GNC_HOW_DENOM_SIGFIG:
+      ratio    = fabs(gnc_numeric_to_double(in));
+      if(ratio < 10e-20) {
+        logratio = 0;
+      }
+      else {
+        logratio = log10(ratio);
+        logratio = ((logratio > 0.0) ?
+                    (floor(logratio)+1.0) : (ceil(logratio)));
+      }
+      sigfigs  = GNC_HOW_GET_SIGFIGS(how);
+
+      if(sigfigs-logratio >= 0) {
+        denom    = (gint64)(pow(10, sigfigs-logratio));
+      }
+      else {
+        denom    = -((gint64)(pow(10, logratio-sigfigs)));
+      }
+
+      how = how & ~GNC_HOW_DENOM_SIGFIG & ~GNC_NUMERIC_SIGFIGS_MASK;
+      break;
+
+    }
+  }
+
+  /* Make sure we need to do the work */
+  if(in.denom == denom) {
+    return in;
+  }
+
+  /* If the denominator of the input value is negative, get rid of that. */
+  if(in.denom < 0) {
+    in.num = in.num * (- in.denom);
+    in.denom = 1;
+  }
+
+  sign = (in.num < 0) ? -1 : 1;
+
+  /* If the denominator is less than zero, we are to interpret it as
+   * the reciprocal of its magnitude. */
+  if(denom < 0)
+  {
+
+    /* XXX FIXME: use 128-bit math here ... */
+    denom     = - denom;
+    denom_neg = 1;
+    temp_a    = (in.num < 0) ? -in.num : in.num;
+    temp_bc   = in.denom * denom;
+    remainder = in.num % temp_bc;
+    out.num   = in.num / temp_bc;
+    out.denom = - denom;
+  }
+  else
+  {
+    /* Do all the modulo and int division on positive values to make
+     * things a little clearer. Reduce the fraction denom/in.denom to
+     * help with range errors */
+    temp.num   = denom;
+    temp.denom = in.denom;
+    temp       = gnc_numeric_reduce(temp);
+
+    /* Symbolically, do the following:
+     * out.num   = in.num * temp.num;
+     * remainder = out.num % temp.denom;
+     * out.num   = out.num / temp.denom;
+     * out.denom = denom;
+     */
+    nume = mult128 (in.num, temp.num);
+    newm = div128 (nume, temp.denom);
+    remainder = rem128 (nume, temp.denom);
+
+    if (newm.isbig)
+    {
+       return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+    }
+
+    out.num = newm.lo;
+    out.denom = denom;
+  }
+
+  if (remainder)
+  {
+    switch(how & GNC_NUMERIC_RND_MASK)
+    {
+    case GNC_HOW_RND_FLOOR:
+      if(sign < 0) {
+        out.num = out.num + 1;
+      }
+      break;
+
+    case GNC_HOW_RND_CEIL:
+      if(sign > 0) {
+        out.num = out.num + 1;
+      }
+      break;
+
+    case GNC_HOW_RND_TRUNC:
+      break;
+
+    case GNC_HOW_RND_PROMOTE:
+      out.num = out.num + 1;
+      break;
+
+    case GNC_HOW_RND_ROUND_HALF_DOWN:
+      if(denom_neg)
+      {
+        if((2 * remainder) > in.denom*denom)
+        {
+          out.num = out.num + 1;
+        }
+      }
+      else if((2 * remainder) > temp.denom)
+      {
+        out.num = out.num + 1;
+      }
+      /* check that 2*remainder didn't over-flow */
+      else if (((2 * remainder) < remainder) &&
+               (remainder > (temp.denom / 2)))
+      {
+        out.num = out.num + 1;
+      }
+      break;
+
+    case GNC_HOW_RND_ROUND_HALF_UP:
+      if(denom_neg)
+      {
+        if((2 * remainder) >= in.denom*denom)
+        {
+          out.num = out.num + 1;
+        }
+      }
+      else if((2 * remainder ) >= temp.denom)
+      {
+        out.num = out.num + 1;
+      }
+      /* check that 2*remainder didn't over-flow */
+      else if (((2 * remainder) < remainder) &&
+               (remainder >= (temp.denom / 2)))
+      {
+        out.num = out.num + 1;
+      }
+      break;
+
+    case GNC_HOW_RND_ROUND:
+      if(denom_neg)
+      {
+        if((2 * remainder) > in.denom*denom)
+        {
+          out.num = out.num + 1;
+        }
+        else if((2 * remainder) == in.denom*denom)
+        {
+          if(out.num % 2)
+          {
+            out.num = out.num + 1;
+          }
+        }
+      }
+      else
+      {
+        if((2 * remainder ) > temp.denom)
+        {
+          out.num = out.num + 1;
+        }
+        /* check that 2*remainder didn't over-flow */
+        else if (((2 * remainder) < remainder) &&
+                 (remainder > (temp.denom / 2)))
+        {
+          out.num = out.num + 1;
+        }
+        else if((2 * remainder) == temp.denom)
+        {
+          if(out.num % 2)
+          {
+            out.num = out.num + 1;
+          }
+        }
+        /* check that 2*remainder didn't over-flow */
+        else if (((2 * remainder) < remainder) &&
+                 (remainder ==  (temp.denom / 2)))
+        {
+          if(out.num % 2)
+          {
+            out.num = out.num + 1;
+          }
+        }
+      }
+      break;
+
+    case GNC_HOW_RND_NEVER:
+      return gnc_numeric_error(GNC_ERROR_REMAINDER);
+      break;
+    }
+  }
+
+  out.num = (sign > 0) ? out.num : (-out.num);
+
+  return out;
+}
+
+
+/********************************************************************
+ ** reduce a fraction by GCF elimination.  This is NOT done as a
+ *  part of the arithmetic API unless GNC_HOW_DENOM_REDUCE is specified
+ *  as the output denominator.
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_reduce(gnc_numeric in)
+{
+  gint64   t;
+  gint64   num = (in.num < 0) ? (- in.num) : in.num ;
+  gint64   denom = in.denom;
+  gnc_numeric out;
+
+  if(gnc_numeric_check(in))
+  {
+    return gnc_numeric_error(GNC_ERROR_ARG);
+  }
+
+  /* The strategy is to use Euclid's algorithm */
+  while (denom > 0) {
+    t = num % denom;
+    num = denom;
+    denom = t;
+  }
+  /* num now holds the GCD (Greatest Common Divisor) */
+
+  /* All calculations are done on positive num, since it's not
+   * well defined what % does for negative values */
+  out.num   = in.num / num;
+  out.denom = in.denom / num;
+  return out;
+}
+
+/********************************************************************
+ *  double_to_gnc_numeric
+ ********************************************************************/
+
+gnc_numeric
+double_to_gnc_numeric(double in, gint64 denom, gint how)
+{
+  gnc_numeric out;
+  gint64 int_part=0;
+  double frac_part;
+  gint64 frac_int=0;
+  double logval;
+  double sigfigs;
+
+  if((denom == GNC_DENOM_AUTO) && (how & GNC_HOW_DENOM_SIGFIG))
+  {
+    if(fabs(in) < 10e-20) {
+      logval = 0;
+    }
+    else {
+      logval   = log10(fabs(in));
+      logval   = ((logval > 0.0) ?
+                  (floor(logval)+1.0) : (ceil(logval)));
+    }
+    sigfigs  = GNC_HOW_GET_SIGFIGS(how);
+    if(sigfigs-logval >= 0) {
+      denom    = (gint64)(pow(10, sigfigs-logval));
+    }
+    else {
+      denom    = -((gint64)(pow(10, logval-sigfigs)));
+    }
+
+    how =  how & ~GNC_HOW_DENOM_SIGFIG & ~GNC_NUMERIC_SIGFIGS_MASK;
+  }
+
+  int_part  = (gint64)(floor(fabs(in)));
+  frac_part = in - (double)int_part;
+
+  int_part = int_part * denom;
+  frac_part = frac_part * (double)denom;
+
+  switch(how & GNC_NUMERIC_RND_MASK) {
+  case GNC_HOW_RND_FLOOR:
+    frac_int = (gint64)floor(frac_part);
+    break;
+
+  case GNC_HOW_RND_CEIL:
+    frac_int = (gint64)ceil(frac_part);
+    break;
+
+  case GNC_HOW_RND_TRUNC:
+    frac_int = (gint64)frac_part;
+    break;
+
+  case GNC_HOW_RND_ROUND:
+  case GNC_HOW_RND_ROUND_HALF_UP:
+    frac_int = (gint64)rint(frac_part);
+    break;
+
+  case GNC_HOW_RND_NEVER:
+    frac_int = (gint64)floor(frac_part);
+    if(frac_part != (double) frac_int) {
+      /* signal an error */
+    }
+    break;
+  }
+
+  out.num   = int_part + frac_int;
+  out.denom = denom;
+  return out;
+}
+
+/********************************************************************
+ *  gnc_numeric_to_double
+ ********************************************************************/
+
+double
+gnc_numeric_to_double(gnc_numeric in)
+{
+  if(in.denom > 0)
+  {
+    return (double)in.num/(double)in.denom;
+  }
+  else
+  {
+    return (double)(in.num * in.denom);
+  }
+}
+
+/********************************************************************
+ *  gnc_numeric_error
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_error(GNCNumericErrorCode error_code)
+{
+  return gnc_numeric_create(error_code, 0LL);
+}
+
+
+/********************************************************************
+ *  gnc_numeric_add_with_error
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_add_with_error(gnc_numeric a, gnc_numeric b,
+                           gint64 denom, gint how,
+                           gnc_numeric * error)
+{
+
+  gnc_numeric sum   = gnc_numeric_add(a, b, denom, how);
+  gnc_numeric exact = gnc_numeric_add(a, b, GNC_DENOM_AUTO,
+                                      GNC_HOW_DENOM_REDUCE);
+  gnc_numeric err   = gnc_numeric_sub(sum, exact, GNC_DENOM_AUTO,
+                                      GNC_HOW_DENOM_REDUCE);
+
+  if(error) {
+    *error = err;
+  }
+  return sum;
+}
+
+/********************************************************************
+ *  gnc_numeric_sub_with_error
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_sub_with_error(gnc_numeric a, gnc_numeric b,
+                           gint64 denom, gint how,
+                           gnc_numeric * error)
+{
+  gnc_numeric diff  = gnc_numeric_sub(a, b, denom, how);
+  gnc_numeric exact = gnc_numeric_sub(a, b, GNC_DENOM_AUTO,
+                                      GNC_HOW_DENOM_REDUCE);
+  gnc_numeric err   = gnc_numeric_sub(diff, exact, GNC_DENOM_AUTO,
+                                      GNC_HOW_DENOM_REDUCE);
+  if(error) {
+    *error = err;
+  }
+  return diff;
+}
+
+
+/********************************************************************
+ *  gnc_numeric_mul_with_error
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_mul_with_error(gnc_numeric a, gnc_numeric b,
+                           gint64 denom, gint how,
+                           gnc_numeric * error)
+{
+  gnc_numeric prod  = gnc_numeric_mul(a, b, denom, how);
+  gnc_numeric exact = gnc_numeric_mul(a, b, GNC_DENOM_AUTO,
+                                      GNC_HOW_DENOM_REDUCE);
+  gnc_numeric err   = gnc_numeric_sub(prod, exact, GNC_DENOM_AUTO,
+                                      GNC_HOW_DENOM_REDUCE);
+  if(error) {
+    *error = err;
+  }
+  return prod;
+}
+
+
+/********************************************************************
+ *  gnc_numeric_div_with_error
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_div_with_error(gnc_numeric a, gnc_numeric b,
+                           gint64 denom, gint how,
+                           gnc_numeric * error)
+{
+  gnc_numeric quot  = gnc_numeric_div(a, b, denom, how);
+  gnc_numeric exact = gnc_numeric_div(a, b, GNC_DENOM_AUTO,
+                                      GNC_HOW_DENOM_REDUCE);
+  gnc_numeric err   = gnc_numeric_sub(quot, exact,
+                                      GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
+  if(error) {
+    *error = err;
+  }
+  return quot;
+}
+
+/********************************************************************
+ *  gnc_numeric text IO
+ ********************************************************************/
+
+gchar *
+gnc_numeric_to_string(gnc_numeric n)
+{
+  gchar *result;
+  gint64 tmpnum = n.num;
+  gint64 tmpdenom = n.denom;
+
+  result = g_strdup_printf("%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, tmpnum, tmpdenom);
+
+  return result;
+}
+
+gchar *
+gnc_num_dbg_to_string(gnc_numeric n)
+{
+  static char buff[1000];
+  static char *p = buff;
+  gint64 tmpnum = n.num;
+  gint64 tmpdenom = n.denom;
+
+  p+= 100;
+  if (p-buff >= 1000) p = buff;
+
+  sprintf(p, "%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, tmpnum, tmpdenom);
+
+  return p;
+}
+
+gboolean
+string_to_gnc_numeric(const gchar* str, gnc_numeric *n)
+{
+  size_t num_read;
+  gint64 tmpnum;
+  gint64 tmpdenom;
+
+  if(!str) return FALSE;
+
+#ifdef GNC_DEPRECATED
+  /* must use "<" here because %n's effects aren't well defined */
+  if(sscanf(str, " " GNC_SCANF_LLD "/" GNC_SCANF_LLD "%n",
+            &tmpnum, &tmpdenom, &num_read) < 2) {
+    return FALSE;
+  }
+#else
+  tmpnum = strtoll (str, NULL, 0);
+  str = strchr (str, '/');
+  if (!str) return FALSE;
+  str ++;
+  tmpdenom = strtoll (str, NULL, 0);
+  num_read = strspn (str, "0123456789");
+#endif
+  n->num = tmpnum;
+  n->denom = tmpdenom;
+  return TRUE;
+}
+
+/********************************************************************
+ *  gnc_numeric misc testing
+ ********************************************************************/
+#ifdef _GNC_NUMERIC_TEST
+
+static char *
+gnc_numeric_print(gnc_numeric in)
+{
+  char * retval;
+  if(gnc_numeric_check(in)) {
+    retval = g_strdup_printf("<ERROR> [%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT "]",
+                             in.num,
+                             in.denom); 
+  }
+  else {
+    retval = g_strdup_printf("[%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT "]",
+                             in.num,
+                             in.denom); 
+  }
+  return retval;
+}
+
+int
+main(int argc, char ** argv)
+{
+  gnc_numeric a = gnc_numeric_create(1, 3);
+  gnc_numeric b = gnc_numeric_create(1, 4);
+  gnc_numeric c;
+
+  gnc_numeric err;
+
+  c = gnc_numeric_add_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
+  printf("add 100ths/error : %s + %s = %s + (error) %s\n\n",
+         gnc_numeric_print(a), gnc_numeric_print(b),
+         gnc_numeric_print(c),
+         gnc_numeric_print(err));
+
+  c = gnc_numeric_sub_with_error(a, b, 100, GNC_HOW_RND_FLOOR, &err);
+  printf("sub 100ths/error : %s - %s = %s + (error) %s\n\n",
+         gnc_numeric_print(a), gnc_numeric_print(b),
+         gnc_numeric_print(c),
+         gnc_numeric_print(err));
+
+  c = gnc_numeric_mul_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
+  printf("mul 100ths/error : %s * %s = %s + (error) %s\n\n",
+         gnc_numeric_print(a), gnc_numeric_print(b),
+         gnc_numeric_print(c),
+         gnc_numeric_print(err));
+
+  c = gnc_numeric_div_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
+  printf("div 100ths/error : %s / %s = %s + (error) %s\n\n",
+         gnc_numeric_print(a), gnc_numeric_print(b),
+         gnc_numeric_print(c),
+         gnc_numeric_print(err));
+
+  printf("multiply (EXACT): %s * %s = %s\n",
+	 gnc_numeric_print(a), gnc_numeric_print(b),
+	 gnc_numeric_print(gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT)));
+
+  printf("multiply (REDUCE): %s * %s = %s\n",
+	 gnc_numeric_print(a), gnc_numeric_print(b),
+	 gnc_numeric_print(gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE)));
+
+
+  return 0;
+}
+#endif
+
+/* ======================== END OF FILE =================== */

Added: gnucash/trunk/lib/libqof/qof/gnc-numeric.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-numeric.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-numeric.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,515 @@
+/********************************************************************
+ * gnc-numeric.h - A rational number library                        *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ *******************************************************************/
+
+/** @addtogroup Numeric
+
+    The 'Numeric' functions provide a way of working with rational
+    numbers while maintaining strict control over rounding errors
+    when adding rationals with different denominators.  The Numeric
+    class is primarily used for working with monetary amounts, 
+    where the denominator typically represents the smallest fraction
+    of the currency (e.g. pennies, centimes).  The numeric class
+    can handle any fraction (e.g. twelfth's) and is not limited
+    to fractions that are powers of ten.  
+
+    A 'Numeric' value represents a number in rational form, with a
+    64-bit integer as numerator and denominator.  Rationals are
+    ideal for many uses, such as performing exact, roundoff-error-free
+    addition and multiplication, but 64-bit rationals do not have 
+    the dynamic range of floating point numbers.  
+
+EXAMPLE\n
+-------\n
+The following program finds the best ::gnc_numeric approximation to
+the \a math.h constant \a M_PI given a maximum denominator. For
+large denominators, the ::gnc_numeric approximation is accurate to
+more decimal places than will generally be needed, but in some cases
+this may not be good enough. For example,
+
+ at verbatim
+    M_PI                   = 3.14159265358979323846
+    245850922 / 78256779   = 3.14159265358979311599  (16 sig figs)
+    3126535 / 995207       = 3.14159265358865047446  (12 sig figs)
+    355 / 113              = 3.14159292035398252096  (7 sig figs)
+ at endverbatim
+
+ at verbatim
+#include <glib.h>
+#include "gnc-numeric.h"
+#include <math.h>
+
+int
+main(int argc, char ** argv)
+{
+  gnc_numeric approx, best;
+  double err, best_err=1.0;
+  double m_pi = M_PI;
+  gint64 denom;
+  gint64 max;
+
+  sscanf(argv[1], "%Ld", &max);
+  
+  for (denom = 1; denom < max; denom++)
+  {
+    approx = double_to_gnc_numeric (m_pi, denom, GNC_RND_ROUND);
+    err    = m_pi - gnc_numeric_to_double (approx);
+    if (fabs (err) < fabs (best_err))
+    {
+      best = approx;
+      best_err = err;
+      printf ("%Ld / %Ld = %.30f\n", gnc_numeric_num (best),
+              gnc_numeric_denom (best), gnc_numeric_to_double (best));
+    }
+  }
+}
+ at endverbatim
+
+@{ */
+/** @file gnc-numeric.h
+    @brief An exact-rational-number library for gnucash.
+    @author Copyright (C) 2000 Bill Gribble
+    @author Copyright (C) 2004 Linas Vepstas <linas at linas.org>
+*/
+
+
+#ifndef GNC_NUMERIC_H
+#define GNC_NUMERIC_H
+
+#include <glib.h>
+
+struct _gnc_numeric 
+{
+  gint64  num;
+  gint64  denom;
+};
+
+/** @brief An rational-number type
+ *
+ * This is a rational number, defined by numerator and denominator. */
+typedef struct _gnc_numeric gnc_numeric;
+
+/** @name Arguments Standard Arguments to most functions
+
+    Most of the gnc_numeric arithmetic functions take two arguments
+    in addition to their numeric args: 'denom', which is the denominator
+    to use in the output gnc_numeric object, and 'how'. which
+    describes how the arithmetic result is to be converted to that
+    denominator. This combination of output denominator and rounding policy
+    allows the results of financial and other rational computations to be
+    properly rounded to the appropriate units.
+
+    Valid values for denom are:
+    GNC_DENOM_AUTO  -- compute denominator exactly
+    integer n       -- Force the denominator of teh result to be this integer
+    GNC_DENOM_RECIPROCAL -- Use 1/n as the denominator (???huh???)
+
+    Valid values for 'how' are bitwise combinations of zero or one
+    "rounding instructions" with zero or one "denominator types".
+    Valid rounding instructions are:
+        GNC_HOW_RND_FLOOR
+        GNC_HOW_RND_CEIL 
+        GNC_HOW_RND_TRUNC
+        GNC_HOW_RND_PROMOTE 
+        GNC_HOW_RND_ROUND_HALF_DOWN
+        GNC_HOW_RND_ROUND_HALF_UP 
+        GNC_HOW_RND_ROUND
+        GNC_HOW_RND_NEVER
+
+    The denominator type specifies how to compute a denominator if
+    GNC_DENOM_AUTO is specified as the 'denom'. Valid 
+    denominator types are:
+        GNC_HOW_DENOM_EXACT  
+        GNC_HOW_DENOM_REDUCE 
+        GNC_HOW_DENOM_LCD   
+        GNC_HOW_DENOM_FIXED 
+        GNC_HOW_DENOM_SIGFIGS(N)
+
+   To use traditional rational-number operational semantics (all results
+   are exact and are reduced to relatively-prime fractions) pass the
+   argument GNC_DENOM_AUTO as 'denom' and 
+   GNC_HOW_DENOM_REDUCE| GNC_HOW_RND_NEVER as 'how'.
+
+   To enforce strict financial semantics (such that all operands must have
+   the same denominator as each other and as the result), use
+   GNC_DENOM_AUTO as 'denom' and 
+   GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER as 'how'.
+@{
+*/
+
+/** \brief bitmasks for HOW flags.
+
+ * bits 8-15 of 'how' are reserved for the number of significant
+ * digits to use in the output with GNC_HOW_DENOM_SIGFIG 
+ */ 
+#define GNC_NUMERIC_RND_MASK     0x0000000f
+#define GNC_NUMERIC_DENOM_MASK   0x000000f0
+#define GNC_NUMERIC_SIGFIGS_MASK 0x0000ff00
+
+/** \brief Rounding/Truncation modes for operations.
+
+ *  Rounding instructions control how fractional parts in the specified
+ *  denominator affect the result. For example, if a computed result is
+ *  "3/4" but the specified denominator for the return value is 2, should
+ *  the return value be "1/2" or "2/2"?  
+ *
+ * Possible rounding instructions are:
+ */
+enum { 
+  /** Round toward -infinity */
+  GNC_HOW_RND_FLOOR            = 0x01, 
+
+  /** Round toward +infinity */
+  GNC_HOW_RND_CEIL             = 0x02,  
+
+  /** Truncate fractions (round toward zero) */
+  GNC_HOW_RND_TRUNC            = 0x03,
+
+  /** Promote fractions (round away from zero) */
+  GNC_HOW_RND_PROMOTE          = 0x04,
+
+  /** Round to the nearest integer, rounding toward zero 
+   *  when there are two equidistant nearest integers.
+   */
+  GNC_HOW_RND_ROUND_HALF_DOWN  = 0x05, 
+
+  /** Round to the nearest integer, rounding away from zero 
+   *  when there are two equidistant nearest integers.
+   */
+  GNC_HOW_RND_ROUND_HALF_UP    = 0x06, 
+
+  /** Use unbiased ("banker's") rounding. This rounds to the 
+   *  nearest integer, and to the nearest even integer when there 
+   *  are two equidistant nearest integers. This is generally the 
+   *  one you should use for financial quantities.
+   */
+  GNC_HOW_RND_ROUND            = 0x07, 
+
+  /** Never round at all, and signal an error if there is a 
+   *  fractional result in a computation.
+   */
+  GNC_HOW_RND_NEVER            = 0x08
+};
+
+/** How to compute a denominator, or'ed into the "how" field. */
+enum { 
+  /** Use any denominator which gives an exactly correct ratio of 
+   *  numerator to denominator. Use EXACT when you do not wish to 
+   *  lose any information in the result but also do not want to 
+   *  spend any time finding the "best" denominator.
+   */
+  GNC_HOW_DENOM_EXACT  = 0x10, 
+
+  /** Reduce the result value by common factor elimination, 
+   *  using the smallest possible value for the denominator that 
+   *  keeps the correct ratio. The numerator and denominator of 
+   *  the result are relatively prime. 
+   */
+  GNC_HOW_DENOM_REDUCE = 0x20,
+
+  /** Find the least common multiple of the arguments' denominators 
+   *  and use that as the denominator of the result.
+   */
+  GNC_HOW_DENOM_LCD    = 0x30,
+
+  /** All arguments are required to have the same denominator,
+   *  that denominator is to be used in the output, and an error 
+   *  is to be signaled if any argument has a different denominator.
+   */
+  GNC_HOW_DENOM_FIXED  = 0x40,
+
+  /** Round to the number of significant figures given in the rounding
+   *  instructions by the GNC_HOW_DENOM_SIGFIGS () macro.
+   */
+  GNC_HOW_DENOM_SIGFIG = 0x50
+};
+
+/** Build a 'how' value that will generate a denominator that will 
+ *  keep at least n significant figures in the result.
+ */
+#define GNC_HOW_DENOM_SIGFIGS( n ) ( ((( n ) & 0xff) << 8) | GNC_HOW_DENOM_SIGFIG)
+#define GNC_HOW_GET_SIGFIGS( a ) ( (( a ) & 0xff00 ) >> 8)
+
+/** Error codes */
+typedef enum {
+  GNC_ERROR_OK         =  0,   /**< No error */
+  GNC_ERROR_ARG        = -1,   /**< Argument is not a valid number */
+  GNC_ERROR_OVERFLOW   = -2,   /**< Intermediate result overflow */
+
+  /** GNC_HOW_DENOM_FIXED was specified, but argument denominators differed.  */
+  GNC_ERROR_DENOM_DIFF = -3,   
+
+  /** GNC_HOW_RND_NEVER  was specified, but the result could not be
+   *  converted to the desired denominator without a remainder. */
+  GNC_ERROR_REMAINDER  = -4   
+} GNCNumericErrorCode;
+
+
+/** Values that can be passed as the 'denom' argument.  
+ *  The include a positive number n to be used as the 
+ *  denominator of teh output value.  Other possibilities 
+ *  include the list below:
+ */
+
+/** Compute an appropriate denominator automatically. Flags in 
+ *  the 'how' argument will specify how to compute the denominator.
+ */
+#define GNC_DENOM_AUTO 0
+
+/** Use the value 1/n as the denominator of the output value. */
+#define GNC_DENOM_RECIPROCAL( a ) (- ( a ))
+
+/**  @} */
+
+/** @name Constructors
+@{
+*/
+/** Make a gnc_numeric from numerator and denominator */
+static inline 
+gnc_numeric gnc_numeric_create(gint64 num, gint64 denom) {
+  gnc_numeric out;
+  out.num = num;
+  out.denom = denom;
+  return out;
+}
+
+/** create a zero-value gnc_numeric */
+static inline
+gnc_numeric gnc_numeric_zero(void) { return gnc_numeric_create(0, 1); }
+
+/** Convert a floating-point number to a gnc_numeric. 
+ *  Both 'denom' and 'how' are used as in arithmetic, 
+ *  but GNC_DENOM_AUTO is not recognized; a denominator
+ *  must be specified either explicitctly or through sigfigs.
+ */
+gnc_numeric double_to_gnc_numeric(double in, gint64 denom,  
+                                  gint how);
+
+/** Read a gnc_numeric from str, skipping any leading whitespace.
+ *  Return TRUE on success and store the resulting value in "n".
+ *  Return NULL on error. */
+gboolean string_to_gnc_numeric(const gchar* str, gnc_numeric *n);
+
+/** Create a gnc_numeric object that signals the error condition
+ *  noted by error_code, rather than a number. 
+ */
+gnc_numeric gnc_numeric_error(GNCNumericErrorCode error_code);
+/** @} */
+
+/** @name Value Accessors
+ @{
+*/
+/** Return numerator */
+static inline 
+gint64 gnc_numeric_num(gnc_numeric a) { return a.num; }
+/** Return denominator */
+static inline 
+gint64 gnc_numeric_denom(gnc_numeric a) { return a.denom; }
+
+/** Convert numeric to floating-point value. */
+double      gnc_numeric_to_double(gnc_numeric in);
+
+/** Convert to string. The returned buffer is to be g_free'd by the
+ *  caller (it was allocated through g_strdup) */
+gchar *gnc_numeric_to_string(gnc_numeric n);
+
+/** Convert to string. Uses a static, non-thread-safe buffer.
+ *  For internal use only. */
+gchar * gnc_num_dbg_to_string(gnc_numeric n);
+/** @}*/
+
+/** @name Comparisons and Predicates 
+ @{
+*/
+/** Check for error signal in value. Returns GNC_ERROR_OK (==0) if
+ *  the number appears to be valid, otherwise it returns the
+ *  type of error.  Error values always have a denominator of zero.
+ */ 
+GNCNumericErrorCode  gnc_numeric_check(gnc_numeric a);
+
+/** Returns 1 if a>b, -1 if b>a, 0 if a == b  */
+int gnc_numeric_compare(gnc_numeric a, gnc_numeric b);
+
+/** Returns 1 if the given gnc_numeric is 0 (zero), else returns 0. */
+gboolean gnc_numeric_zero_p(gnc_numeric a);
+
+/** Returns 1 if a < 0, otherwise returns 0. */
+gboolean gnc_numeric_negative_p(gnc_numeric a);
+
+/** Returns 1 if a > 0, otherwise returns 0. */
+gboolean gnc_numeric_positive_p(gnc_numeric a);
+
+/** Equivalence predicate: Returns TRUE (1) if a and b are 
+ *  exactly the same (have the same numerator and denominator)
+ */ 
+gboolean gnc_numeric_eq(gnc_numeric a, gnc_numeric b);     
+
+/** Equivalence predicate: Returns TRUE (1) if a and b represent
+ *  the same number.  That is, return TRUE if the ratios, when 
+ *  reduced by eliminating common factors, are identical.
+ */ 
+gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b);  
+
+/** Equivalence predicate: 
+ *  Convert both a and b to denom using the 
+ *  specified DENOM and method HOW, and compare numerators 
+ *  the results using gnc_numeric_equal.
+ *
+  For example, if a == 7/16 and b == 3/4,
+  gnc_numeric_same(a, b, 2, GNC_HOW_RND_TRUNC) == 1
+  because both 7/16 and 3/4 round to 1/2 under truncation. However,
+  gnc_numeric_same(a, b, 2, GNC_HOW_RND_ROUND) == 0
+  because 7/16 rounds to 1/2 under unbiased rounding but 3/4 rounds
+  to 2/2.
+ */ 
+int gnc_numeric_same(gnc_numeric a, gnc_numeric b,   
+                     gint64 denom, gint how);
+/** @} */
+
+/** @name Arithmetic Operations 
+ @{ 
+*/
+/** Return a+b. */
+gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b, 
+                            gint64 denom, gint how);
+
+/** Return a-b. */
+gnc_numeric gnc_numeric_sub(gnc_numeric a, gnc_numeric b, 
+                            gint64 denom, gint how);
+
+/** Multiply a times b, returning the product.  An overflow
+ *  may occur if the result of the multiplication can't
+ *  be represented as a ratio of 64-bit int's after removing
+ *  common factors.
+ */
+gnc_numeric gnc_numeric_mul(gnc_numeric a, gnc_numeric b, 
+                            gint64 denom, gint how);
+
+/** Division.  Note that division can overflow, in the following 
+ *  sense: if we write x=a/b and y=c/d  then x/y = (a*d)/(b*c)  
+ *  If, after eliminating all common factors between the numerator 
+ *  (a*d) and the denominator (b*c),  then if either the numerator 
+ *  and/or the denominator are *still* greater than 2^63, then 
+ *  the division has overflowed.
+ */
+gnc_numeric gnc_numeric_div(gnc_numeric x, gnc_numeric y, 
+                            gint64 denom, gint how);
+/** Negate the argument  */
+gnc_numeric gnc_numeric_neg(gnc_numeric a);
+
+/** Return the absolute value of the argument */
+gnc_numeric gnc_numeric_abs(gnc_numeric a);
+
+/** 
+ * Shortcut for common case: gnc_numeric_add(a, b, GNC_DENOM_AUTO,
+ *                        GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
+ */
+static inline
+gnc_numeric gnc_numeric_add_fixed(gnc_numeric a, gnc_numeric b) {
+   return gnc_numeric_add(a, b, GNC_DENOM_AUTO,
+                         GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
+}
+
+/** 
+ * Shortcut for most common case: gnc_numeric_sub(a, b, GNC_DENOM_AUTO,
+ *                        GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
+ */
+static inline 
+gnc_numeric gnc_numeric_sub_fixed(gnc_numeric a, gnc_numeric b) {
+  return gnc_numeric_sub(a, b, GNC_DENOM_AUTO,
+                         GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
+}
+/** @} */
+
+/** @name Arithmetic Functions with Exact Error Returns 
+ @{
+*/
+/** The same as gnc_numeric_add, but uses 'error' for accumulating
+ *  conversion roundoff error. */
+gnc_numeric gnc_numeric_add_with_error(gnc_numeric a, gnc_numeric b,
+                                       gint64 denom, gint how,
+                                       gnc_numeric * error);
+
+/** The same as gnc_numeric_sub, but uses error for accumulating
+ *  conversion roundoff error. */
+gnc_numeric gnc_numeric_sub_with_error(gnc_numeric a, gnc_numeric b,
+                                       gint64 denom, gint how,
+                                       gnc_numeric * error);
+
+/** The same as gnc_numeric_mul, but uses error for 
+ *  accumulating conversion roundoff error.
+ */
+gnc_numeric gnc_numeric_mul_with_error(gnc_numeric a, gnc_numeric b,
+                                       gint64 denom, gint how,
+                                       gnc_numeric * error);
+
+/** The same as gnc_numeric_div, but uses error for 
+ *  accumulating conversion roundoff error.
+ */
+gnc_numeric gnc_numeric_div_with_error(gnc_numeric a, gnc_numeric b,
+                                       gint64 denom, gint how,
+                                       gnc_numeric * error);
+/** @} */
+
+/** @name Change Denominator 
+ @{
+*/
+/** Change the denominator of a gnc_numeric value to the 
+ *  specified denominator under standard arguments 
+ *  'denom' and 'how'. 
+ */
+gnc_numeric gnc_numeric_convert(gnc_numeric in, gint64 denom, 
+                                gint how);
+
+/** Same as gnc_numeric_convert, but return a remainder 
+ *  value for accumulating conversion error. 
+*/
+gnc_numeric gnc_numeric_convert_with_error(gnc_numeric in, gint64 denom, 
+                                           gint how,
+                                           gnc_numeric * error);
+
+/** Return input after reducing it by Greated Common Factor (GCF) 
+ *  elimination */
+gnc_numeric gnc_numeric_reduce(gnc_numeric in);
+/** @} */
+
+/** @name Deprecated, backwards-compatible definitions 
+  @{
+*/
+#define GNC_RND_FLOOR	GNC_HOW_RND_FLOOR
+#define GNC_RND_CEIL 	GNC_HOW_RND_CEIL 
+#define GNC_RND_TRUNC	GNC_HOW_RND_TRUNC
+#define GNC_RND_PROMOTE 	GNC_HOW_RND_PROMOTE 
+#define GNC_RND_ROUND_HALF_DOWN	GNC_HOW_RND_ROUND_HALF_DOWN
+#define GNC_RND_ROUND_HALF_UP 	GNC_HOW_RND_ROUND_HALF_UP 
+#define GNC_RND_ROUND	GNC_HOW_RND_ROUND
+#define GNC_RND_NEVER	GNC_HOW_RND_NEVER
+
+#define GNC_DENOM_EXACT  	GNC_HOW_DENOM_EXACT  
+#define GNC_DENOM_REDUCE 	GNC_HOW_DENOM_REDUCE 
+#define GNC_DENOM_LCD   	GNC_HOW_DENOM_LCD   
+#define GNC_DENOM_FIXED 	GNC_HOW_DENOM_FIXED 
+#define GNC_DENOM_SIGFIG 	GNC_HOW_DENOM_SIGFIG 
+
+#define GNC_DENOM_SIGFIGS(X)  GNC_HOW_DENOM_SIGFIGS(X)
+#define GNC_NUMERIC_GET_SIGFIGS(X) GNC_HOW_GET_SIGFIGS(X)
+/** @} */
+/** @} */
+#endif

Added: gnucash/trunk/lib/libqof/qof/gnc-numeric.scm
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-numeric.scm	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-numeric.scm	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,95 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; gnc-numeric.scm : rational number representation for gnucash
+;; Copyright 2000 Bill Gribble <grib at gnumatic.com>
+;; Copyright 2001 Christian Stimming <stimming at tu-harburg.de>
+;; 
+;; This program is free software; you can redistribute it and/or    
+;; modify it under the terms of the GNU General Public License as   
+;; published by the Free Software Foundation; either version 2 of   
+;; the License, or (at your option) any later version.              
+;;                                                                  
+;; This program is distributed in the hope that it will be useful,  
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of   
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    
+;; GNU General Public License for more details.                     
+;;                                                                  
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, contact:
+;;
+;; Free Software Foundation           Voice:  +1-617-542-5942
+;; 59 Temple Place - Suite 330        Fax:    +1-617-542-2652
+;; Boston, MA  02111-1307,  USA       gnu at gnu.org
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; use 'logior' in guile to bit-combine RND and DENOM flags.
+
+(define GNC-RND-FLOOR           1)
+(define GNC-RND-CEIL            2)
+(define GNC-RND-TRUNC           3)
+(define GNC-RND-PROMOTE         4)
+(define GNC-RND-ROUND-HALF-DOWN 5)
+(define GNC-RND-ROUND-HALF-UP   6)
+(define GNC-RND-ROUND           7)
+(define GNC-RND-NEVER           8)
+
+(define GNC-DENOM-AUTO          0)
+(define GNC-DENOM-REDUCE       32)
+(define GNC-DENOM-FIXED        64)
+(define GNC-DENOM-LCD          48)
+(define GNC-DENOM-SIGFIG       80)
+
+(define (GNC-DENOM-SIGFIGS n)
+  (logior GNC-DENOM-SIGFIG (* n 256)))
+
+(define GNC-ERROR-OK            0)
+(define GNC-ERROR-ARG          -1)
+(define GNC-ERROR-OVERFLOW     -2)
+(define GNC-ERROR-DENOM-DIFF   -3)
+(define GNC-ERROR-REMAINDER    -4)
+
+(define <gnc-numeric>
+  (make-record-type "<gnc-numeric>" 
+                    '(num denom)))
+
+(define gnc:make-gnc-numeric 
+  (record-constructor <gnc-numeric>))
+
+(define gnc:gnc-numeric? 
+  (record-predicate <gnc-numeric>))
+
+(define gnc:gnc-numeric-num
+  (record-accessor <gnc-numeric> 'num))
+
+(define gnc:gnc-numeric-denom
+  (record-accessor <gnc-numeric> 'denom))
+
+(define (gnc:gnc-numeric-denom-reciprocal arg)
+  (- arg))
+
+
+
+(define <gnc-monetary> 
+  (make-record-type "<gnc-monetary>" 
+		    '(commodity amount)))
+
+;; Constructor; takes one <gnc:commodity*> and one <gnc-numeric>
+(define (gnc:make-gnc-monetary c a)
+  (if (and (gw:wcp-is-of-type? <gnc:commodity*> c) (gnc:gnc-numeric? a))
+      ((record-constructor <gnc-monetary>) c a)
+      (warn "wrong arguments for gnc:make-gnc-monetary: " c a)))
+
+(define gnc:gnc-monetary? 
+  (record-predicate <gnc-monetary>))
+
+(define gnc:gnc-monetary-commodity
+  (record-accessor <gnc-monetary> 'commodity))
+
+(define gnc:gnc-monetary-amount
+  (record-accessor <gnc-monetary> 'amount))
+
+(define (gnc:monetary-neg a)
+  (if (gnc:gnc-monetary? a)
+      (gnc:make-gnc-monetary 
+       (gnc:gnc-monetary-commodity a)
+       (gnc:numeric-neg (gnc:gnc-monetary-amount a)))
+      (warn "wrong arguments for gnc:monetary-neg: " a)))

Added: gnucash/trunk/lib/libqof/qof/gnc-trace.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-trace.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-trace.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,370 @@
+/* *****************************************************************\
+ * gnc-trace.c -- QOF error logging and tracing facility            *
+ * Copyright (C) 1997-2003 Linas Vepstas <linas at linas.org>          *
+ * Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>          *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ *   Author: Rob Clark (rclark at cs.hmc.edu)                          *
+ *   Author: Linas Vepstas (linas at linas.org)                        *
+\********************************************************************/
+
+/** @addtogroup Trace
+@{ */
+
+/** @file gnc-trace.c
+    @brief QOF error logging facility 
+		@author Neil Williams <linux at codehelp.co.uk>
+*/
+
+#include "config.h"
+
+#include <glib.h>
+#ifdef HAVE_UNISTD_H
+#  include <unistd.h>
+#else
+  /* What to do? */
+#endif
+#include <stdarg.h>
+#include <string.h>
+#include <sys/time.h>
+#include "qof.h"
+#include "gnc-trace.h"
+
+static FILE *fout = NULL;
+static gchar* filename = NULL;
+
+static const int MAX_TRACE_FILENAME = 100;
+static GHashTable *log_table = NULL;
+
+AS_STRING_FUNC(gncLogLevel, LOG_LEVEL_LIST)  /**< enum_as_string function
+
+uses the enum_as_string macro from QOF
+but the From macro is not required. Lookups
+are done on the string. */
+
+FROM_STRING_FUNC(gncLogLevel, LOG_LEVEL_LIST)
+
+/* Don't be fooled: gnc_trace_num_spaces has external linkage and
+   static storage, but can't be defined with 'extern' because it has
+   an initializer, and can't be declared with 'static' because that
+   would give it internal linkage. */
+gint __attribute__ ((unused)) gnc_trace_num_spaces = 0;
+
+static void
+fh_printer (const gchar   *log_domain,
+            GLogLevelFlags    log_level,
+            const gchar   *message,
+            gpointer    user_data)
+{
+  extern gint gnc_trace_num_spaces;
+  FILE *fh = user_data;
+  fprintf (fh, "%*s%s\n", gnc_trace_num_spaces, "", message);
+  fflush(fh);
+}
+
+void 
+gnc_log_init (void)
+{
+   if(!fout) //allow gnc_set_logfile
+   {
+	   fout = fopen ("/tmp/qof.trace", "w");
+   }
+
+   if(!fout && (filename = (char *)g_malloc(MAX_TRACE_FILENAME))) {
+      snprintf(filename, MAX_TRACE_FILENAME-1, "/tmp/qof.trace.%d", 
+	       getpid());
+      fout = fopen (filename, "w");
+      g_free(filename);
+   }
+
+   if(!fout)
+      fout = stderr;
+
+   g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MASK, fh_printer, fout);
+}
+
+/* Set the logging level of the given module. */
+void
+gnc_set_log_level(QofLogModule log_module, gncLogLevel level)
+{
+	gchar* level_string;
+
+	if(!log_module || level == 0) { return; }
+	level_string = g_strdup(gncLogLevelasString(level));
+	if(!log_table)
+	{
+		log_table = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+	g_hash_table_insert(log_table, (gpointer)log_module, level_string);
+}
+
+static void
+log_module_foreach(gpointer key, gpointer value, gpointer data)
+{
+	g_hash_table_insert(log_table, key, data);
+}
+
+/* Set the logging level for all known modules. */
+void
+gnc_set_log_level_global(gncLogLevel level)
+{
+	gchar* level_string;
+
+	if(!log_table || level == 0) { return; }
+	level_string = g_strdup(gncLogLevelasString(level));
+	g_hash_table_foreach(log_table, log_module_foreach, level_string);
+}
+
+void
+gnc_set_logfile (FILE *outfile)
+{
+   if(!outfile) { fout = stderr; return; }
+   fout = outfile;
+}
+
+void
+qof_log_init_filename (const gchar* logfilename)
+{
+	if(!logfilename)
+	{
+		fout = stderr;
+	}
+	else
+	{
+		filename = g_strdup(logfilename);
+		fout = fopen(filename, "w");
+	}
+	gnc_log_init();
+}
+
+void
+qof_log_shutdown (void)
+{
+	if(fout && fout != stderr) { fclose(fout); }
+	if(filename) { g_free(filename); }
+	g_hash_table_destroy(log_table);
+}
+
+#define MAX_CHARS 50
+/* gnc_log_prettify() cleans up subroutine names. AIX/xlC has the habit
+ * of printing signatures not names; clean this up. On other operating
+ * systems, truncate name to 30 chars. Note this routine is not thread
+ * safe. Note we wouldn't need this routine if AIX did something more
+ * reasonable. Hope thread safety doesn't poke us in eye. */
+const char *
+gnc_log_prettify (const char *name)
+{
+  static char bf[128];
+  char *p;
+
+  if (!name)
+    return "";
+
+  strncpy (bf, name, MAX_CHARS-1); bf[MAX_CHARS-2] = 0;
+  p = strchr (bf, '(');
+
+  if (p)
+  {
+    *(p+1) = ')';
+    *(p+2) = 0x0;
+  }
+  else
+    strcpy (&bf[MAX_CHARS-4], "...()");
+
+  return bf;
+}
+
+/********************************************************************\
+\********************************************************************/
+
+#define NUM_CLOCKS 10
+
+static
+struct timeval gnc_clock[NUM_CLOCKS] = {
+   {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, 
+   {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, 
+};
+
+static
+struct timeval gnc_clock_total[NUM_CLOCKS] = {
+   {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, 
+   {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, 
+};
+
+void
+gnc_start_clock (int clockno, QofLogModule log_module, gncLogLevel log_level,
+                 const char *function_name, const char *format, ...)
+{
+  struct timezone tz;
+  va_list ap;
+
+  if ((0>clockno) || (NUM_CLOCKS <= clockno)) return;
+  gettimeofday (&gnc_clock[clockno], &tz);
+
+  if (!fout) gnc_log_init();
+
+  fprintf (fout, "Clock %d Start: %s: ",
+           clockno, gnc_log_prettify (function_name));
+
+  va_start (ap, format);
+
+  vfprintf (fout, format, ap);
+
+  va_end (ap);
+
+  fprintf (fout, "\n");
+  fflush (fout);
+}
+
+void
+gnc_report_clock (int clockno, QofLogModule log_module, gncLogLevel log_level,
+                  const char *function_name, const char *format, ...)
+{
+  struct timezone tz;
+  struct timeval now;
+  va_list ap;
+
+  if ((0>clockno) || (NUM_CLOCKS <= clockno)) return;
+  gettimeofday (&now, &tz);
+
+  /* need to borrow to make difference */
+  if (now.tv_usec < gnc_clock[clockno].tv_usec)
+  {
+    now.tv_sec --;
+    now.tv_usec += 1000000;
+  }
+  now.tv_sec -= gnc_clock[clockno].tv_sec;
+  now.tv_usec -= gnc_clock[clockno].tv_usec;
+
+  gnc_clock_total[clockno].tv_sec += now.tv_sec;
+  gnc_clock_total[clockno].tv_usec += now.tv_usec;
+
+  if (!fout) gnc_log_init();
+
+  fprintf (fout, "Clock %d Elapsed: %ld.%06lds %s: ",
+           clockno, (long int) now.tv_sec, (long int) now.tv_usec, 
+	   gnc_log_prettify (function_name));
+
+  va_start (ap, format);
+
+  vfprintf (fout, format, ap);
+
+  va_end (ap);
+
+  fprintf (fout, "\n");
+  fflush (fout);
+}
+
+void
+gnc_report_clock_total (int clockno,
+                        QofLogModule log_module, gncLogLevel log_level,
+                        const char *function_name, const char *format, ...)
+{
+  va_list ap;
+
+  if ((0>clockno) || (NUM_CLOCKS <= clockno)) return;
+
+  /* need to normalize usec */
+  while (gnc_clock_total[clockno].tv_usec >= 1000000)
+  {
+    gnc_clock_total[clockno].tv_sec ++;
+    gnc_clock_total[clockno].tv_usec -= 1000000;
+  }
+
+  if (!fout) gnc_log_init();
+
+  fprintf (fout, "Clock %d Total Elapsed: %ld.%06lds  %s: ",
+           clockno,
+           (long int) gnc_clock_total[clockno].tv_sec,
+           (long int) gnc_clock_total[clockno].tv_usec,
+           gnc_log_prettify (function_name));
+
+  va_start (ap, format);
+
+  vfprintf (fout, format, ap);
+
+  va_end (ap);
+
+  fprintf (fout, "\n");
+  fflush (fout);
+}
+
+gboolean
+gnc_should_log(QofLogModule log_module, gncLogLevel log_level)
+{
+	gchar* log_string;
+	gncLogLevel maximum; /* Any log_level less than this will be logged. */
+
+	log_string = NULL;
+	if(!log_table || log_module == NULL || log_level == 0) { return FALSE; }
+	log_string = (gchar*)g_hash_table_lookup(log_table, log_module);
+	/* if log_module not found, do not log. */
+	if(!log_string) { return FALSE; }
+	maximum = gncLogLevelfromString(log_string);
+	if(log_level <= maximum) { return TRUE; }
+	return FALSE;
+}
+
+void qof_log_set_default(gncLogLevel log_level)
+{
+	gnc_set_log_level(QOF_MOD_BACKEND, log_level);
+	gnc_set_log_level(QOF_MOD_CLASS,   log_level);
+	gnc_set_log_level(QOF_MOD_ENGINE,  log_level);
+	gnc_set_log_level(QOF_MOD_OBJECT,  log_level);
+	gnc_set_log_level(QOF_MOD_KVP,     log_level);
+	gnc_set_log_level(QOF_MOD_MERGE,   log_level);
+	gnc_set_log_level(QOF_MOD_QUERY,   log_level);
+	gnc_set_log_level(QOF_MOD_SESSION, log_level);
+}
+
+struct hash_s
+{
+	QofLogCB cb;
+	gpointer data;
+};
+
+static void hash_cb (gpointer key, gpointer value, gpointer data)
+{
+	struct hash_s *iter;
+
+	iter = (struct hash_s*)data;
+	if(!iter) { return; }
+	(iter->cb)(key, value, iter->data);
+}
+
+void qof_log_module_foreach(QofLogCB cb, gpointer data)
+{
+	struct hash_s iter;
+
+	if(!cb) { return; }
+	iter.cb = cb;
+	iter.data = data;
+	g_hash_table_foreach(log_table, hash_cb, (gpointer)&iter);
+}
+
+gint qof_log_module_count(void)
+{
+	if(!log_table) { return 0; }
+	return g_hash_table_size(log_table);
+}
+
+/** @} */
+
+/************************* END OF FILE ******************************\
+\********************************************************************/

Added: gnucash/trunk/lib/libqof/qof/gnc-trace.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-trace.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-trace.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,270 @@
+/********************************************************************\
+ * gnc-trace.h -- QOF error logging and tracing facility            *
+ * Copyright (C) 1998-2003 Linas Vepstas <linas at linas.org>          *
+ * Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>          *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ *   Author: Linas Vepstas (linas at linas.org)                        *
+\********************************************************************/
+
+/** @addtogroup Trace
+    @{ */
+
+/** @file gnc-trace.h 
+ *  @brief QOF error logging and tracing facility
+ *  @author Neil Williams <linux at codehelp.co.uk>
+ */
+
+#ifndef GNC_TRACE_H
+#define GNC_TRACE_H
+
+#include <glib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include "qof.h"
+#include "gnc-engine-util.h"
+
+#define QOF_MOD_ENGINE "qof-engine"
+
+#define LOG_LEVEL_LIST(_) \
+  _(GNC_LOG_FATAL, = 0)   \
+  _(GNC_LOG_ERROR, = 1)   \
+  _(GNC_LOG_WARNING, = 2) \
+  _(GNC_LOG_INFO, = 3)    \
+  _(GNC_LOG_DEBUG, = 4)   \
+  _(GNC_LOG_DETAIL, = 5)  \
+  _(GNC_LOG_TRACE, = 6)
+
+DEFINE_ENUM (gncLogLevel, LOG_LEVEL_LIST)
+
+/** Convert gncLogLevel to a string.
+
+The macro correlates the enum value and an
+exact copy as a string, removing the need to
+keep two separate lists in sync.
+*/
+AS_STRING_DEC(gncLogLevel, LOG_LEVEL_LIST)
+
+/** Convert the log_string to a gncLogLevel
+
+Only for use as a partner to ::gncLogLevelasString
+*/
+FROM_STRING_DEC(gncLogLevel, LOG_LEVEL_LIST)
+
+#define GNC_TRACE_INDENT_WIDTH 4
+
+/** Initialize the error logging subsystem
+
+\note Applications should call gnc_set_logfile
+to set the output, otherwise the
+default of \a /tmp/qof.trace will be used.
+
+As an alternative, use qof_log_init_filename
+which sets the filename and initialises the
+logging subsystem in one operation.
+*/
+void gnc_log_init (void);
+
+/** Set the logging level of the given log_module. */
+void gnc_set_log_level(QofLogModule module, gncLogLevel level);
+
+/** Set the logging level for all known log_modules.
+
+\note Unless a log_module has been registered using
+gnc_set_log_level, it will be unaffected by this change.
+
+*/
+void gnc_set_log_level_global(gncLogLevel level);
+
+/** Specify an alternate log output, to pipe or file.  By default,
+ *  all logging goes to /tmp/qof.trace 
+ 
+ Needs to be called \b before gnc_log_init()
+*/
+void gnc_set_logfile (FILE *outfile);
+
+/** Specify a filename for log output.
+
+Calls gnc_log_init() for you.
+*/
+void qof_log_init_filename (const gchar* logfilename);
+
+/** Be nice, close the logfile is possible. */
+void qof_log_shutdown (void);
+
+/** gnc_log_prettify() cleans up subroutine names. AIX/xlC has the habit
+ * of printing signatures not names; clean this up. On other operating
+ * systems, truncate name to 30 chars. Note this routine is not thread
+ * safe. Note we wouldn't need this routine if AIX did something more
+ * reasonable. Hope thread safety doesn't poke us in eye. */
+const char * gnc_log_prettify (const char *name);
+
+/** Do not log log_modules that have not been enabled.
+
+ Whether to log cannot be decided inline because a hashtable is
+ now used. This is the price of extending logging to non-Gnucash
+ log_modules.
+
+*/
+gboolean gnc_should_log(QofLogModule log_module, gncLogLevel log_level);
+
+/** Set the default QOF log_modules to the log level. */
+void qof_log_set_default(gncLogLevel log_level);
+
+typedef void (*QofLogCB) (QofLogModule log_module, gncLogLevel* log_level, gpointer user_data);
+
+/** Iterate over each known log_module
+
+Only log_modules with log_levels set will 
+be available.
+*/
+void qof_log_module_foreach(QofLogCB cb, gpointer data);
+
+/** Number of log_modules registered*/
+gint qof_log_module_count(void);
+
+#define FUNK gnc_log_prettify(__FUNCTION__)
+
+/** Log error/waring/info messages to stderr or to other pipe. 
+ *  This logging infrastructure is meant for validating the 
+ *  correctness of the execution of the code.  'Info' level 
+ *  messages help trace program flow. 'Error' messages are 
+ *  meant to indicate internal data inconsistencies.
+ * 
+ * Messages can be logged to stdout, stderr, or to any desired
+ * FILE * file handle. Use fdopen() to get a file handle from a 
+ * file descriptor. Use gnc_set_logfile to set the logging file 
+ * handle.
+ */
+
+/** Log an fatal error */
+#define FATAL(format, args...) {                     \
+    g_log (G_LOG_DOMAIN, G_LOG_LEVEL_ERROR,          \
+      "Fatal Error: %s(): " format, FUNK , ## args); \
+}
+
+/** Log an serious error */
+#define PERR(format, args...) {                    \
+  if (gnc_should_log (log_module, GNC_LOG_ERROR)) {    \
+    g_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,     \
+      "Error: %s(): " format, FUNK , ## args);     \
+  }                                                \
+}
+
+/** Log an warning */
+#define PWARN(format, args...) {                   \
+  if (gnc_should_log (log_module, GNC_LOG_WARNING)) {  \
+    g_log (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,      \
+      "Warning: %s(): " format, FUNK , ## args);   \
+  }                                                \
+}
+
+/** Print an informational note */
+#define PINFO(format, args...) {                   \
+  if (gnc_should_log (log_module, GNC_LOG_INFO)) {     \
+    g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO,         \
+      "Info: %s(): " format,                       \
+      FUNK , ## args);                             \
+  }                                                \
+}
+
+/** Print an debugging message */
+#define DEBUG(format, args...) {                   \
+  if (gnc_should_log (log_module, GNC_LOG_DEBUG)) {    \
+    g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,        \
+      "Debug: %s(): " format,                      \
+      FUNK , ## args);                             \
+  }                                                \
+}
+
+/** Print an function entry debugging message */
+#define ENTER(format, args...) {                   \
+  extern gint gnc_trace_num_spaces;                \
+  if (gnc_should_log (log_module, GNC_LOG_DEBUG)) {    \
+    g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,        \
+      "Enter in %s: %s()" format, __FILE__,        \
+      FUNK , ## args);                             \
+    gnc_trace_num_spaces += GNC_TRACE_INDENT_WIDTH;\
+  }                                                \
+}
+
+/** Print an function exit debugging message */
+#define LEAVE(format, args...) {                   \
+  extern gint gnc_trace_num_spaces;                \
+  if (gnc_should_log (log_module, GNC_LOG_DEBUG)) {    \
+    gnc_trace_num_spaces -= GNC_TRACE_INDENT_WIDTH;\
+    g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,        \
+      "Leave: %s()" format,                        \
+      FUNK , ## args);                             \
+  }                                                \
+}
+
+/** Print an function trace debugging message */
+#define TRACE(format, args...) {                   \
+  if (gnc_should_log (log_module, GNC_LOG_TRACE)) {    \
+    g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,        \
+      "Trace: %s(): " format, FUNK , ## args);     \
+  }                                                \
+}
+
+#define DEBUGCMD(x) { if (gnc_should_log (log_module, GNC_LOG_DEBUG)) { (x); }}
+
+/* -------------------------------------------------------- */
+/** Infrastructure to make timing measurements for critical peices 
+ * of code. Used for only for performance tuning & debugging. 
+ */
+
+void gnc_start_clock (int clockno, QofLogModule log_module, gncLogLevel log_level,
+                      const char *function_name, const char *format, ...);
+
+void gnc_report_clock (int clockno,
+                       QofLogModule log_module,
+                       gncLogLevel log_level,
+                       const char *function_name,
+                       const char *format, ...);
+
+void gnc_report_clock_total (int clockno,
+                             QofLogModule log_module,
+                             gncLogLevel log_level,
+                             const char *function_name,
+                             const char *format, ...);
+
+/** start a particular timer */
+#define START_CLOCK(clockno,format, args...) {              \
+  if (gnc_should_log (log_module, GNC_LOG_INFO))                \
+    gnc_start_clock (clockno, log_module, GNC_LOG_INFO,         \
+             __FUNCTION__, format , ## args);               \
+}
+
+/** report elapsed time since last report on a particular timer */
+#define REPORT_CLOCK(clockno,format, args...) {             \
+  if (gnc_should_log (log_module, GNC_LOG_INFO))                \
+    gnc_report_clock (clockno, log_module, GNC_LOG_INFO,        \
+             __FUNCTION__, format , ## args);               \
+}
+
+/** report total elapsed time since timer started */
+#define REPORT_CLOCK_TOTAL(clockno,format, args...) {       \
+  if (gnc_should_log (log_module, GNC_LOG_INFO))                \
+    gnc_report_clock_total (clockno, log_module, GNC_LOG_INFO,  \
+             __FUNCTION__, format , ## args);               \
+}
+
+#endif /* GNC_TRACE_H */
+/* @} */

Added: gnucash/trunk/lib/libqof/qof/guid.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/guid.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/guid.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,680 @@
+/********************************************************************\
+ * guid.c -- globally unique ID implementation                      *
+ * Copyright (C) 2000 Dave Peticolas <peticola at cs.ucdavis.edu>      *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#define _GNU_SOURCE
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/times.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "guid.h"
+#include "md5.h"
+#include "qofid.h"
+#include "gnc-trace.h"
+
+# ifndef P_tmpdir
+#  define P_tmpdir "/tmp"
+# endif
+
+/** Constants *******************************************************/
+#define DEBUG_GUID 0
+#define BLOCKSIZE 4096
+#define THRESHOLD (2 * BLOCKSIZE)
+
+
+/** Static global variables *****************************************/
+static gboolean guid_initialized = FALSE;
+static struct md5_ctx guid_context;
+static GMemChunk *guid_memchunk = NULL;
+
+/* This static indicates the debugging module that this .o belongs to.  */
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+/** Memory management routines ***************************************/
+static void
+guid_memchunk_init (void)
+{
+  if (!guid_memchunk)
+    guid_memchunk = g_mem_chunk_create (GUID, 512, G_ALLOC_AND_FREE);
+}
+
+static void
+guid_memchunk_shutdown (void)
+{
+  if (guid_memchunk)
+  {
+    g_mem_chunk_destroy (guid_memchunk);
+    guid_memchunk = NULL;
+  }
+}
+
+GUID *
+guid_malloc (void)
+{
+  if (!guid_memchunk) guid_memchunk_init();
+  return g_chunk_new (GUID, guid_memchunk);
+}
+
+void
+guid_free (GUID *guid)
+{
+  if (!guid)
+    return;
+
+  g_chunk_free (guid, guid_memchunk);
+}
+
+
+const GUID *
+guid_null(void)
+{
+  static int null_inited = 0;
+  static GUID null_guid;
+
+  if (!null_inited)
+  {
+    int i;
+    char *tmp = "NULLGUID.EMPTY.";
+
+    for (i = 0; i < 16; i++)
+      null_guid.data[i] = tmp[i];
+
+    null_inited = 1;
+  }
+
+  return &null_guid;
+}
+
+/** Function implementations ****************************************/
+
+/* This code is based on code in md5.c in GNU textutils. */
+static size_t
+init_from_stream(FILE *stream, size_t max_size)
+{
+  char buffer[BLOCKSIZE + 72];
+  size_t sum, block_size, total;
+
+  if (max_size <= 0)
+    return 0;
+
+  total = 0;
+
+  /* Iterate over file contents. */
+  while (1)
+  {
+    /* We read the file in blocks of BLOCKSIZE bytes.  One call of the
+     * computation function processes the whole buffer so that with the
+     * next round of the loop another block can be read.  */
+    size_t n;
+    sum = 0;
+
+    if (max_size < BLOCKSIZE)
+      block_size = max_size;
+    else
+      block_size = BLOCKSIZE;
+
+    /* Read block.  Take care for partial reads.  */
+    do
+    {
+      n = fread (buffer + sum, 1, block_size - sum, stream);
+
+      sum += n;
+    }
+    while (sum < block_size && n != 0);
+
+    max_size -= sum;
+
+    if (n == 0 && ferror (stream))
+      return total;
+
+    /* If end of file or max_size is reached, end the loop. */
+    if ((n == 0) || (max_size == 0))
+      break;
+
+    /* Process buffer with BLOCKSIZE bytes.  Note that
+     * BLOCKSIZE % 64 == 0  */
+    md5_process_block (buffer, BLOCKSIZE, &guid_context);
+
+    total += sum;
+  }
+
+  /* Add the last bytes if necessary.  */
+  if (sum > 0)
+  {
+    md5_process_bytes (buffer, sum, &guid_context);
+    total += sum;
+  }
+
+  return total;
+}
+
+static size_t
+init_from_file(const char *filename, size_t max_size)
+{
+  struct stat stats;
+  size_t total = 0;
+  size_t file_bytes;
+  FILE *fp;
+
+  memset(&stats, 0, sizeof(stats));
+  if (stat(filename, &stats) != 0)
+    return 0;
+
+  md5_process_bytes(&stats, sizeof(stats), &guid_context);
+  total += sizeof(stats);
+
+  if (max_size <= 0)
+    return total;
+
+  fp = fopen (filename, "r");
+  if (fp == NULL)
+    return total;
+
+  file_bytes = init_from_stream(fp, max_size);
+
+  PINFO ("guid_init got %llu bytes from %s", (unsigned long long int) file_bytes,
+	 filename);
+
+  total += file_bytes;
+
+  fclose(fp);
+
+  return total;
+}
+
+static size_t
+init_from_dir(const char *dirname, unsigned int max_files)
+{
+  char filename[1024];
+  struct dirent *de;
+  struct stat stats;
+  size_t total;
+  int result;
+  DIR *dir;
+
+  if (max_files <= 0)
+    return 0;
+
+  dir = opendir (dirname);
+  if (dir == NULL)
+    return 0;
+
+  total = 0;
+
+  do
+  {
+    de = readdir(dir);
+    if (de == NULL)
+      break;
+
+    md5_process_bytes(de, sizeof(struct dirent), &guid_context);
+    total += sizeof(struct dirent);
+
+    result = snprintf(filename, sizeof(filename),
+                      "%s/%s", dirname, de->d_name);
+    if ((result < 0) || (result >= (int)sizeof(filename)))
+      continue;
+
+    memset(&stats, 0, sizeof(stats));
+    if (stat(filename, &stats) != 0)
+      continue;
+    md5_process_bytes(&stats, sizeof(stats), &guid_context);
+    total += sizeof(stats);
+
+    max_files--;
+  } while (max_files > 0);
+
+  closedir(dir);
+
+  return total;
+}
+
+static size_t
+init_from_time(void)
+{
+  size_t total;
+  time_t t_time;
+  clock_t clocks;
+  struct tms tms_buf;
+
+  total = 0;
+
+  t_time = time(NULL);
+  md5_process_bytes(&t_time, sizeof(t_time), &guid_context);
+  total += sizeof(t_time);
+
+  clocks = times(&tms_buf);
+  md5_process_bytes(&clocks, sizeof(clocks), &guid_context);
+  md5_process_bytes(&tms_buf, sizeof(tms_buf), &guid_context);
+  total += sizeof(clocks) + sizeof(tms_buf);
+
+  return total;
+}
+
+static size_t
+init_from_int(int val)
+{
+  md5_process_bytes(&val, sizeof(val), &guid_context);
+  return sizeof(int);
+}
+
+static size_t
+init_from_buff(unsigned char * buf, size_t buflen)
+{
+  md5_process_bytes(buf, buflen, &guid_context);
+  return buflen;
+}
+
+void
+guid_init(void)
+{
+  size_t bytes = 0;
+
+  /* Not needed; taken care of on first malloc.
+   * guid_memchunk_init(); */
+
+  md5_init_ctx(&guid_context);
+
+  /* entropy pool */
+  bytes += init_from_file ("/dev/urandom", 512);
+
+  /* files */
+  {
+    const char * files[] =
+    { "/etc/passwd",
+      "/proc/loadavg",
+      "/proc/meminfo",
+      "/proc/net/dev",
+      "/proc/rtc",
+      "/proc/self/environ",
+      "/proc/self/stat",
+      "/proc/stat",
+      "/proc/uptime",
+      NULL
+    };
+    int i;
+
+    for (i = 0; files[i] != NULL; i++)
+      bytes += init_from_file(files[i], BLOCKSIZE);
+  }
+
+  /* directories */
+  {
+    const char * dirname;
+    const char * dirs[] =
+    {
+      "/proc",
+      P_tmpdir,
+      "/var/lock",
+      "/var/log",
+      "/var/mail",
+      "/var/spool/mail",
+      "/var/run",
+      NULL
+    };
+    int i;
+
+    for (i = 0; dirs[i] != NULL; i++)
+      bytes += init_from_dir(dirs[i], 32);
+
+    dirname = getenv("HOME");
+    if (dirname != NULL)
+      bytes += init_from_dir(dirname, 32);
+  }
+
+  /* process and parent ids */
+  {
+    pid_t pid;
+
+    pid = getpid();
+    md5_process_bytes(&pid, sizeof(pid), &guid_context);
+    bytes += sizeof(pid);
+
+    pid = getppid();
+    md5_process_bytes(&pid, sizeof(pid), &guid_context);
+    bytes += sizeof(pid);
+  }
+
+  /* user info */
+  {
+    uid_t uid;
+    gid_t gid;
+    char *s;
+
+    s = getlogin();
+    if (s != NULL)
+    {
+      md5_process_bytes(s, strlen(s), &guid_context);
+      bytes += strlen(s);
+    }
+
+    uid = getuid();
+    md5_process_bytes(&uid, sizeof(uid), &guid_context);
+    bytes += sizeof(uid);
+
+    gid = getgid();
+    md5_process_bytes(&gid, sizeof(gid), &guid_context);
+    bytes += sizeof(gid);
+  }
+
+  /* host info */
+  {
+    char string[1024];
+
+    memset(string, 0, sizeof(string));
+    gethostname(string, sizeof(string));
+    md5_process_bytes(string, sizeof(string), &guid_context);
+    bytes += sizeof(string);
+  }
+
+  /* plain old random */
+  {
+    int n, i;
+
+    srand((unsigned int) time(NULL));
+
+    for (i = 0; i < 32; i++)
+    {
+      n = rand();
+
+      md5_process_bytes(&n, sizeof(n), &guid_context);
+      bytes += sizeof(n);
+    }
+  }
+
+  /* time in secs and clock ticks */
+  bytes += init_from_time();
+
+  PINFO ("got %llu bytes", (unsigned long long int) bytes);
+
+  if (bytes < THRESHOLD)
+    PWARN("only got %llu bytes.\n"
+              "The identifiers might not be very random.\n",
+	  (unsigned long long int)bytes);
+
+  guid_initialized = TRUE;
+}
+
+void
+guid_init_with_salt(const void *salt, size_t salt_len)
+{
+  guid_init();
+
+  md5_process_bytes(salt, salt_len, &guid_context);
+}
+
+void
+guid_init_only_salt(const void *salt, size_t salt_len)
+{
+  md5_init_ctx(&guid_context);
+
+  md5_process_bytes(salt, salt_len, &guid_context);
+
+  guid_initialized = TRUE;
+}
+
+void 
+guid_shutdown (void)
+{
+	guid_memchunk_shutdown();
+}
+
+#define GUID_PERIOD 5000
+
+void
+guid_new(GUID *guid)
+{
+  static int counter = 0;
+  struct md5_ctx ctx;
+
+  if (guid == NULL)
+    return;
+
+  if (!guid_initialized)
+    guid_init();
+
+  /* make the id */
+  ctx = guid_context;
+  md5_finish_ctx(&ctx, guid->data);
+
+  /* update the global context */
+  init_from_time();
+
+  /* Make it a little extra salty.  I think init_from_time was buggy,
+   * or something, since duplicate id's actually happened. Or something
+   * like that.  I think this is because init_from_time kept returning
+   * the same values too many times in a row.  So we'll do some 'block
+   * chaining', and feed in the old guid as new random data.
+   *
+   * Anyway, I think the whole fact that I saw a bunch of duplicate 
+   * id's at one point, but can't reproduce the bug is rather alarming.
+   * Something must be broken somewhere, and merely adding more salt
+   * is just hiding the problem, not fixing it.
+   */
+  init_from_int (433781*counter);
+  init_from_buff (guid->data, 16);
+
+  if (counter == 0)
+  {
+    FILE *fp;
+
+    fp = fopen ("/dev/urandom", "r");
+    if (fp == NULL)
+      return;
+
+    init_from_stream(fp, 32);
+
+    fclose(fp);
+
+    counter = GUID_PERIOD;
+  }
+
+  counter--;
+}
+
+GUID
+guid_new_return(void)
+{
+  GUID guid;
+
+  guid_new (&guid);
+
+  return guid;
+}
+
+/* needs 32 bytes exactly, doesn't print a null char */
+static void
+encode_md5_data(const unsigned char *data, char *buffer)
+{
+  size_t count;
+
+  for (count = 0; count < 16; count++, buffer += 2)
+    sprintf(buffer, "%02x", data[count]);
+}
+
+/* returns true if the first 32 bytes of buffer encode
+ * a hex number. returns false otherwise. Decoded number
+ * is packed into data in little endian order. */
+static gboolean
+decode_md5_string(const char *string, unsigned char *data)
+{
+  unsigned char n1, n2;
+  size_t count = -1;
+  char c1, c2;
+
+  if (NULL == data) return FALSE;
+  if (NULL == string) goto badstring;
+
+  for (count = 0; count < 16; count++)
+  {
+    /* check for a short string e.g. null string ... */
+    if ((0==string[2*count]) || (0==string[2*count+1])) goto badstring;
+
+    c1 = tolower(string[2 * count]);
+    if (!isxdigit(c1)) goto badstring;
+
+    c2 = tolower(string[2 * count + 1]);
+    if (!isxdigit(c2)) goto badstring;
+
+    if (isdigit(c1))
+      n1 = c1 - '0';
+    else
+      n1 = c1 - 'a' + 10;
+
+    if (isdigit(c2))
+      n2 = c2 - '0';
+    else
+      n2 = c2 - 'a' + 10;
+
+    data[count] = (n1 << 4) | n2;
+  }
+  return TRUE;
+
+badstring:
+  for (count = 0; count < 16; count++)
+  {
+    data[count] = 0;
+  }
+  return FALSE;
+}
+
+/* Allocate the key */
+
+const char *
+guid_to_string(const GUID * guid)
+{
+#ifdef G_THREADS_ENABLED
+  static GStaticPrivate guid_buffer_key = G_STATIC_PRIVATE_INIT;
+  gchar *string;
+
+  string = g_static_private_get (&guid_buffer_key);
+  if (string == NULL) {
+    string = malloc(GUID_ENCODING_LENGTH+1);
+    g_static_private_set (&guid_buffer_key, string, g_free);
+  }
+#else
+  static char string[64];
+#endif
+
+  encode_md5_data(guid->data, string);
+  string[GUID_ENCODING_LENGTH] = '\0';
+
+  return string;
+}
+
+char *
+guid_to_string_buff(const GUID * guid, char *string)
+{
+  if (!string || !guid) return NULL;
+
+  encode_md5_data(guid->data, string);
+
+  string[GUID_ENCODING_LENGTH] = '\0';
+  return &string[GUID_ENCODING_LENGTH];
+}
+
+gboolean
+string_to_guid(const char * string, GUID * guid)
+{
+  return decode_md5_string(string, (guid != NULL) ? guid->data : NULL);
+}
+
+gboolean
+guid_equal(const GUID *guid_1, const GUID *guid_2)
+{
+  if (guid_1 && guid_2)
+    return (memcmp(guid_1, guid_2, sizeof(GUID)) == 0);
+  else
+    return FALSE;
+}
+
+gint
+guid_compare(const GUID *guid_1, const GUID *guid_2)
+{
+  if (guid_1 == guid_2)
+    return 0;
+
+  /* nothing is always less than something */
+  if (!guid_1 && guid_2)
+    return -1;
+
+  if (guid_1 && !guid_2)
+    return 1;
+
+  return memcmp (guid_1, guid_2, sizeof (GUID));
+}
+
+guint
+guid_hash_to_guint (gconstpointer ptr)
+{
+  const GUID *guid = ptr;
+
+  if (!guid)
+  {
+    PERR ("received NULL guid pointer.");
+    return 0;
+  }
+
+  if (sizeof(guint) <= sizeof(guid->data))
+  {
+    return (*((guint *) guid->data));
+  }
+  else
+  {
+    guint hash = 0;
+    unsigned int i, j;
+
+    for (i = 0, j = 0; i < sizeof(guint); i++, j++) {
+      if (j == 16) j = 0;
+
+      hash <<= 4;
+      hash |= guid->data[j];
+    }
+
+    return hash;
+  }
+}
+
+static gint
+guid_g_hash_table_equal (gconstpointer guid_a, gconstpointer guid_b)
+{
+  return guid_equal (guid_a, guid_b);
+}
+
+GHashTable *
+guid_hash_table_new (void)
+{
+  return g_hash_table_new (guid_hash_to_guint, guid_g_hash_table_equal);
+}

Added: gnucash/trunk/lib/libqof/qof/guid.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/guid.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/guid.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,204 @@
+/********************************************************************\
+ * guid.h -- globally unique ID User API                            *
+ * Copyright (C) 2000 Dave Peticolas <peticola at cs.ucdavis.edu>      *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#ifndef GUID_H
+#define GUID_H 
+
+#include <glib.h>
+#include <stddef.h>
+
+/** @addtogroup Entity
+    @{ */
+/** @addtogroup GUID
+    Globally Unique ID's provide a way to uniquely identify 
+    some thing.  A GUID is a unique, cryptographically
+    random 128-bit value.  The identifier is so random that
+    it is safe to assume that there is no other such item
+    on the planet Earth, and indeed, not even in the Galaxy
+    or beyond.
+
+    QOF GUID's can be used independently of any other subsystem
+    in QOF.   In particular, they do not require the use of
+    other parts of the object subsystem.
+
+    @{ */
+/** @file guid.h
+    @brief  globally unique ID User API 
+    @author Copyright (C) 2000 Dave Peticolas <peticola at cs.ucdavis.edu>
+*/
+
+/** The type used to store guids */
+typedef union _GUID
+{
+  unsigned char data[16];
+
+  int __align_me; /* this just ensures that GUIDs are 32-bit
+                   * aligned on systems that need them to be. */
+} GUID;
+
+
+/** number of characters needed to encode a guid as a string
+ * not including the null terminator. */
+#define GUID_ENCODING_LENGTH 32
+
+
+/** Initialize the id generator with a variety of random
+ *  sources.
+ *
+ *  @note Only one of guid_init(), guid_init_with_salt() and
+ *  guid_init_only_salt() should be called.  Calling any
+ *  initialization function a second time will reset the generator and
+ *  erase the effect of the first call.
+ */
+void guid_init(void);
+
+/** Initialize the id generator with a variety of random sources. and
+ *  with the data given in the salt argument. This argument can be
+ *  used to add additional randomness to the generated ids.
+ *
+ *  @param salt The additional random values to add to the generator.
+ *
+ *  @param salt_len The length of the additional random values.
+ *
+ *  @note Only one of guid_init(), guid_init_with_salt() and
+ *  guid_init_only_salt() should be called.  Calling any
+ *  initialization function a second time will reset the generator and
+ *  erase the effect of the first call.
+ */
+void guid_init_with_salt(const void *salt, size_t salt_len);
+
+/** Initialize the id generator with the data given in the salt
+ *  argument, but not with any other source. Calling this function with
+ *  a specific argument will reliably produce a specific sequence of
+ *  ids.
+ *
+ *  @param salt The additional random values to add to the generator.
+ *
+ *  @param salt_len The length of the additional random values.
+ *
+ *  @note Only one of guid_init(), guid_init_with_salt() and
+ *  guid_init_only_salt() should be called.  Calling any
+ *  initialization function a second time will reset the generator and
+ *  erase the effect of the first call.
+ */
+void guid_init_only_salt(const void *salt, size_t salt_len);
+
+/** Release the memory chunk associated with gui storage. Use this
+ *  only when shutting down the program, as it invalidates *all*
+ *  GUIDs at once. */
+void guid_shutdown (void);
+
+/** Generate a new id. If no initialization function has been called,
+ *  guid_init() will be called before the id is created.
+ *
+ *  @param guid A pointer to an existing guid data structure.  The
+ *  existing value will be replaced with a new value.
+ *
+ * This routine uses the md5 algorithm to build strong random guids.
+ * Note that while guid's are generated randomly, the odds of this 
+ * routine returning a non-unique id are astronomically small.
+ * (Literally astronomically: If you had Cray's on every solar
+ * system in the universe running for the entire age of the universe,
+ * you'd still have less than a one-in-a-million chance of coming up 
+ * with a duplicate id.  2^128 == 10^38 is a really really big number.)
+ */
+void guid_new(GUID *guid);
+
+/** Generate a new id. If no initialization function has been called,
+ *  guid_init() will be called before the id is created.
+ *
+ *  @return guid A pointer to a data structure containing a new GUID.
+ *  The memory pointed to is owned by this routine and the guid must
+ *  be copied out. 
+ * 
+ *  CAS: huh? make that: @return guid A data structure containing a newly
+ * allocated GUID.  Caller is responsible for calling guid_free().
+ */
+GUID guid_new_return(void);
+
+/** Returns a GUID which is guaranteed to never reference any entity. */
+/* CAS: AFAICT: this isn't really guaranteed, but it's only as likely
+   as any other md5 collision. This could be guaranteed if GUID
+   contained a validity flag. */
+const GUID * guid_null (void);
+
+/** Efficiently allocate & free memory for GUIDs */
+GUID * guid_malloc (void);
+
+/* Return a guid set to all zero's */
+void   guid_free (GUID *guid);
+
+/** The guid_to_string() routine returns a null-terminated string 
+ *  encoding of the id. String encodings of identifiers are hex 
+ *  numbers printed only with the characters '0' through '9' and 
+ *  'a' through 'f'. The encoding will always be GUID_ENCODING_LENGTH 
+ *  characters long. 
+ *
+ *  XXX This routine is not thread safe and is deprecated. Please
+ *  use the routine guid_to_string_buff() instead.
+ *
+ *  @param guid The guid to print.
+ *
+ *  @return A pointer to the starting character of the string.  The
+ *  returned memory is owned by this routine and may not be freed by
+ *  the caller.
+ */
+const char * guid_to_string (const GUID * guid);
+
+/** The guid_to_string_buff() routine puts a null-terminated string
+ *  encoding of the id into the memory pointed at by buff.  The
+ *  buffer must be at least GUID_ENCODING_LENGTH+1 characters long.
+ *  This routine is handy for avoiding a malloc/free cycle.  It
+ *  returns a pointer to the >>end<< of what was written.  (i.e. it
+ *  can be used like 'stpcpy' during string concatenation)
+ *
+ *  @param guid The guid to print.
+ *
+ *  @param buff The buffer to print it into.
+ *
+ *  @return A pointer to the terminating null character of the string.
+ */
+char * guid_to_string_buff (const GUID * guid, char *buff);
+
+
+/** Given a string, decode the id into the guid if guid is non-NULL.
+ * The function returns TRUE if the string was a valid 32 character
+ * hexadecimal number. This function accepts both upper and lower case
+ * hex digits. If the return value is FALSE, the effect on guid is
+ * undefined. */
+gboolean string_to_guid(const char * string, GUID * guid);
+
+
+/** Given two GUIDs, return TRUE if they are non-NULL and equal.
+ * Return FALSE, otherwise. */
+gboolean guid_equal(const GUID *guid_1, const GUID *guid_2);
+gint     guid_compare(const GUID *g1, const GUID *g2);
+
+/** Given a GUID *, hash it to a guint */
+guint guid_hash_to_guint(gconstpointer ptr);
+
+GHashTable *guid_hash_table_new(void);
+
+/* @} */
+/* @} */
+#endif

Added: gnucash/trunk/lib/libqof/qof/kvp-util-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/kvp-util-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/kvp-util-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,108 @@
+/********************************************************************\
+ * kvp-util-p.h -- misc odd-job kvp utils (private file)            *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#ifndef XACC_KVP_UTIL_P_H
+#define XACC_KVP_UTIL_P_H
+
+#include "config.h"
+
+#include "guid.h"
+#include "kvp_frame.h"
+
+/** @addtogroup KVP
+    @{ 
+*/
+/** @file kvp-util-p.h
+ * @brief misc odd-job kvp utils engine-private routines
+ * @author Copyright (C) 2001, 2003 Linas Vepstas <linas at linas.org>               *
+*/
+/** @name KvpBag Bags of GUID Pointers 
+ @{ 
+*/
+
+/** The gnc_kvp_bag_add() routine is used to maintain a collection 
+ *  of pointers in a kvp tree.
+ *
+ *  The thing being pointed at is uniquely identified by its GUID. 
+ *  This routine is typically used to create a linked list, and/or
+ *  a collection of pointers to objects that are 'related' to each 
+ *  other in some way.
+ *
+ *  The var-args should be pairs of strings (const char *) followed by
+ *  the corresponding GUID pointer (const GUID *).  Terminate the varargs
+ *  with a NULL as the last string argument.
+ *
+ *  The actual 'pointer' is stored in a subdirectory in a bag located at
+ *  the node directory 'path'.  A 'bag' is merely a collection of
+ *  (unamed) values.  The name of our bag is 'path'. A bag can contain
+ *  any kind of values, including frames.  This routine will create a
+ *  frame, and put it in the bag.  The frame will contain named data
+ *  from the subroutine arguments.  Thus, for example:
+ *
+ *  gnc_kvp_array (kvp, "foo", secs, "acct_guid", aguid, 
+ *                                   "book_guid", bguid, NULL);
+ *
+ *  will create a frame containing "/acct_guid" and "/book_guid", whose
+ *  values are aguid and bguid respecitvely.  The frame will also
+ *  contain "/date", whose value will be secs.  This frame will be
+ *  placed into the bag located at "foo". 
+ *
+ *  This routine returns a pointer to the frame that was created, or 
+ *  NULL if an error occured.
+ */
+
+KvpFrame * gnc_kvp_bag_add (KvpFrame *kvp_root, const char *path, time_t secs, 
+                     const char *first_name, ...);
+
+
+/** The gnc_kvp_bag_merge() routine will move the bag contents from
+ *    the 'kvp_from', to the 'into' bag.  It will then delete the 
+ *    'from' bag from the kvp tree.
+ */
+void gnc_kvp_bag_merge (KvpFrame *kvp_into, const char *intopath, 
+                        KvpFrame *kvp_from, const char *frompath);
+
+/** The gnc_kvp_bag_find_by_guid() routine examines the bag pointed
+ *    located at root.  It looks for a frame in that bag that has the
+ *    guid value of "desired_guid" filed under the key name "guid_name".
+ *    If it finds that matching guid, then it returns a pointer to 
+ *    the KVP frame that contains it.  If it is not found, or if there
+ *    is any other error, NULL is returned.
+ */
+
+KvpFrame * gnc_kvp_bag_find_by_guid (KvpFrame *root,  const char * path,
+                         const char *guid_name, GUID *desired_guid);
+
+
+/** Remove the given frame from the bag.  The frame is removed,
+ *  however, it is not deleted.  Note that the frame pointer must
+ *  be a pointer to the actual frame (for example, as returned by
+ *  gnc_kvp_bag_find_by_guid() for by gnc_kvp_bag_add()), and not
+ *  some copy of the frame.
+ */
+
+void gnc_kvp_bag_remove_frame (KvpFrame *root, const char *path,
+                               KvpFrame *fr);
+
+/** @} */
+/** @} */
+#endif /* XACC_KVP_UTIL_P_H */

Added: gnucash/trunk/lib/libqof/qof/kvp-util.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/kvp-util.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/kvp-util.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,235 @@
+/********************************************************************\
+ * kvp_util.c -- misc odd-job kvp utils                             *
+ * Copyright (C) 2001 Linas Vepstas <linas at linas.org>               *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#include "config.h"
+
+#include <glib.h>
+#include <stdio.h>
+
+#include "kvp_frame.h"
+#include "kvp-util.h"
+#include "kvp-util-p.h"
+
+/* ================================================================ */
+ 
+static KvpFrame *
+gnc_kvp_array_va (KvpFrame *kvp_root, const char * path, 
+                  time_t secs, const char * first_name, va_list ap)
+{
+   KvpFrame *cwd;
+   Timespec ts;
+   const char *name;
+
+   if (!kvp_root) return NULL;  
+   if (!first_name) return NULL;
+   
+   /* Create subdirectory and put the actual data */
+   cwd = kvp_frame_new();
+
+   /* Record the time */
+   ts.tv_sec = secs;
+   ts.tv_nsec = 0;
+   kvp_frame_set_timespec (cwd, "date", ts);
+
+   /* Loop over the args */
+   name = first_name;
+   while (name)
+   {
+      const GUID *guid;
+      guid = va_arg (ap, const GUID *);
+
+      kvp_frame_set_guid (cwd, name, guid);
+
+      name = va_arg (ap, const char *);
+   }
+
+   /* Attach cwd into the array */
+   kvp_frame_add_frame_nc (kvp_root, path, cwd);
+   return cwd;
+}
+
+/* ================================================================ */
+
+KvpFrame *
+gnc_kvp_bag_add (KvpFrame *pwd, const char * path, 
+                 time_t secs, const char *first_name, ...)
+{
+   KvpFrame *cwd;
+   va_list ap;
+   va_start (ap, first_name);
+   cwd = gnc_kvp_array_va (pwd, path, secs, first_name, ap);
+   va_end (ap);
+   return cwd;
+}
+
+/* ================================================================ */
+
+#define MATCH_GUID(elt) {                                       \
+  KvpFrame *fr = kvp_value_get_frame (elt);                     \
+  if (fr) {                                                     \
+     GUID *guid = kvp_frame_get_guid (fr, guid_name);           \
+     if (guid && guid_equal (desired_guid, guid)) return fr;    \
+  }                                                             \
+} 
+
+KvpFrame *
+gnc_kvp_bag_find_by_guid (KvpFrame *root, const char * path,
+                         const char *guid_name, GUID *desired_guid)
+{
+  KvpValue *arr;
+  KvpValueType valtype;
+  GList *node;
+
+  arr = kvp_frame_get_value (root, path);
+  valtype = kvp_value_get_type (arr);
+  if (KVP_TYPE_FRAME == valtype)
+  {
+    MATCH_GUID (arr);
+    return NULL;
+  }
+
+  /* Its gotta be a single isolated frame, or a list of them. */
+  if (KVP_TYPE_GLIST != valtype) return NULL;
+
+  for (node = kvp_value_get_glist(arr); node; node=node->next)
+  {
+    KvpValue *va = node->data;
+    MATCH_GUID (va);
+  }
+  return NULL;
+}
+
+/* ================================================================ */
+
+void
+gnc_kvp_bag_remove_frame (KvpFrame *root, const char *path, KvpFrame *fr)
+{
+  KvpValue *arr;
+  KvpValueType valtype;
+  GList *node, *listhead;
+
+  arr = kvp_frame_get_value (root, path);
+  valtype = kvp_value_get_type (arr);
+  if (KVP_TYPE_FRAME == valtype)
+  {
+    if (fr == kvp_value_get_frame (arr))
+    {
+      KvpValue *old_val = kvp_frame_replace_value_nc (root, path, NULL); 
+      kvp_value_replace_frame_nc (old_val, NULL);
+      kvp_value_delete (old_val);
+    }
+    return;
+  }
+
+  /* Its gotta be a single isolated frame, or a list of them. */
+  if (KVP_TYPE_GLIST != valtype) return;
+
+  listhead = kvp_value_get_glist(arr);
+  for (node = listhead; node; node=node->next)
+  {
+    KvpValue *va = node->data;
+    if (fr == kvp_value_get_frame (va))
+    {
+      listhead = g_list_remove_link (listhead, node);
+      g_list_free_1 (node);
+      kvp_value_replace_glist_nc (arr, listhead);
+      kvp_value_replace_frame_nc (va, NULL);
+      kvp_value_delete (va);
+      return;
+    }
+  }
+}
+
+/* ================================================================ */
+
+static KvpFrame *
+gnc_kvp_bag_get_first (KvpFrame *root, const char * path)
+{
+  KvpValue *arr, *va;
+  KvpValueType valtype;
+  GList *node;
+
+  arr = kvp_frame_get_value (root, path);
+  valtype = kvp_value_get_type (arr);
+  if (KVP_TYPE_FRAME == valtype)
+  {
+    return kvp_value_get_frame(arr);
+  }
+
+  /* Its gotta be a single isolated frame, or a list of them. */
+  if (KVP_TYPE_GLIST != valtype) return NULL;
+
+  node = kvp_value_get_glist(arr);
+  if (NULL == node) return NULL;
+
+  va = node->data;
+  return kvp_value_get_frame(va);
+}
+
+void
+gnc_kvp_bag_merge (KvpFrame *kvp_into, const char *intopath,
+                   KvpFrame *kvp_from, const char *frompath)
+{
+  KvpFrame *fr;
+
+  fr = gnc_kvp_bag_get_first (kvp_from, frompath);
+  while (fr) 
+  {
+    gnc_kvp_bag_remove_frame (kvp_from, frompath, fr);
+    kvp_frame_add_frame_nc (kvp_into, intopath, fr);
+    fr = gnc_kvp_bag_get_first (kvp_from, frompath);
+  }
+}
+
+/* ================================================================ */
+/*
+ * See header for docs.
+ */
+
+static void
+kv_pair_helper(gpointer key, gpointer val, gpointer user_data)
+{
+  GSList **result = (GSList **) user_data;
+  GHashTableKVPair *kvp = g_new(GHashTableKVPair, 1);
+
+  kvp->key = key;
+  kvp->value = val;
+  *result = g_slist_prepend(*result, kvp);
+}
+
+GSList *
+g_hash_table_key_value_pairs(GHashTable *table)
+{
+  GSList *result_list = NULL;
+  g_hash_table_foreach(table, kv_pair_helper, &result_list);
+  return result_list;
+}
+
+void
+g_hash_table_kv_pair_free_gfunc(gpointer data, gpointer user_data)
+{
+  GHashTableKVPair *kvp = (GHashTableKVPair *) data;
+  g_free(kvp);
+}
+
+/*======================== END OF FILE =============================*/

Added: gnucash/trunk/lib/libqof/qof/kvp-util.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/kvp-util.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/kvp-util.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,64 @@
+/********************************************************************\
+ * kvp-util.h -- misc KVP utilities                                 *
+ * Copyright (C) 2003 Linas Vepstas <linas at linas.org>               *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+/** @addtogroup KVP
+    @{ 
+*/
+/** @file kvp-util.h 
+    @brief GnuCash KVP utility functions 
+ */
+/**  @name Hash Utilities
+ @{ 
+*/
+
+#ifndef GNC_KVP_UTIL_H
+#define GNC_KVP_UTIL_H
+
+#include "config.h"
+
+typedef struct {
+  gpointer key;
+  gpointer value;
+} GHashTableKVPair;
+
+/** 
+  Returns a GSList* of all the
+  keys and values in a given hash table.  Data elements of lists are
+  actual hash elements, so be careful, and deallocation of the
+  GHashTableKVPairs in the result list are the caller's
+  responsibility.  A typical sequence might look like this:
+
+    GSList *kvps = g_hash_table_key_value_pairs(hash);  
+    ... use kvps->data->key and kvps->data->val, etc. here ...
+    g_slist_foreach(kvps, g_hash_table_kv_pair_free_gfunc, NULL);
+    g_slist_free(kvps);
+
+*/
+
+GSList *g_hash_table_key_value_pairs(GHashTable *table);
+void g_hash_table_kv_pair_free_gfunc(gpointer data, gpointer user_data);
+
+/***********************************************************************/
+
+/** @} */
+/** @} */
+#endif /* GNC_KVP_UTIL_H */

Added: gnucash/trunk/lib/libqof/qof/kvp_frame.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/kvp_frame.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/kvp_frame.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1838 @@
+/********************************************************************
+ * kvp_frame.c -- Implements a key-value frame system               *
+ * Copyright (C) 2000 Bill Gribble                                  *
+ * Copyright (C) 2001,2003 Linas Vepstas <linas at linas.org>          *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ ********************************************************************/
+
+#include "config.h"
+
+#define _GNU_SOURCE
+#include <glib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "gnc-date.h"
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "gnc-numeric.h"
+#include "guid.h"
+#include "kvp_frame.h"
+
+
+ /* Note that we keep the keys for this hash table in a GCache
+  * (gnc_string_cache), as it is very likely we will see the 
+  * same keys over and over again  */
+
+struct _KvpFrame 
+{
+  GHashTable  * hash;
+};
+
+
+typedef struct 
+{
+  void        *data;
+  int         datasize;
+} KvpValueBinaryData;
+
+struct _KvpValue 
+{
+  KvpValueType type;
+  union {
+    gint64 int64;
+    double dbl;
+    gnc_numeric numeric;
+    gchar *str;
+    GUID *guid;
+    Timespec timespec;
+    KvpValueBinaryData binary;
+    GList *list;
+    KvpFrame *frame;    
+  } value;
+};
+
+/* This static indicates the debugging module that this .o belongs to.  */
+static QofLogModule log_module = QOF_MOD_KVP;
+
+/********************************************************************
+ * KvpFrame functions
+ ********************************************************************/
+
+static guint 
+kvp_hash_func(gconstpointer v) 
+{
+  return g_str_hash(v);
+}
+
+static gint
+kvp_comp_func(gconstpointer v, gconstpointer v2) 
+{
+  return g_str_equal(v, v2);
+}
+
+static gboolean
+init_frame_body_if_needed(KvpFrame *f) 
+{
+  if(!f->hash) 
+  {
+    f->hash = g_hash_table_new(&kvp_hash_func, &kvp_comp_func);
+  }
+  return(f->hash != NULL);
+}
+
+KvpFrame * 
+kvp_frame_new(void) 
+{
+  KvpFrame * retval = g_new0(KvpFrame, 1);
+
+  /* Save space until the frame is actually used */
+  retval->hash = NULL;
+  return retval;
+}
+
+static void
+kvp_frame_delete_worker(gpointer key, gpointer value, gpointer user_data) 
+{
+  g_cache_remove(gnc_engine_get_string_cache(), key);
+  kvp_value_delete((KvpValue *)value);  
+}
+
+void
+kvp_frame_delete(KvpFrame * frame) 
+{
+  if (!frame) return;
+
+  if(frame->hash) 
+  {
+    /* free any allocated resource for frame or its children */
+    g_hash_table_foreach(frame->hash, & kvp_frame_delete_worker, 
+                         (gpointer)frame);
+    
+    /* delete the hash table */
+    g_hash_table_destroy(frame->hash);
+    frame->hash = NULL;
+  }
+  g_free(frame);
+}
+
+gboolean
+kvp_frame_is_empty(KvpFrame * frame) 
+{
+  if (!frame) return TRUE;
+  if (!frame->hash) return TRUE;
+  return FALSE;
+}
+
+static void
+kvp_frame_copy_worker(gpointer key, gpointer value, gpointer user_data) 
+{
+  KvpFrame * dest = (KvpFrame *)user_data;
+  g_hash_table_insert(dest->hash,
+                      (gpointer)g_cache_insert(gnc_engine_get_string_cache(), key), 
+                      (gpointer)kvp_value_copy(value));
+}
+
+KvpFrame * 
+kvp_frame_copy(const KvpFrame * frame) 
+{
+  KvpFrame * retval = kvp_frame_new();
+
+  if (!frame) return retval;
+
+  if(frame->hash) 
+  {
+    if(!init_frame_body_if_needed(retval)) return(NULL);
+    g_hash_table_foreach(frame->hash,
+                         & kvp_frame_copy_worker, 
+                         (gpointer)retval);
+  }
+  return retval;
+}
+
+/* Replace the old value with the new value.  Return the old value.
+ * Passing in a null value into this routine has the effect of 
+ * removing the key from the KVP tree.
+ */
+KvpValue *
+kvp_frame_replace_slot_nc (KvpFrame * frame, const char * slot, 
+                                 KvpValue * new_value) 
+{
+  gpointer orig_key;
+  gpointer orig_value = NULL;
+  int      key_exists;
+
+  if (!frame || !slot) return NULL; 
+  if (!init_frame_body_if_needed(frame)) return NULL; /* Error ... */
+
+  key_exists = g_hash_table_lookup_extended(frame->hash, slot,
+                                            & orig_key, & orig_value);
+  if(key_exists) 
+  {
+    g_hash_table_remove(frame->hash, slot);
+    g_cache_remove(gnc_engine_get_string_cache(), orig_key);
+  }
+  else
+  {
+    orig_value = NULL;
+  }
+
+  if(new_value) 
+  {
+    g_hash_table_insert(frame->hash,
+                        g_cache_insert(gnc_engine_get_string_cache(),
+                                       (gpointer) slot),
+                        new_value);
+  }
+
+  return (KvpValue *) orig_value;
+}
+
+/* Passing in a null value into this routine has the effect
+ * of deleting the old value stored at this slot.
+ */
+static inline void
+kvp_frame_set_slot_destructively(KvpFrame * frame, const char * slot, 
+                                 KvpValue * new_value) 
+{
+  KvpValue * old_value;
+  old_value = kvp_frame_replace_slot_nc (frame, slot, new_value);
+  kvp_value_delete (old_value);
+}
+
+/* ============================================================ */
+/* Get the named frame, or create it if it doesn't exist.
+ * gcc -O3 should inline it.  It performs no error checks,
+ * the caller is responsible of passing good keys and frames.
+ */
+static inline KvpFrame *
+get_or_make (KvpFrame *fr, const char * key)
+{
+    KvpFrame *next_frame;
+    KvpValue *value;
+
+    value = kvp_frame_get_slot (fr, key);
+    if (value) 
+    {
+      next_frame = kvp_value_get_frame (value);
+    }
+    else
+    {
+      next_frame = kvp_frame_new ();
+      kvp_frame_set_slot_nc (fr, key, 
+                   kvp_value_new_frame_nc (next_frame));
+    }
+    return next_frame;
+}
+
+/* Get pointer to last frame in path. If teh path doesn't exist,
+ * it is created.  The string stored in keypath will be hopelessly 
+ * mangled .
+ */
+static inline KvpFrame *
+kvp_frame_get_frame_slash_trash (KvpFrame *frame, char *key_path) 
+{
+  char *key, *next;
+  if (!frame || !key_path) return frame;
+
+  key = key_path;
+  key --;
+
+  while (key) 
+  {
+    key ++;
+    while ('/' == *key) { key++; }
+    if (0x0 == *key) break;    /* trailing slash */
+    next = strchr (key, '/');
+    if (next) *next = 0x0;
+
+    frame = get_or_make (frame, key);
+    if (!frame) break;  /* error - should never happen */
+    
+    key = next;
+  }
+  return frame;
+}
+
+/* ============================================================ */
+/* Get pointer to last frame in path, or NULL if the path doesn't
+ * exist. The string stored in keypath will be hopelessly mangled .
+ */
+static inline const KvpFrame *
+kvp_frame_get_frame_or_null_slash_trash (const KvpFrame *frame, char *key_path) 
+{
+  KvpValue *value;
+  char *key, *next;
+  if (!frame || !key_path) return NULL;
+
+  key = key_path;
+  key --;
+
+  while (key) 
+  {
+    key ++;
+    while ('/' == *key) { key++; }
+    if (0x0 == *key) break;    /* trailing slash */
+    next = strchr (key, '/');
+    if (next) *next = 0x0;
+
+    value = kvp_frame_get_slot (frame, key);
+    if (!value) return NULL;
+    frame = kvp_value_get_frame (value);
+    if (!frame) return NULL;
+   
+    key = next;
+  }
+  return frame;
+}
+
+/* Return pointer to last frame in path, and also store the
+ * last dangling part of path in 'end_key'.  If path doesn't 
+ * exist, it is created.
+ */
+
+static inline KvpFrame *
+get_trailer_make (KvpFrame * frame, const char * key_path, char **end_key)
+{
+  char *last_key;
+
+  if (!frame || !key_path || (0 == key_path[0])) return NULL;
+
+  last_key = strrchr (key_path, '/');
+  if (NULL == last_key)
+  {
+    last_key = (char *) key_path;
+  }
+  else if (last_key == key_path)
+  {
+    last_key ++;
+  }
+  else if (0 == last_key[1])
+  {
+    return NULL;
+  }
+  else
+  {
+    char *root, *lkey;
+    root = g_strdup (key_path);
+    lkey = strrchr (root, '/');
+    *lkey = 0;
+    frame = kvp_frame_get_frame_slash_trash (frame, root);
+    g_free(root);
+
+    last_key ++;
+  }
+
+  *end_key = last_key;
+  return frame;
+}
+
+
+/* Return pointer to last frame in path, or NULL if the path
+ * doesn't exist.  Also store the last dangling part of path
+ * in 'end_key'.
+ */
+
+static inline const KvpFrame *
+get_trailer_or_null (const KvpFrame * frame, const char * key_path, char **end_key)
+{
+  char *last_key;
+
+  if (!frame || !key_path || (0 == key_path[0])) return NULL;
+
+  last_key = strrchr (key_path, '/');
+  if (NULL == last_key)
+  {
+    last_key = (char *) key_path;
+  }
+  else if (last_key == key_path)
+  {
+    last_key ++;
+  }
+  else if (0 == last_key[1])
+  {
+    return NULL;
+  }
+  else
+  {
+    char *root, *lkey;
+    root = g_strdup (key_path);
+    lkey = strrchr (root, '/');
+    *lkey = 0;
+    frame = kvp_frame_get_frame_or_null_slash_trash (frame, root);
+    g_free(root);
+
+    last_key ++;
+  }
+
+  *end_key = last_key;
+  return frame;
+}
+
+/* ============================================================ */
+
+void
+kvp_frame_set_gint64(KvpFrame * frame, const char * path, gint64 ival) 
+{
+  KvpValue *value;
+  value = kvp_value_new_gint64 (ival);
+  frame = kvp_frame_set_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_double(KvpFrame * frame, const char * path, double dval) 
+{
+  KvpValue *value;
+  value = kvp_value_new_double (dval);
+  frame = kvp_frame_set_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_numeric(KvpFrame * frame, const char * path, gnc_numeric nval) 
+{
+  KvpValue *value;
+  value = kvp_value_new_gnc_numeric (nval);
+  frame = kvp_frame_set_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_string(KvpFrame * frame, const char * path, const char* str) 
+{
+  KvpValue *value;
+  value = kvp_value_new_string (str);
+  frame = kvp_frame_set_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_guid(KvpFrame * frame, const char * path, const GUID *guid) 
+{
+  KvpValue *value;
+  value = kvp_value_new_guid (guid);
+  frame = kvp_frame_set_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_timespec(KvpFrame * frame, const char * path, Timespec ts) 
+{
+  KvpValue *value;
+  value = kvp_value_new_timespec (ts);
+  frame = kvp_frame_set_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_frame(KvpFrame * frame, const char * path, KvpFrame *fr) 
+{
+  KvpValue *value;
+  value = kvp_value_new_frame (fr);
+  frame = kvp_frame_set_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_frame_nc(KvpFrame * frame, const char * path, KvpFrame *fr) 
+{
+  KvpValue *value;
+  value = kvp_value_new_frame_nc (fr);
+  frame = kvp_frame_set_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+/* ============================================================ */
+
+KvpFrame *
+kvp_frame_set_value_nc (KvpFrame * frame, const char * key_path, 
+                        KvpValue * value) 
+{
+  char *last_key;
+
+  frame = get_trailer_make (frame, key_path, &last_key);
+  if (!frame) return NULL;
+  kvp_frame_set_slot_destructively(frame, last_key, value);
+  return frame;
+}
+
+KvpFrame *
+kvp_frame_set_value (KvpFrame * frame, const char * key_path, 
+                     const KvpValue * value) 
+{
+  KvpValue *new_value = NULL;
+  char *last_key;
+
+  frame = get_trailer_make (frame, key_path, &last_key);
+  if (!frame) return NULL;
+
+  if (value) new_value = kvp_value_copy(value);
+  kvp_frame_set_slot_destructively(frame, last_key, new_value);
+  return frame;
+}
+
+KvpValue *
+kvp_frame_replace_value_nc (KvpFrame * frame, const char * key_path, 
+                            KvpValue * new_value) 
+{
+  KvpValue * old_value;
+  char *last_key;
+
+  last_key = NULL;
+  if (new_value)
+  {
+     frame = get_trailer_make (frame, key_path, &last_key);
+  }
+  else
+  {
+     frame = (KvpFrame *) get_trailer_or_null (frame, key_path, &last_key);
+  }
+  if (!frame) return NULL;
+
+  old_value = kvp_frame_replace_slot_nc (frame, last_key, new_value);
+  return old_value;
+}
+
+/* ============================================================ */
+
+KvpFrame *
+kvp_frame_add_value_nc(KvpFrame * frame, const char * path, KvpValue *value) 
+{
+  char *key = NULL;
+  KvpValue *oldvalue;
+
+  frame = (KvpFrame *) get_trailer_or_null (frame, path, &key);
+  oldvalue = kvp_frame_get_slot (frame, key);
+
+  ENTER ("old frame=%s", kvp_frame_to_string(frame));
+  if (oldvalue)
+  {
+    /* If already a glist here, just append */
+    if (KVP_TYPE_GLIST == oldvalue->type)
+    {
+       GList * vlist = oldvalue->value.list;
+       vlist = g_list_append (vlist, value);
+       oldvalue->value.list = vlist;
+    }
+    else
+       /* If some other value, convert it to a glist */
+    {
+       KvpValue *klist;
+       GList *vlist = NULL;
+
+       vlist = g_list_append (vlist, oldvalue);
+       vlist = g_list_append (vlist, value);
+       klist = kvp_value_new_glist_nc (vlist);
+
+       kvp_frame_replace_slot_nc (frame, key, klist);
+    }
+    LEAVE ("new frame=%s", kvp_frame_to_string(frame));
+    return frame;
+  }
+
+  /* Hmm, if we are here, the path doesn't exist. We need to 
+   * create the path, add the value to it. */
+  frame = kvp_frame_set_value_nc (frame, path, value);
+  LEAVE ("new frame=%s", kvp_frame_to_string(frame));
+  return frame;
+}
+
+KvpFrame *
+kvp_frame_add_value(KvpFrame * frame, const char * path, KvpValue *value)
+{
+  value = kvp_value_copy (value);
+  frame = kvp_frame_add_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+  return frame;
+}
+
+void
+kvp_frame_add_gint64(KvpFrame * frame, const char * path, gint64 ival) 
+{
+  KvpValue *value;
+  value = kvp_value_new_gint64 (ival);
+  frame = kvp_frame_add_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_double(KvpFrame * frame, const char * path, double dval) 
+{
+  KvpValue *value;
+  value = kvp_value_new_double (dval);
+  frame = kvp_frame_add_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_numeric(KvpFrame * frame, const char * path, gnc_numeric nval) 
+{
+  KvpValue *value;
+  value = kvp_value_new_gnc_numeric (nval);
+  frame = kvp_frame_add_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_string(KvpFrame * frame, const char * path, const char* str) 
+{
+  KvpValue *value;
+  value = kvp_value_new_string (str);
+  frame = kvp_frame_add_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_guid(KvpFrame * frame, const char * path, const GUID *guid) 
+{
+  KvpValue *value;
+  value = kvp_value_new_guid (guid);
+  frame = kvp_frame_add_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_timespec(KvpFrame * frame, const char * path, Timespec ts) 
+{
+  KvpValue *value;
+  value = kvp_value_new_timespec (ts);
+  frame = kvp_frame_add_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_frame(KvpFrame * frame, const char * path, KvpFrame *fr) 
+{
+  KvpValue *value;
+  value = kvp_value_new_frame (fr);
+  frame = kvp_frame_add_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_frame_nc(KvpFrame * frame, const char * path, KvpFrame *fr) 
+{
+  KvpValue *value;
+  value = kvp_value_new_frame_nc (fr);
+  frame = kvp_frame_add_value_nc (frame, path, value);
+  if (!frame) kvp_value_delete (value);
+}
+
+/* ============================================================ */
+
+void
+kvp_frame_set_slot(KvpFrame * frame, const char * slot, 
+                   const KvpValue * value) 
+{
+  KvpValue *new_value = NULL;
+
+  if (!frame) return;
+
+  g_return_if_fail (slot && *slot != '\0');
+
+  if(value) new_value = kvp_value_copy(value);
+  kvp_frame_set_slot_destructively(frame, slot, new_value);
+}
+
+void
+kvp_frame_set_slot_nc(KvpFrame * frame, const char * slot, 
+                      KvpValue * value) 
+{
+  if (!frame) return;
+
+  g_return_if_fail (slot && *slot != '\0');
+
+  kvp_frame_set_slot_destructively(frame, slot, value);
+}
+
+KvpValue * 
+kvp_frame_get_slot(const KvpFrame * frame, const char * slot) 
+{
+  KvpValue *v;
+  if (!frame) return NULL;
+  if (!frame->hash) return NULL;  /* Error ... */
+  v = g_hash_table_lookup(frame->hash, slot);
+  return v;
+}
+
+/* ============================================================ */
+
+void
+kvp_frame_set_slot_path (KvpFrame *frame,
+                         const KvpValue *new_value,
+                         const char *first_key, ...) 
+{
+  va_list ap;
+  const char *key;
+
+  if (!frame) return;
+
+  g_return_if_fail (first_key && *first_key != '\0');
+
+  va_start (ap, first_key);
+
+  key = first_key;
+
+  while (TRUE) 
+  {
+    KvpValue *value;
+    const char *next_key;
+
+    next_key = va_arg (ap, const char *);
+    if (!next_key) 
+    {
+      kvp_frame_set_slot (frame, key, new_value);
+      break;
+    }
+
+    g_return_if_fail (*next_key != '\0');
+
+    value = kvp_frame_get_slot (frame, key);
+    if (!value) {
+      KvpFrame *new_frame = kvp_frame_new ();
+      KvpValue *frame_value = kvp_value_new_frame (new_frame);
+
+      kvp_frame_set_slot_nc (frame, key, frame_value);
+
+      value = kvp_frame_get_slot (frame, key);
+      if (!value) break;
+    }
+
+    frame = kvp_value_get_frame (value);
+    if (!frame) break;
+
+    key = next_key;
+  }
+
+  va_end (ap);
+}
+
+void
+kvp_frame_set_slot_path_gslist (KvpFrame *frame,
+                                const KvpValue *new_value,
+                                GSList *key_path) 
+{
+  if (!frame || !key_path) return;
+
+  while (TRUE) 
+  {
+    const char *key = key_path->data;
+    KvpValue *value;
+
+    if (!key)
+      return;
+
+    g_return_if_fail (*key != '\0');
+
+    key_path = key_path->next;
+    if (!key_path) 
+    {
+      kvp_frame_set_slot (frame, key, new_value);
+      return;
+    }
+
+    value = kvp_frame_get_slot (frame, key);
+    if (!value) 
+    {
+      KvpFrame *new_frame = kvp_frame_new ();
+      KvpValue *frame_value = kvp_value_new_frame (new_frame);
+
+      kvp_frame_set_slot_nc (frame, key, frame_value);
+
+      value = kvp_frame_get_slot (frame, key);
+      if (!value)
+        return;
+    }
+
+    frame = kvp_value_get_frame (value);
+    if (!frame)
+      return;
+  }
+}
+
+/* ============================================================ */
+/* decode url-encoded string, do it in place
+ * + == space
+ * %xx == asci char where xx is hexadecimal ascii value
+ */
+
+static void
+decode (char *enc)
+{
+	char * p, *w;
+
+	/* Loop, convert +'s to blanks */
+	p = strchr (enc, '+');
+	while (p)
+	{
+		*p = ' ';
+		p = strchr (p, '+');
+	}
+	
+	p = strchr (enc, '%');
+	w = p;
+
+	while (p)
+	{
+		int ch,cl;
+		p++;
+		ch = *p - 0x30;               /* ascii 0 = 0x30 */
+		if (9 < ch) ch -= 0x11 - 10;  /* uppercase A = 0x41 */
+		if (16 < ch) ch -= 0x20;      /* lowercase a = 0x61 */
+
+		p++;
+		cl = *p - 0x30;               /* ascii 0 = 0x30 */
+		if (9 < cl) cl -= 0x11 - 10;  /* uppercase A = 0x41 */
+		if (16 < cl) cl -= 0x20;      /* lowercase a = 0x61 */
+
+		*w = (char) (ch<<4 | cl);
+		
+		do
+		{
+			++w; ++p;
+			*w = *p;
+			if (0x0 == *p) { p = 0; break; }
+			if ('%' == *p) { break; }
+		} while (*p);
+	}
+}
+
+void     
+kvp_frame_add_url_encoding (KvpFrame *frame, const char *enc)
+{
+	char *buff, *p;
+	if (!frame || !enc) return;
+
+	/* Loop over all key-value pairs in the encoded string */
+	buff = g_strdup (enc);
+	p = buff;
+	while (*p)
+	{
+		char *n, *v;
+		n = strchr (p, '&');  /* n = next key-value */
+		if (n) *n = 0x0;
+
+		v = strchr (p, '=');  /* v =  pointer to value */
+		if (!v) break;
+		*v = 0x0;
+		v ++;
+		
+		decode (p);
+		decode (v);
+		kvp_frame_set_slot_nc (frame, p, kvp_value_new_string(v));
+
+		if (!n) break; /* no next key, we are done */
+		p = ++n;
+	}
+	
+	g_free(buff);
+}
+
+/* ============================================================ */
+
+
+gint64
+kvp_frame_get_gint64(const KvpFrame *frame, const char *path)
+{
+  char *key = NULL;
+  frame = get_trailer_or_null (frame, path, &key);
+  return kvp_value_get_gint64(kvp_frame_get_slot (frame, key));
+}
+
+double      
+kvp_frame_get_double(const KvpFrame *frame, const char *path)
+{
+  char *key = NULL;
+  frame = get_trailer_or_null (frame, path, &key);
+  return kvp_value_get_double(kvp_frame_get_slot (frame, key));
+}
+
+gnc_numeric 
+kvp_frame_get_numeric(const KvpFrame *frame, const char *path)
+{
+  char *key = NULL;
+  frame = get_trailer_or_null (frame, path, &key);
+  return kvp_value_get_numeric(kvp_frame_get_slot (frame, key));
+}
+
+char * 
+kvp_frame_get_string(const KvpFrame *frame, const char *path)
+{
+  char *key = NULL;
+  frame = get_trailer_or_null (frame, path, &key);
+  return kvp_value_get_string(kvp_frame_get_slot (frame, key));
+}
+
+GUID *
+kvp_frame_get_guid(const KvpFrame *frame, const char *path)
+{
+  char *key = NULL;
+  frame = get_trailer_or_null (frame, path, &key);
+  return kvp_value_get_guid(kvp_frame_get_slot (frame, key));
+}
+
+void * 
+kvp_frame_get_binary(const KvpFrame *frame, const char *path,
+                                   guint64 * size_return)
+{
+  char *key = NULL;
+  frame = get_trailer_or_null (frame, path, &key);
+  return kvp_value_get_binary(kvp_frame_get_slot (frame, key), size_return);
+}
+
+Timespec
+kvp_frame_get_timespec(const KvpFrame *frame, const char *path)
+{
+  char *key = NULL;
+  frame = get_trailer_or_null (frame, path, &key);
+  return kvp_value_get_timespec(kvp_frame_get_slot (frame, key));
+}
+
+KvpFrame *
+kvp_frame_get_frame(const KvpFrame *frame, const char *path)
+{
+  char *key = NULL;
+  frame = get_trailer_or_null (frame, path, &key);
+  return kvp_value_get_frame(kvp_frame_get_slot (frame, key));
+}
+
+KvpValue *
+kvp_frame_get_value(const KvpFrame *frame, const char *path)
+{
+  char *key = NULL;
+  frame = get_trailer_or_null (frame, path, &key);
+  return kvp_frame_get_slot (frame, key);
+}
+
+/* ============================================================ */
+
+KvpFrame *
+kvp_frame_get_frame_gslist (KvpFrame *frame, GSList *key_path) 
+{
+  if (!frame) return frame;
+
+  while (key_path) 
+  {
+    const char *key = key_path->data;
+
+    if (!key) return frame;  /* an unusual but valid exit for this routine. */
+
+    frame = get_or_make (frame, key);
+    if (!frame) return frame;  /* this should never happen */
+
+    key_path = key_path->next;
+  }
+  return frame;  /* this is the normal exit for this func */
+}
+
+KvpFrame *
+kvp_frame_get_frame_path (KvpFrame *frame, const char *key,  ...) 
+{
+  va_list ap;
+  if (!frame || !key) return frame;
+
+  va_start (ap, key);
+
+  while (key) 
+  {
+    frame = get_or_make (frame, key);
+    if (!frame) break;     /* error, should never occur */
+    key = va_arg (ap, const char *);
+  }
+
+  va_end (ap);
+  return frame;
+}
+
+KvpFrame *
+kvp_frame_get_frame_slash (KvpFrame *frame, const char *key_path) 
+{
+  char *root;
+  if (!frame || !key_path) return frame;
+
+  root = g_strdup (key_path);
+  frame = kvp_frame_get_frame_slash_trash (frame, root);
+  g_free(root);
+  return frame;
+}
+
+/* ============================================================ */
+
+KvpValue *
+kvp_frame_get_slot_path (KvpFrame *frame,
+                         const char *first_key, ...) 
+{
+  va_list ap;
+  KvpValue *value;
+  const char *key;
+
+  if (!frame || !first_key) return NULL;
+
+  va_start (ap, first_key);
+
+  key = first_key;
+  value = NULL;
+
+  while (TRUE) 
+  {
+    value = kvp_frame_get_slot (frame, key);
+    if (!value) break;
+
+    key = va_arg (ap, const char *);
+    if (!key) break;
+
+    frame = kvp_value_get_frame (value);
+    if (!frame)
+    {
+      value = NULL;
+      break;
+    }
+  }
+
+  va_end (ap);
+
+  return value;
+}
+
+KvpValue *
+kvp_frame_get_slot_path_gslist (KvpFrame *frame,
+                                GSList *key_path) 
+{
+  if (!frame || !key_path) return NULL;
+
+  while (TRUE) 
+  {
+    const char *key = key_path->data;
+    KvpValue *value;
+
+    if (!key) return NULL;
+
+    value = kvp_frame_get_slot (frame, key);
+    if (!value) return NULL;
+
+    key_path = key_path->next;
+    if (!key_path) return value;
+
+    frame = kvp_value_get_frame (value);
+    if (!frame) return NULL;
+  }
+}
+
+/********************************************************************
+ * kvp glist functions
+ ********************************************************************/
+
+void
+kvp_glist_delete(GList * list) 
+{
+  GList *node;
+  if(!list) return;
+  
+  /* Delete the data in the list */
+  for (node=list; node; node=node->next)
+  {
+    KvpValue *val = node->data;
+    kvp_value_delete(val);
+  }
+  
+  /* Free the backbone */
+  g_list_free(list);
+}
+
+GList *
+kvp_glist_copy(const GList * list) 
+{
+  GList * retval = NULL;
+  GList * lptr;
+
+  if (!list) return retval;
+  
+  /* Duplicate the backbone of the list (this duplicates the POINTERS
+   * to the values; we need to deep-copy the values separately) */
+  retval = g_list_copy((GList *) list);
+  
+  /* This step deep-copies the values */
+  for(lptr = retval; lptr; lptr = lptr->next) 
+  {
+    lptr->data = kvp_value_copy(lptr->data);
+  }
+  
+  return retval;
+}
+
+gint
+kvp_glist_compare(const GList * list1, const GList * list2) 
+{
+  const GList *lp1;
+  const GList *lp2;
+
+  if(list1 == list2) return 0;
+
+  /* Nothing is always less than something */
+  if(!list1 && list2) return -1;
+  if(list1 && !list2) return 1;
+
+  lp1 = list1;
+  lp2 = list2;
+  while(lp1 && lp2) 
+  {
+    KvpValue *v1 = (KvpValue *) lp1->data;
+    KvpValue *v2 = (KvpValue *) lp2->data;
+    gint vcmp = kvp_value_compare(v1, v2);
+    if(vcmp != 0) return vcmp;
+    lp1 = lp1->next;
+    lp2 = lp2->next;
+  }
+  if(!lp1 && lp2) return -1;
+  if(!lp2 && lp1) return 1;
+  return 0;
+}
+
+/********************************************************************
+ * KvpValue functions
+ ********************************************************************/
+
+KvpValue *
+kvp_value_new_gint64(gint64 value) 
+{
+  KvpValue * retval  = g_new0(KvpValue, 1);
+  retval->type        = KVP_TYPE_GINT64;
+  retval->value.int64 = value;
+  return retval;
+}  
+
+KvpValue *
+kvp_value_new_double(double value) 
+{
+  KvpValue * retval  = g_new0(KvpValue, 1);
+  retval->type        = KVP_TYPE_DOUBLE;
+  retval->value.dbl   = value;
+  return retval;
+}
+
+KvpValue *
+kvp_value_new_numeric(gnc_numeric value) 
+{
+  KvpValue * retval    = g_new0(KvpValue, 1);
+  retval->type          = KVP_TYPE_NUMERIC;
+  retval->value.numeric = value;
+  return retval;
+}
+
+KvpValue *
+kvp_value_new_string(const char * value) 
+{
+  KvpValue * retval;
+  if (!value) return NULL;
+
+  retval = g_new0(KvpValue, 1);
+  retval->type       = KVP_TYPE_STRING;
+  retval->value.str  = g_strdup(value);
+  return retval;
+}  
+
+KvpValue *
+kvp_value_new_guid(const GUID * value) 
+{
+  KvpValue * retval;
+  if (!value) return NULL;
+
+  retval = g_new0(KvpValue, 1);
+  retval->type       = KVP_TYPE_GUID;
+  retval->value.guid = g_new0(GUID, 1);
+  memcpy(retval->value.guid, value, sizeof(GUID));
+  return retval;
+}  
+
+KvpValue *
+kvp_value_new_timespec(Timespec value) 
+{
+  KvpValue * retval = g_new0(KvpValue, 1);
+  retval->type       = KVP_TYPE_TIMESPEC;
+  retval->value.timespec = value;
+  return retval;
+}  
+
+KvpValue *
+kvp_value_new_binary(const void * value, guint64 datasize) 
+{
+  KvpValue * retval;
+  if (!value) return NULL;
+
+  retval = g_new0(KvpValue, 1);
+  retval->type = KVP_TYPE_BINARY;
+  retval->value.binary.data = g_new0(char, datasize);
+  retval->value.binary.datasize = datasize;
+  memcpy(retval->value.binary.data, value, datasize);
+  return retval;
+}
+
+KvpValue *
+kvp_value_new_binary_nc(void * value, guint64 datasize) 
+{
+  KvpValue * retval;
+  if (!value) return NULL;
+
+  retval = g_new0(KvpValue, 1);
+  retval->type = KVP_TYPE_BINARY;
+  retval->value.binary.data = value;
+  retval->value.binary.datasize = datasize;
+  return retval;
+}
+
+KvpValue *
+kvp_value_new_glist(const GList * value) 
+{
+  KvpValue * retval;
+  if (!value) return NULL;
+
+  retval = g_new0(KvpValue, 1);
+  retval->type       = KVP_TYPE_GLIST;
+  retval->value.list = kvp_glist_copy(value);
+  return retval;
+}  
+
+KvpValue *
+kvp_value_new_glist_nc(GList * value) 
+{
+  KvpValue * retval;
+  if (!value) return NULL;
+
+  retval = g_new0(KvpValue, 1);
+  retval->type       = KVP_TYPE_GLIST;
+  retval->value.list = value;
+  return retval;
+}  
+
+KvpValue *
+kvp_value_new_frame(const KvpFrame * value) 
+{
+  KvpValue * retval;
+  if (!value) return NULL;
+
+  retval  = g_new0(KvpValue, 1);
+  retval->type        = KVP_TYPE_FRAME;
+  retval->value.frame = kvp_frame_copy(value);
+  return retval;  
+}
+
+KvpValue *
+kvp_value_new_frame_nc(KvpFrame * value) 
+{
+  KvpValue * retval;
+  if (!value) return NULL;
+
+  retval  = g_new0(KvpValue, 1);
+  retval->type        = KVP_TYPE_FRAME;
+  retval->value.frame = value;
+  return retval;  
+}
+
+void
+kvp_value_delete(KvpValue * value) 
+{
+  if(!value) return;
+
+  switch(value->type) 
+  {
+  case KVP_TYPE_STRING:
+    g_free(value->value.str);
+    break;
+  case KVP_TYPE_GUID:
+    g_free(value->value.guid);
+    break;
+  case KVP_TYPE_BINARY:
+    g_free(value->value.binary.data);
+    break;
+  case KVP_TYPE_GLIST:
+    kvp_glist_delete(value->value.list);
+    break;
+  case KVP_TYPE_FRAME:
+    kvp_frame_delete(value->value.frame);
+    break;
+    
+  case KVP_TYPE_GINT64:    
+  case KVP_TYPE_DOUBLE:
+  case KVP_TYPE_NUMERIC:
+  default:
+    break;
+  }
+  g_free(value);
+}
+
+KvpValueType
+kvp_value_get_type(const KvpValue * value) 
+{
+  if (!value) return -1;
+  return value->type;
+}
+
+gint64
+kvp_value_get_gint64(const KvpValue * value) 
+{
+  if (!value) return 0;
+  if(value->type == KVP_TYPE_GINT64) {
+    return value->value.int64;
+  }
+  else {
+    return 0;
+  }
+}
+
+double 
+kvp_value_get_double(const KvpValue * value) 
+{
+  if (!value) return 0.0;
+  if(value->type == KVP_TYPE_DOUBLE) {
+    return value->value.dbl;
+  }
+  else {
+    return 0.0;
+  }
+}
+
+gnc_numeric 
+kvp_value_get_numeric(const KvpValue * value) 
+{
+  if (!value) return gnc_numeric_zero ();
+  if(value->type == KVP_TYPE_NUMERIC) {
+    return value->value.numeric;
+  }
+  else {
+    return gnc_numeric_zero ();
+  }
+}
+
+char *
+kvp_value_get_string(const KvpValue * value) 
+{
+  if (!value) return NULL;
+  if(value->type == KVP_TYPE_STRING) {
+    return value->value.str;
+  }
+  else { 
+    return NULL; 
+  }
+}
+
+GUID *
+kvp_value_get_guid(const KvpValue * value) 
+{
+  if (!value) return NULL;
+  if(value->type == KVP_TYPE_GUID) {
+    return value->value.guid;
+  }
+  else {
+    return NULL;
+  }
+}
+
+Timespec
+kvp_value_get_timespec(const KvpValue * value) 
+{
+  Timespec ts; ts.tv_sec = 0; ts.tv_nsec = 0;
+  if (!value) return ts;
+  if (value->type == KVP_TYPE_TIMESPEC)
+    return value->value.timespec;
+  else
+    return ts;
+}
+
+void *
+kvp_value_get_binary(const KvpValue * value, guint64 * size_return) 
+{
+  if (!value)
+  {
+    if (size_return)
+      *size_return = 0;
+    return NULL;
+  }
+
+  if(value->type == KVP_TYPE_BINARY) {
+    if (size_return)
+      *size_return = value->value.binary.datasize;
+    return value->value.binary.data;
+  }
+  else {
+    if (size_return)
+      *size_return = 0;
+    return NULL;
+  }
+}
+
+GList *
+kvp_value_get_glist(const KvpValue * value) 
+{
+  if (!value) return NULL;
+  if(value->type == KVP_TYPE_GLIST) {
+    return value->value.list;
+  }
+  else {
+    return NULL;
+  }
+}
+
+KvpFrame *
+kvp_value_get_frame(const KvpValue * value) 
+{
+  if (!value) return NULL;
+  if(value->type == KVP_TYPE_FRAME) {
+    return value->value.frame;
+  }
+  else {
+    return NULL;
+  }
+}
+
+KvpFrame *
+kvp_value_replace_frame_nc(KvpValue *value, KvpFrame * newframe) 
+{
+  KvpFrame *oldframe;
+  if (!value) return NULL;
+  if (KVP_TYPE_FRAME != value->type) return NULL;
+
+  oldframe = value->value.frame;
+  value->value.frame = newframe;
+  return oldframe;  
+}
+
+GList *
+kvp_value_replace_glist_nc(KvpValue * value, GList *newlist) 
+{
+  GList *oldlist;
+  if (!value) return NULL;
+  if (KVP_TYPE_GLIST != value->type) return NULL;
+
+  oldlist = value->value.list;
+  value->value.list = newlist;
+  return oldlist;
+}
+
+/* manipulators */
+
+KvpValue *
+kvp_value_copy(const KvpValue * value) 
+{
+  if(!value) return NULL;
+
+  switch(value->type) {
+  case KVP_TYPE_GINT64:
+    return kvp_value_new_gint64(value->value.int64);
+    break;
+  case KVP_TYPE_DOUBLE:
+    return kvp_value_new_double(value->value.dbl);
+    break;
+  case KVP_TYPE_NUMERIC:
+    return kvp_value_new_gnc_numeric(value->value.numeric);
+    break;
+  case KVP_TYPE_STRING:
+    return kvp_value_new_string(value->value.str);
+    break;
+  case KVP_TYPE_GUID:
+    return kvp_value_new_guid(value->value.guid);
+    break;
+  case KVP_TYPE_TIMESPEC:
+    return kvp_value_new_timespec(value->value.timespec);
+    break;
+  case KVP_TYPE_BINARY:
+    return kvp_value_new_binary(value->value.binary.data,
+                                value->value.binary.datasize);
+    break;
+  case KVP_TYPE_GLIST:
+    return kvp_value_new_glist(value->value.list);
+    break;
+  case KVP_TYPE_FRAME:
+    return kvp_value_new_frame(value->value.frame);
+    break;
+  }  
+  return NULL;
+}
+
+void
+kvp_frame_for_each_slot(KvpFrame *f,
+                        void (*proc)(const char *key,
+                                     KvpValue *value,
+                                     gpointer data),
+                        gpointer data)
+{
+  if(!f) return;
+  if(!proc) return;
+  if(!(f->hash)) return;
+
+  g_hash_table_foreach(f->hash, (GHFunc) proc, data);
+}
+
+gint
+double_compare(double d1, double d2)
+{
+  if(isnan(d1) && isnan(d2)) return 0;
+  if(d1 < d2) return -1;
+  if(d1 > d2) return 1;
+  return 0;
+}
+
+gint
+kvp_value_compare(const KvpValue * kva, const KvpValue * kvb) 
+{
+  if(kva == kvb) return 0;
+  /* nothing is always less than something */
+  if(!kva && kvb) return -1;
+  if(kva && !kvb) return 1;
+
+  if(kva->type < kvb->type) return -1;
+  if(kva->type > kvb->type) return 1;
+
+  switch(kva->type) {
+  case KVP_TYPE_GINT64:
+    if(kva->value.int64 < kvb->value.int64) return -1;
+    if(kva->value.int64 > kvb->value.int64) return 1;
+    return 0;
+    break;
+  case KVP_TYPE_DOUBLE:
+    return double_compare(kva->value.dbl, kvb->value.dbl);
+    break;
+  case KVP_TYPE_NUMERIC:
+    return gnc_numeric_compare (kva->value.numeric, kvb->value.numeric);
+    break;
+  case KVP_TYPE_STRING:
+    return strcmp(kva->value.str, kvb->value.str);
+    break;
+  case KVP_TYPE_GUID:
+    return guid_compare(kva->value.guid, kvb->value.guid);
+    break;
+  case KVP_TYPE_TIMESPEC:
+    return timespec_cmp(&(kva->value.timespec), &(kvb->value.timespec));
+    break;
+  case KVP_TYPE_BINARY:
+    /* I don't know that this is a good compare. Ab is bigger than Acef.
+       But I'm not sure that actually matters here. */
+    if(kva->value.binary.datasize < kvb->value.binary.datasize) return -1;
+    if(kva->value.binary.datasize > kvb->value.binary.datasize) return 1;
+    return memcmp(kva->value.binary.data,
+                  kvb->value.binary.data,
+                  kva->value.binary.datasize);
+    break;
+  case KVP_TYPE_GLIST:
+    return kvp_glist_compare(kva->value.list, kvb->value.list);
+    break;
+  case KVP_TYPE_FRAME:
+    return kvp_frame_compare(kva->value.frame, kvb->value.frame);
+    break;
+  }
+  PERR ("reached unreachable code.");
+  return FALSE;
+}
+
+typedef struct {
+  gint compare;
+  KvpFrame *other_frame;
+} kvp_frame_cmp_status;
+
+static void
+kvp_frame_compare_helper(const char *key, KvpValue * val, gpointer data) 
+{
+  kvp_frame_cmp_status *status = (kvp_frame_cmp_status *) data;
+  if(status->compare == 0) {
+    KvpFrame *other_frame = status->other_frame;
+    KvpValue *other_val = kvp_frame_get_slot(other_frame, key);
+    
+    if(other_val) {
+      status->compare = kvp_value_compare(val, other_val);
+    } else {
+      status->compare = 1;
+    }
+  }
+}
+
+gint
+kvp_frame_compare(const KvpFrame *fa, const KvpFrame *fb) 
+{
+  kvp_frame_cmp_status status;
+
+  if(fa == fb) return 0;
+  /* nothing is always less than something */
+  if(!fa && fb) return -1;
+  if(fa && !fb) return 1;
+
+  /* nothing is always less than something */
+  if(!fa->hash && fb->hash) return -1;
+  if(fa->hash && !fb->hash) return 1;
+
+  status.compare = 0;
+  status.other_frame = (KvpFrame *) fb;
+
+  kvp_frame_for_each_slot((KvpFrame *) fa, kvp_frame_compare_helper, &status);
+
+  if (status.compare != 0)
+    return status.compare;
+
+  status.other_frame = (KvpFrame *) fa;
+
+  kvp_frame_for_each_slot((KvpFrame *) fb, kvp_frame_compare_helper, &status);
+
+  return(-status.compare);
+}
+
+gchar*
+binary_to_string(const void *data, guint32 size)
+{
+    GString *output;
+    guint32 i;
+    guchar *data_str = (guchar*)data;
+    
+    output = g_string_sized_new(size * sizeof(char));
+    
+    for(i = 0; i < size; i++)
+    {
+        g_string_append_printf(output, "%02x", (unsigned int) (data_str[i]));
+    }
+
+    return output->str;
+}
+
+gchar*
+kvp_value_glist_to_string(const GList *list)
+{
+    gchar *tmp1;
+    gchar *tmp2;
+    const GList *cursor;
+
+    tmp1 = g_strdup_printf("[ ");
+
+    for(cursor = list; cursor; cursor = cursor->next)
+    {
+        gchar *tmp3;
+
+        tmp3 = kvp_value_to_string((KvpValue *)cursor->data);
+        tmp2 = g_strdup_printf("%s %s,", tmp1, tmp3 ? tmp3 : "");
+        g_free(tmp1);
+        g_free(tmp3);
+        tmp1 = tmp2;
+    }
+
+    tmp2 = g_strdup_printf("%s ]", tmp1);
+    g_free(tmp1);
+    
+    return tmp2;
+}
+
+gchar*
+kvp_value_to_bare_string(const KvpValue *val)
+{
+    gchar *tmp1;
+    gchar *tmp2;
+    const gchar *ctmp;
+    
+    g_return_val_if_fail(val, NULL);
+    
+    switch(kvp_value_get_type(val))
+    {
+    case KVP_TYPE_GINT64:
+        return g_strdup_printf("%" G_GINT64_FORMAT, kvp_value_get_gint64(val));
+        break;
+
+    case KVP_TYPE_DOUBLE:
+        return g_strdup_printf("(%g)", kvp_value_get_double(val));
+        break;
+
+    case KVP_TYPE_NUMERIC:
+        tmp1 = gnc_numeric_to_string(kvp_value_get_numeric(val));
+        tmp2 = g_strdup_printf("%s", tmp1 ? tmp1 : "");
+        g_free(tmp1);
+        return tmp2;
+        break;
+
+    case KVP_TYPE_STRING:
+        tmp1 = kvp_value_get_string (val);
+        return g_strdup_printf("%s", tmp1 ? tmp1 : "");
+        break;
+
+    case KVP_TYPE_GUID:
+        ctmp = guid_to_string(kvp_value_get_guid(val));
+        tmp2 = g_strdup_printf("%s", ctmp ? ctmp : "");
+        return tmp2;
+        break;
+
+    case KVP_TYPE_TIMESPEC:
+        tmp1 = g_new0 (char, 40);
+        gnc_timespec_to_iso8601_buff (kvp_value_get_timespec (val), tmp1);
+        tmp2 = g_strdup_printf("%s", tmp1);
+        g_free(tmp1);
+        return tmp2;
+        break;
+
+    case KVP_TYPE_BINARY:
+    {
+        guint64 len;
+        void *data;
+        data = kvp_value_get_binary(val, &len);
+        tmp1 = binary_to_string(data, len);
+        return g_strdup_printf("%s", tmp1 ? tmp1 : "");
+    }
+        break;
+ 
+    case KVP_TYPE_GLIST:
+        tmp1 = kvp_value_glist_to_string(kvp_value_get_glist(val));
+        tmp2 = g_strdup_printf("%s", tmp1 ? tmp1 : "");
+        g_free(tmp1);
+        return tmp2;
+        break;
+
+    case KVP_TYPE_FRAME:
+        tmp1 = kvp_frame_to_string(kvp_value_get_frame(val));
+        tmp2 = g_strdup_printf("%s", tmp1 ? tmp1 : "");
+        g_free(tmp1);
+        return tmp2;
+        break;
+
+    default:
+        return g_strdup_printf(" ");
+        break;
+    }
+}
+
+gchar*
+kvp_value_to_string(const KvpValue *val)
+{
+    gchar *tmp1;
+    gchar *tmp2;
+    const gchar *ctmp;
+    
+    g_return_val_if_fail(val, NULL);
+    
+    switch(kvp_value_get_type(val))
+    {
+    case KVP_TYPE_GINT64:
+        return g_strdup_printf("KVP_VALUE_GINT64(%" G_GINT64_FORMAT ")",
+                               kvp_value_get_gint64(val));
+        break;
+
+    case KVP_TYPE_DOUBLE:
+        return g_strdup_printf("KVP_VALUE_DOUBLE(%g)",
+                               kvp_value_get_double(val));
+        break;
+
+    case KVP_TYPE_NUMERIC:
+        tmp1 = gnc_numeric_to_string(kvp_value_get_numeric(val));
+        tmp2 = g_strdup_printf("KVP_VALUE_NUMERIC(%s)", tmp1 ? tmp1 : "");
+        g_free(tmp1);
+        return tmp2;
+        break;
+
+    case KVP_TYPE_STRING:
+        tmp1 = kvp_value_get_string (val);
+        return g_strdup_printf("KVP_VALUE_STRING(%s)", tmp1 ? tmp1 : "");
+        break;
+
+    case KVP_TYPE_GUID:
+        ctmp = guid_to_string(kvp_value_get_guid(val));
+        tmp2 = g_strdup_printf("KVP_VALUE_GUID(%s)", ctmp ? ctmp : "");
+        return tmp2;
+        break;
+
+    case KVP_TYPE_TIMESPEC:
+        tmp1 = g_new0 (char, 40);
+        gnc_timespec_to_iso8601_buff (kvp_value_get_timespec (val), tmp1);
+        tmp2 = g_strdup_printf("KVP_VALUE_TIMESPEC(%s)", tmp1);
+        g_free(tmp1);
+        return tmp2;
+        break;
+
+    case KVP_TYPE_BINARY:
+    {
+        guint64 len;
+        void *data;
+        data = kvp_value_get_binary(val, &len);
+        tmp1 = binary_to_string(data, len);
+        return g_strdup_printf("KVP_VALUE_BINARY(%s)", tmp1 ? tmp1 : "");
+    }
+        break;
+ 
+    case KVP_TYPE_GLIST:
+        tmp1 = kvp_value_glist_to_string(kvp_value_get_glist(val));
+        tmp2 = g_strdup_printf("KVP_VALUE_GLIST(%s)", tmp1 ? tmp1 : "");
+        g_free(tmp1);
+        return tmp2;
+        break;
+
+    case KVP_TYPE_FRAME:
+        tmp1 = kvp_frame_to_string(kvp_value_get_frame(val));
+        tmp2 = g_strdup_printf("KVP_VALUE_FRAME(%s)", tmp1 ? tmp1 : "");
+        g_free(tmp1);
+        return tmp2;
+        break;
+
+    default:
+        return g_strdup_printf(" ");
+        break;
+    }
+}
+
+static void
+kvp_frame_to_string_helper(gpointer key, gpointer value, gpointer data)
+{
+    gchar *tmp_val;
+    gchar **str = (gchar**)data;
+    gchar *old_data = *str;
+
+    tmp_val = kvp_value_to_string((KvpValue *)value);
+
+    *str = g_strdup_printf("%s    %s => %s,\n",
+                           *str ? *str : "",
+                           key ? (char *) key : "",
+                           tmp_val ? tmp_val : "");
+
+    g_free(old_data);
+    g_free(tmp_val);
+}
+
+gchar*
+kvp_frame_to_string(const KvpFrame *frame)
+{
+    gchar *tmp1;
+
+    g_return_val_if_fail (frame != NULL, NULL);
+
+    tmp1 = g_strdup_printf("{\n");
+
+    if (frame->hash)
+      g_hash_table_foreach(frame->hash, kvp_frame_to_string_helper, &tmp1);
+
+    {
+        gchar *tmp2;
+        tmp2 = g_strdup_printf("%s}\n", tmp1);
+        g_free(tmp1);
+        tmp1 = tmp2;
+    }
+
+    return tmp1;
+}
+
+GHashTable*
+kvp_frame_get_hash(const KvpFrame *frame)
+{
+    g_return_val_if_fail (frame != NULL, NULL);
+    return frame->hash;
+}
+
+/* ========================== END OF FILE ======================= */

Added: gnucash/trunk/lib/libqof/qof/kvp_frame.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/kvp_frame.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/kvp_frame.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,694 @@
+/********************************************************************\
+ * kvp_frame.h -- Implements a key-value frame system               *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup KVP
+
+ * A KvpFrame is a set of associations between character strings
+ * (keys) and KvpValue structures.  A KvpValue is a union with
+ * possible types enumerated in the KvpValueType enum, and includes, 
+ * among other things, ints, doubles, strings, guid's, lists, time
+ * and numeric values.  KvpValues may also be other frames, so
+ * KVP is inherently hierarchical.
+ * 
+ * Values are stored in a 'slot' associated with a key.
+ * Pointers passed as arguments into set_slot and get_slot are the
+ * responsibility of the caller.  Pointers returned by get_slot are
+ * owned by the kvp_frame.  Make copies as needed.
+ * 
+ * A 'path' is a sequence of keys that can be followed to a value.
+ * Paths may be specified as varargs (variable number of arguments
+ * to a subrutine, NULL-terminated), as a GSList, or as a standard
+ * URL-like path name.  The later is parsed and treated in the same
+ * way as file paths would be: / separates keys, /./ is treated as /
+ * and /../ means backup one level.  Repeated slashes are treated
+ * as one slash.
+ *
+ * Note that although, in principle, keys may contain the / and . and
+ * .. characters,  doing so may lead to confusion, and will make
+ * path-string parsing routines fail.  In other words, don't use 
+ * a key such as 'some/key' or 'some/./other/../key' because you
+ * may get unexpected results.
+ * 
+ * To set a value into a frame, you will want to use one of the 
+ * kvp_frame_set_xxx() routines.  Most of the other routines provide
+ * only low-level access that you probably shouldn't use.
+ 
+@{
+*/
+/** @file kvp_frame.h
+    @brief A key-value frame system
+    @author Copyright (C) 2000 Bill Gribble
+    @author Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+*/
+
+#ifndef KVP_FRAME_H
+#define KVP_FRAME_H
+
+#include <glib.h>
+
+#include "gnc-date.h"
+#include "gnc-numeric.h"
+#include "guid.h"
+
+#define QOF_MOD_KVP "qof-kvp"
+
+/** Opaque frame structure */
+typedef struct _KvpFrame KvpFrame;
+
+/** A KvpValue is a union with possible types enumerated in the
+ * KvpValueType enum. */
+typedef struct _KvpValue KvpValue;
+ 
+/** \brief possible types in the union KvpValue 
+ * \todo : People have asked for boolean values, 
+ *  e.g. in xaccAccountSetAutoInterestXfer
+ *
+ * \todo In the long run, this should be synchronized with the 
+ * core QOF types, which in turn should be synced to the g_types
+ * in GLib.  Unfortunately, this requires writing a pile of code
+ * to handle all of the different cases.
+ * An alternative might be to make kvp values inherit from the 
+ * core g_types (i.e. add new core g_types) ??
+ */
+typedef enum {
+  KVP_TYPE_GINT64=1,   /**< QOF_TYPE_INT64  gint64 */
+  KVP_TYPE_DOUBLE,     /**< QOF_TYPE_DOUBLE  gdouble */
+  KVP_TYPE_NUMERIC,    /**< QOF_TYPE_NUMERIC */
+  KVP_TYPE_STRING,     /**< QOF_TYPE_STRING gchar* */
+  KVP_TYPE_GUID,       /**< QOF_TYPE_GUID */
+  KVP_TYPE_TIMESPEC,   /**< QOF_TYPE_DATE */
+  KVP_TYPE_BINARY,     /**< no QOF equivalent. */
+  KVP_TYPE_GLIST,      /**< no QOF equivalent. */
+  KVP_TYPE_FRAME       /**< no QOF equivalent. */
+} KvpValueType;
+
+/** \deprecated Deprecated backwards compat tokens 
+
+do \b not use these in new code. 
+*/
+#define kvp_frame KvpFrame
+#define kvp_value KvpValue
+#define kvp_value_t KvpValueType
+  
+/** @name KvpFrame Constructors
+ @{
+*/
+
+/** Return a new empty instance of KvpFrame */
+KvpFrame   * kvp_frame_new(void);
+
+/** Perform a deep (recursive) delete of the frame and any subframes. 
+
+ * kvp_frame_delete and kvp_value_delete are deep (recursive) deletes.
+ * kvp_frame_copy and kvp_value_copy are deep value copies. 
+ */
+void         kvp_frame_delete(KvpFrame * frame);
+
+/** Perform a deep (recursive) value copy, copying the fraame, 
+ *  subframes, and the values as well. */
+KvpFrame   * kvp_frame_copy(const KvpFrame * frame);
+
+/** Return TRUE if the KvpFrame is empty */
+gboolean     kvp_frame_is_empty(KvpFrame * frame);
+
+/** @} */
+
+/** @name KvpFrame Basic Value Storing
+@{
+*/
+
+/**    store the value of the 
+ *     gint64 at the indicated path. If not all frame components of 
+ *     the path exist, they are created.
+ *
+ */
+void kvp_frame_set_gint64(KvpFrame * frame, const char * path, gint64 ival);
+/**
+ *     store the value of the 
+ *     double at the indicated path. If not all frame components of 
+ *     the path exist, they are created.
+*/
+void kvp_frame_set_double(KvpFrame * frame, const char * path, double dval);
+
+/** \deprecated
+
+Use kvp_frame_set_numeric instead of kvp_frame_set_gnc_numeric
+*/
+#define kvp_frame_set_gnc_numeric kvp_frame_set_numeric
+/**    store the value of the 
+ *     gnc_numeric at the indicated path.
+ *     If not all frame components of 
+ *     the path exist, they are created.
+ */
+void kvp_frame_set_numeric(KvpFrame * frame, const char * path, gnc_numeric nval);
+/**    store the value of the 
+ *     Timespec at the indicated path.
+ *     If not all frame components of 
+ *     the path exist, they are created.
+ */
+void kvp_frame_set_timespec(KvpFrame * frame, const char * path, Timespec ts);
+
+/** \deprecated
+
+Use kvp_frame_set_string instead of kvp_frame_set_str
+*/
+#define kvp_frame_set_str kvp_frame_set_string
+
+/** \brief Store a copy of the string at the indicated path.
+
+ *    If not all frame components of the path 
+ *    exist, they are created.  If there was another string previously
+ *    stored at that path, the old copy is deleted.
+ *
+ *    Similarly, the set_guid and set_frame will make copies and
+ *    store those.  Old copies, if any, are deleted.  
+ *
+ * The kvp_frame_set_frame_nc() routine works as above, but does 
+ *    *NOT* copy the frame. 
+ */
+void kvp_frame_set_string(KvpFrame * frame, const char * path, const char* str);
+void kvp_frame_set_guid(KvpFrame * frame, const char * path, const GUID *guid);
+
+void kvp_frame_set_frame(KvpFrame *frame, const char *path, KvpFrame *chld);
+void kvp_frame_set_frame_nc(KvpFrame *frame, const char *path, KvpFrame *chld);
+
+/** The kvp_frame_set_value() routine copies the value into the frame,
+ *    at the location 'path'.   If the path contains slashes '/', these 
+ *    are assumed to represent a sequence of keys.  The returned value 
+ *    is a pointer to the actual frame into which the value was inserted;
+ *    it is NULL if the frame couldn't be found (and thus the value wasn't 
+ *    inserted).  The old value at this location, if any, is destroyed.
+ *
+ *    Pointers passed as arguments into this routine are the responsibility 
+ *    of the caller; the pointers are *not* taken over or managed.
+ */
+KvpFrame *   kvp_frame_set_value(KvpFrame * frame, 
+                                 const char * path, const KvpValue * value);
+/**
+ * The kvp_frame_set_value_nc() routine puts the value (without copying
+ *    it) into the frame, putting it at the location 'path'.  If the path 
+ *    contains slashes '/', these are assumed to represent a sequence of keys.
+ *    The returned value is a pointer to the actual frame into which the value 
+ *    was inserted; it is NULL if the frame couldn't be found (and thus the 
+ *    value wasn't inserted).  The old value at this location, if any,
+ *    is destroyed.
+ *
+ *    This routine is handy for avoiding excess memory allocations & frees.
+ *    Note that because the KvpValue was grabbed, you can't just delete 
+ *    unless you remove the key as well (or unless you replace the value).
+ */
+KvpFrame *    kvp_frame_set_value_nc(KvpFrame * frame, 
+                                 const char * path, KvpValue * value);
+
+/** The kvp_frame_replace_value_nc() routine places the new value 
+ *    at the indicated path.   It returns the old value, if any.  
+ *    It returns NULL if there was an error, or if there was no 
+ *    old value. If the path doesn't exist, it is created, unless
+ *    new_value is NULL.  Passing in a NULL new_value has the 
+ *    effect of deleting the trailing slot (i.e. the trailing path
+ *    element).  
+ */
+KvpValue * kvp_frame_replace_value_nc (KvpFrame * frame, const char * slot,
+                                       KvpValue * new_value);
+/** @} */
+/** @name KvpFrame URL handling
+ @{
+*/
+/** The kvp_frame_add_url_encoding() routine will parse the
+ *  value string, assuming it to be URL-encoded in the standard way,
+ *  turning it into a set of key-value pairs, and adding those to the
+ *  indicated frame.  URL-encoded strings are the things that are
+ *  returned by web browsers when a form is filled out.  For example,
+ *  'start-date=June&end-date=November' consists of two keys, 
+ *  'start-date' and 'end-date', which have the values 'June' and 
+ *  'November', respectively.  This routine also handles % encoding.
+ *
+ *  This routine treats all values as strings; it does *not* attempt
+ *  to perform any type-conversion.
+ * */
+void     kvp_frame_add_url_encoding (KvpFrame *frame, const char *enc);
+/** @} */
+
+/** @name KvpFrame Glist Bag Storing 
+ @{
+*/
+
+/** The kvp_frame_add_gint64() routine will add the value of the 
+ *     gint64 to the glist bag of values at the indicated path. 
+ *     If not all frame components of the path exist, they are 
+ *     created.  If the value previously stored at this path was 
+ *     not a glist bag, then a bag will be formed there, the old 
+ *     value placed in the bag, and the new value added to the bag.
+ *
+ *     Similarly, the add_double, add_numeric, and add_timespec 
+ *     routines perform the same function, for each of the respective 
+ *     types.
+ */
+void kvp_frame_add_gint64(KvpFrame * frame, const char * path, gint64 ival);
+void kvp_frame_add_double(KvpFrame * frame, const char * path, double dval);
+/** \deprecated
+
+Use kvp_frame_add_numeric instead of kvp_frame_add_gnc_numeric
+*/
+#define kvp_frame_add_gnc_numeric kvp_frame_add_numeric
+
+void kvp_frame_add_numeric(KvpFrame * frame, const char * path, gnc_numeric nval);
+void kvp_frame_add_timespec(KvpFrame * frame, const char * path, Timespec ts);
+
+/** \deprecated
+
+Use kvp_frame_add_string instead of kvp_frame_add_str
+*/
+#define kvp_frame_add_str kvp_frame_add_string
+
+/** \brief Copy of the string to the glist bag at the indicated path.
+
+ *    If not all frame components 
+ *    of the path exist, they are created.  If there was another 
+ *    item previously stored at that path, then the path is converted
+ *    to a bag, and the old value, along with the new value, is added
+ *    to the bag.
+ *
+ *    Similarly, the add_guid and add_frame will make copies and
+ *    add those.  
+ *
+ * The kvp_frame_add_frame_nc() routine works as above, but does 
+ *    *NOT* copy the frame. 
+ */
+void kvp_frame_add_string(KvpFrame * frame, const char * path, const char* str);
+void kvp_frame_add_guid(KvpFrame * frame, const char * path, const GUID *guid);
+
+void kvp_frame_add_frame(KvpFrame *frame, const char *path, KvpFrame *chld);
+void kvp_frame_add_frame_nc(KvpFrame *frame, const char *path, KvpFrame *chld);
+
+/* The kvp_frame_add_value() routine will  add a copy of the value
+ *    to the glist bag at the indicated path. If not all frame components
+ *    of the path exist, they are created.  If there was another
+ *    item previously stored at that path, then the path is converted
+ *    to a bag, and the old value, along with the new value, is added
+ *    to the bag.  This routine returns the pointer to the last frame
+ *    (the actual frame to which the value was added), or NULL if there
+ *    was an error of any sort (typically, a parse error in the path).
+ * 
+ *    The *_nc() routine is analogous, except that it doesn't copy the
+ *    value.
+ */
+KvpFrame * kvp_frame_add_value(KvpFrame * frame, const char * path, KvpValue *value);
+KvpFrame * kvp_frame_add_value_nc(KvpFrame * frame, const char * path, KvpValue *value);
+
+
+/** @} */
+
+/** @name KvpFrame Value Fetching
+
+  Value accessors.  These all take a unix-style slash-separated 
+  path as an argument, and return the value stored at that location.
+  If the object at the end of that path is not of the type that was
+  asked for, then a NULL or a zero is returned.  So, for example,
+  asking for a string when the path stored an int will return a NULL.
+  In some future date, this may be changed to a looser type system, 
+  such as perl's automatic re-typing (e.g. an integer value might be
+  converted to a printed string representing that value).
+
+  If any part of the path does not exist, then NULL or zero will be
+  returned.
+
+  The values returned for GUID, binary, GList, KvpFrame and string 
+  are "non-copying" -- the returned item is the actual item stored.
+  Do not delete this item unless you take the required care to avoid
+  possible bad pointer derefrences (i.e. core dumps).  Also, be 
+  careful hanging on to those references if you are also storing
+  at the same path names: the referenced item will be freed during
+  the store.
+
+  That is, if you get a string value (or guid, binary or frame),
+  and then store something else at that path, the string that you've
+  gotten will be freed during the store (internally, by the set_*()
+  routines), and you will be left hanging onto an invalid pointer.
+@{
+*/
+
+gint64      kvp_frame_get_gint64(const KvpFrame *frame, const char *path);
+double      kvp_frame_get_double(const KvpFrame *frame, const char *path);
+gnc_numeric kvp_frame_get_numeric(const KvpFrame *frame, const char *path);
+char      * kvp_frame_get_string(const KvpFrame *frame, const char *path);
+GUID      * kvp_frame_get_guid(const KvpFrame *frame, const char *path);
+void      * kvp_frame_get_binary(const KvpFrame *frame, const char *path,
+                                   guint64 * size_return); 
+Timespec    kvp_frame_get_timespec(const KvpFrame *frame, const char *path);
+KvpValue  * kvp_frame_get_value(const KvpFrame *frame, const char *path);
+
+/** Value accessor.  Takes a unix-style slash-separated path as an
+ *  argument, and return the KvpFrame stored at that location.  If the
+ *  KvpFrame does not exist, then a NULL is returned.
+ *
+ *  @note The semantics here have changed: In gnucash-1.8, if the
+ *  KvpFrame did not exist, this function automatically created one
+ *  and returned it. However, now this function will return NULL in
+ *  this case and the caller has to create a KvpFrame on his own. The
+ *  old functionality is now implemented by
+ *  kvp_frame_get_frame_path(). This happened on 2003-09-14, revision
+ *  1.31. FIXME: Is it really a good idea to change the semantics of
+ *  an existing function and move the old semantics to a new
+ *  function??! It would save us a lot of trouble if the new semantics
+ *  would have been available in a new function!
+ *
+ *  @return The KvpFrame at the specified path, or NULL if it doesn't
+ *  exist.
+*/
+KvpFrame  * kvp_frame_get_frame(const KvpFrame *frame, const char *path);
+
+/** This routine returns the last frame of the path.
+ *  If the frame path doesn't exist, it is created.  
+ *  Note that this is *VERY DIFFERENT FROM* like kvp_frame_get_frame()
+ *
+ *  @note The semantics of this function implemented the gnucash-1.8
+ *  behaviour of kvp_frame_get_frame: In gnucash-1.8, if the KvpFrame
+ *  did not exist, kvp_frame_get_frame automatically created one and
+ *  returned it. However, now that one will return NULL in this case
+ *  and the caller has to create a KvpFrame on his own. The old
+ *  functionality is implemented by this
+ *  kvp_frame_get_frame_path(). This happened on 2003-09-14, revision
+ *  1.31.
+ */
+KvpFrame    * kvp_frame_get_frame_path (KvpFrame *frame, const char *,...);
+
+/** This routine returns the last frame of the path.
+ *  If the frame path doesn't exist, it is created.  
+ *  Note that this is *VERY DIFFERENT FROM* kvp_frame_get_frame()
+ */
+KvpFrame    * kvp_frame_get_frame_gslist (KvpFrame *frame,
+                                           GSList *key_path);
+
+/** This routine returns the last frame of the path.
+ *  If the frame path doesn't exist, it is created.  
+ *  Note that this is *VERY DIFFERENT FROM* kvp_frame_get_frame()
+ *
+ * The kvp_frame_get_frame_slash() routine takes a single string
+ *    where the keys are separated by slashes; thus, for example:
+ *    /this/is/a/valid/path  and///so//is////this/
+ *    Multiple slashes are compresed.  Leading slash is optional.
+ *    The pointers . and .. are *not* currently followed/obeyed.  
+ *    (This is a bug that needs fixing).
+ */
+KvpFrame    * kvp_frame_get_frame_slash (KvpFrame *frame,
+                                          const char *path);
+
+/** @} */
+/** @name KvpFrame KvpValue low-level storing routines.
+
+You probably shouldn't be using these low-level routines
+
+ All of the kvp_frame_set_slot_*() routines set the slot values
+    "destructively", in that if there was an old value there, that
+    old value is destroyed (and the memory freed).  Thus, one 
+    should not hang on to value pointers, as these will get 
+    trashed if set_slot is called on the corresponding key.
+
+    If you want the old value, use kvp_frame_replace_slot().
+ @{
+*/
+
+/** The kvp_frame_replace_slot_nc() routine places the new value into
+ *    the indicated frame, for the given key.  It returns the old
+ *    value, if any.  It returns NULL if the slot doesn't exist,
+ *    if there was some other an error, or if there was no old value.
+ *    Passing in a NULL new_value has the effect of deleting that
+ *    slot.
+ */
+KvpValue * kvp_frame_replace_slot_nc (KvpFrame * frame, const char * slot,
+                                 KvpValue * new_value);
+
+
+/** The kvp_frame_set_slot() routine copies the value into the frame,
+ *    associating it with a copy of 'key'.  Pointers passed as arguments 
+ *    into kvp_frame_set_slot are the responsibility of the caller;
+ *    the pointers are *not* taken over or managed.  The old value at
+ *    this location, if any, is destroyed.
+ */
+void          kvp_frame_set_slot(KvpFrame * frame, 
+                                 const char * key, const KvpValue * value);
+/**
+ * The kvp_frame_set_slot_nc() routine puts the value (without copying
+ *    it) into the frame, associating it with a copy of 'key'.  This 
+ *    routine is handy for avoiding excess memory allocations & frees.
+ *    Note that because the KvpValue was grabbed, you can't just delete 
+ *    unless you remove the key as well (or unless you replace the value).
+ *    The old value at this location, if any, is destroyed.
+ */
+void          kvp_frame_set_slot_nc(KvpFrame * frame, 
+                                    const char * key, KvpValue * value);
+
+/** The kvp_frame_set_slot_path() routine walks the hierarchy,
+ *     using the key values to pick each branch.  When the terminal 
+ *     node is reached, the value is copied into it.  The old value 
+ *     at this location, if any, is destroyed.
+ */
+void          kvp_frame_set_slot_path (KvpFrame *frame,
+                                       const KvpValue *value,
+                                       const char *first_key, ...);
+
+/** The kvp_frame_set_slot_path_gslist() routine walks the hierarchy,
+ *     using the key values to pick each branch.  When the terminal node
+ *     is reached, the value is copied into it.  The old value at this
+ *     location, if any, is destroyed.
+ */
+void          kvp_frame_set_slot_path_gslist (KvpFrame *frame,
+                                              const KvpValue *value,
+                                              GSList *key_path);
+
+/** @} */
+
+/** @name KvpFrame KvpValue Low-Level Retrieval Routines
+
+  You probably shouldn't be using these low-level routines
+
+  Returns the KvpValue in the given KvpFrame 'frame' that is 
+  associated with 'key'.  If there is no key in the frame, NULL
+  is returned.  If the value associated with the key is NULL, 
+  NULL is returned.
+
+  Pointers passed as arguments into get_slot are the responsibility
+  of the caller.  Pointers returned by get_slot are owned by the
+  kvp_frame.  Make copies as needed.
+ @{
+*/
+KvpValue   * kvp_frame_get_slot(const KvpFrame * frame, const char * key);
+
+/** This routine return the value at the end of the
+ * path, or NULL if any portion of the path doesn't exist.
+ */
+KvpValue   * kvp_frame_get_slot_path (KvpFrame *frame,
+                                       const char *first_key, ...);
+
+/** This routine return the value at the end of the
+ * path, or NULL if any portion of the path doesn't exist.
+ */
+KvpValue   * kvp_frame_get_slot_path_gslist (KvpFrame *frame,
+                                              GSList *key_path);
+
+/**
+ * Similar returns as strcmp.
+ */
+gint          kvp_frame_compare(const KvpFrame *fa, const KvpFrame *fb);
+
+gint          double_compare(double v1, double v2);
+/** @} */
+/** @name KvpValue List Convenience Functions
+
+  You probably shouldn't be using these low-level routines 
+
+ kvp_glist_compare() compares <b>GLists of kvp_values</b> (not to
+ be confused with GLists of something else):  it iterates over
+ the list elements, performing a kvp_value_compare on each.
+ @{
+*/
+gint        kvp_glist_compare(const GList * list1, const GList * list2);
+
+/** kvp_glist_copy() performs a deep copy of a <b>GList of 
+ *     kvp_values</b> (not to be confused with GLists of something
+ *     else): same as mapping kvp_value_copy() over the elements and
+ *     then copying the spine.
+ */
+GList     * kvp_glist_copy(const GList * list);
+
+/** kvp_glist_delete() performs a deep delete of a <b>GList of
+ *     kvp_values</b> (not to be confused with GLists of something
+ *     else): same as mapping * kvp_value_delete() over the elements
+ *     and then deleting the GList.
+ */
+void        kvp_glist_delete(GList * list);
+/** @} */
+
+
+/** @name KvpValue Constructors
+
+ You probably shouldn't be using these low-level routines
+
+ The following routines are constructors for kvp_value.
+ Those with pointer arguments copy in the value.
+ The *_nc() versions do *not* copy in thier values, 
+ but use them directly.
+ @{
+ */
+KvpValue   * kvp_value_new_gint64(gint64 value);
+KvpValue   * kvp_value_new_double(double value);
+
+/** \deprecated
+
+Use kvp_value_new_numeric instead of kvp_value_new_gnc_numeric
+*/
+#define kvp_value_new_gnc_numeric kvp_value_new_numeric
+KvpValue   * kvp_value_new_numeric(gnc_numeric value);
+KvpValue   * kvp_value_new_string(const char * value);
+KvpValue   * kvp_value_new_guid(const GUID * guid);
+KvpValue   * kvp_value_new_timespec(Timespec timespec);
+KvpValue   * kvp_value_new_binary(const void * data, guint64 datasize);
+KvpValue   * kvp_value_new_frame(const KvpFrame * value);
+
+/** Creates a KvpValue from a <b>GList of kvp_value's</b>! (Not to be
+ *  confused with GList's of something else!) */
+KvpValue   * kvp_value_new_glist(const GList * value);
+
+/** value constructors (non-copying - KvpValue takes pointer ownership)
+   values *must* have been allocated via glib allocators! (gnew, etc.) */
+KvpValue   * kvp_value_new_binary_nc(void * data, guint64 datasize);
+
+/** Creates a KvpValue from a <b>GList of kvp_value's</b>! (Not to be
+ * confused with GList's of something else!) 
+ *
+ * This value constructor is non-copying (KvpValue takes pointer
+ * ownership). The values *must* have been allocated via glib
+ * allocators! (gnew, etc.) */
+KvpValue   * kvp_value_new_glist_nc(GList *lst);
+
+/** value constructors (non-copying - KvpValue takes pointer ownership)
+   values *must* have been allocated via glib allocators! (gnew, etc.) */
+KvpValue   * kvp_value_new_frame_nc(KvpFrame * value);
+
+/** This is a deep (recursive) delete. */
+void          kvp_value_delete(KvpValue * value);
+
+/** This is a deep value copy. */
+KvpValue   * kvp_value_copy(const KvpValue * value);
+
+/** Replace old frame value with new, return old frame */
+KvpFrame * kvp_value_replace_frame_nc(KvpValue *value, KvpFrame * newframe);
+
+/** Replace old glist value with new, return old glist */
+GList * kvp_value_replace_glist_nc(KvpValue *value, GList *newlist);
+
+/** @} */
+
+
+/** @name KvpValue Value access
+
+ You probably shouldn't be using these low-level routines 
+ @{
+*/
+
+KvpValueType kvp_value_get_type(const KvpValue * value);
+
+
+/** Value accessors. Those for GUID, binary, GList, KvpFrame and
+ *   string are non-copying -- the caller can modify the value 
+ *   directly. Just don't free it, or you screw up everything.
+ *   Note that if another value is stored at the key location
+ *   that this value came from, then this value will be 
+ *   uncermoniously deleted, and you will be left pointing to 
+ *   garbage.  So don't store values at the same time you are
+ *   examining their contents.
+ */
+
+gint64      kvp_value_get_gint64(const KvpValue * value);
+double      kvp_value_get_double(const KvpValue * value);
+gnc_numeric kvp_value_get_numeric(const KvpValue * value);
+
+/** Value accessor. This one is non-copying -- the caller can modify
+ * the value directly. */
+char        * kvp_value_get_string(const KvpValue * value);
+
+/** Value accessor. This one is non-copying -- the caller can modify
+ * the value directly. */
+GUID        * kvp_value_get_guid(const KvpValue * value);
+
+/** Value accessor. This one is non-copying -- the caller can modify
+ * the value directly. */
+void        * kvp_value_get_binary(const KvpValue * value,
+                                   guint64 * size_return); 
+
+/** Returns the GList of kvp_frame's (not to be confused with GList's
+ * of something else!) from the given kvp_frame.  This one is
+ * non-copying -- the caller can modify the value directly. */
+GList       * kvp_value_get_glist(const KvpValue * value);
+
+/** Value accessor. This one is non-copying -- the caller can modify
+ * the value directly. */
+KvpFrame   * kvp_value_get_frame(const KvpValue * value);
+Timespec    kvp_value_get_timespec(const KvpValue * value);
+
+/**
+ * Similar returns as strcmp.
+ **/
+gint          kvp_value_compare(const KvpValue *va, const KvpValue *vb);
+
+/** @} */
+
+/** \brief General purpose function to convert any KvpValue to a string.
+
+Only the bare string is returned, there is no debugging information.
+*/
+gchar* kvp_value_to_bare_string(const KvpValue *val);
+
+/** \brief Debug version of kvp_value_to_string
+
+This version is used only by ::qof_query_printValueForParam,
+itself a debugging and development utility function.
+*/
+gchar* kvp_value_to_string(const KvpValue *val);
+
+/** Manipulator: 
+ *
+ * copying - but more efficient than creating a new KvpValue manually. */
+gboolean kvp_value_binary_append(KvpValue *v, void *data, guint64 size);
+
+/** @name  Iterators
+@{
+*/
+/** Traverse all of the slots in the given kvp_frame.  This function
+   does not descend recursively to traverse any kvp_frames stored as
+   slot values.  You must handle that in proc, with a suitable
+   recursive call if desired. */
+void kvp_frame_for_each_slot(KvpFrame *f,
+                             void (*proc)(const char *key,
+                                          KvpValue *value,
+                                          gpointer data),
+                             gpointer data);
+
+/** @} */
+
+/** Internal helper routines, you probably shouldn't be using these. */
+gchar* kvp_frame_to_string(const KvpFrame *frame);
+gchar* binary_to_string(const void *data, guint32 size);
+gchar* kvp_value_glist_to_string(const GList *list);
+GHashTable* kvp_frame_get_hash(const KvpFrame *frame);
+
+/** @} */
+#endif

Added: gnucash/trunk/lib/libqof/qof/md5.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/md5.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/md5.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,445 @@
+/* md5.c - Functions to compute MD5 message digest of files or memory blocks
+   according to the definition of MD5 in RFC 1321 from April 1992.
+   Copyright (C) 1995, 1996 Free Software Foundation, Inc.
+   NOTE: The canonical source of this file is maintained with the GNU C
+   Library.  Bugs can be reported to bug-glibc at prep.ai.mit.edu.
+
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by the
+   Free Software Foundation; either version 2, or (at your option) any
+   later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+/* Written by Ulrich Drepper <drepper at gnu.ai.mit.edu>, 1995.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+
+#if STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+# include <string.h>
+#else
+# ifndef HAVE_MEMCPY
+#include <string.h>
+#  define memcpy(d, s, n) bcopy ((s), (d), (n))
+# endif
+#endif
+
+#include "md5.h"
+
+#ifdef _LIBC
+# include <endian.h>
+# if __BYTE_ORDER == __BIG_ENDIAN
+#  define WORDS_BIGENDIAN 1
+# endif
+#endif
+
+#ifdef WORDS_BIGENDIAN
+# define SWAP(n)							\
+    (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
+#else
+# define SWAP(n) (n)
+#endif
+
+
+/* This array contains the bytes used to pad the buffer to the next
+   64-byte boundary.  (RFC 1321, 3.1: Step 1)  */
+static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ...  */ };
+
+
+/* Initialize structure containing state of computation.
+   (RFC 1321, 3.3: Step 3)  */
+void
+md5_init_ctx (ctx)
+     struct md5_ctx *ctx;
+{
+  ctx->A = 0x67452301;
+  ctx->B = 0xefcdab89;
+  ctx->C = 0x98badcfe;
+  ctx->D = 0x10325476;
+
+  ctx->total[0] = ctx->total[1] = 0;
+  ctx->buflen = 0;
+}
+
+/* Put result from CTX in first 16 bytes following RESBUF.  The result
+   must be in little endian byte order.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+void *
+md5_read_ctx (ctx, resbuf)
+     const struct md5_ctx *ctx;
+     void *resbuf;
+{
+  ((md5_uint32 *) resbuf)[0] = SWAP (ctx->A);
+  ((md5_uint32 *) resbuf)[1] = SWAP (ctx->B);
+  ((md5_uint32 *) resbuf)[2] = SWAP (ctx->C);
+  ((md5_uint32 *) resbuf)[3] = SWAP (ctx->D);
+
+  return resbuf;
+}
+
+/* Process the remaining bytes in the internal buffer and the usual
+   prolog according to the standard and write the result to RESBUF.
+
+   IMPORTANT: On some systems it is required that RESBUF is correctly
+   aligned for a 32 bits value.  */
+void *
+md5_finish_ctx (ctx, resbuf)
+     struct md5_ctx *ctx;
+     void *resbuf;
+{
+  /* Take yet unprocessed bytes into account.  */
+  md5_uint32 bytes = ctx->buflen;
+  size_t pad;
+
+  /* Now count remaining bytes.  */
+  ctx->total[0] += bytes;
+  if (ctx->total[0] < bytes)
+    ++ctx->total[1];
+
+  pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
+  memcpy (&ctx->buffer[bytes], fillbuf, pad);
+
+  /* Put the 64-bit file length in *bits* at the end of the buffer.  */
+  *(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP (ctx->total[0] << 3);
+  *(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP ((ctx->total[1] << 3) |
+							(ctx->total[0] >> 29));
+
+  /* Process last bytes.  */
+  md5_process_block (ctx->buffer, bytes + pad + 8, ctx);
+
+  return md5_read_ctx (ctx, resbuf);
+}
+
+/* Compute MD5 message digest for bytes read from STREAM.  The
+   resulting message digest number will be written into the 16 bytes
+   beginning at RESBLOCK.  */
+int
+md5_stream (stream, resblock)
+     FILE *stream;
+     void *resblock;
+{
+  /* Important: BLOCKSIZE must be a multiple of 64.  */
+#define BLOCKSIZE 4096
+  struct md5_ctx ctx;
+  char buffer[BLOCKSIZE + 72];
+  size_t sum;
+
+  /* Initialize the computation context.  */
+  md5_init_ctx (&ctx);
+
+  /* Iterate over full file contents.  */
+  while (1)
+    {
+      /* We read the file in blocks of BLOCKSIZE bytes.  One call of the
+	 computation function processes the whole buffer so that with the
+	 next round of the loop another block can be read.  */
+      size_t n;
+      sum = 0;
+
+      /* Read block.  Take care for partial reads.  */
+      do
+	{
+	  n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream);
+
+	  sum += n;
+	}
+      while (sum < BLOCKSIZE && n != 0);
+      if (n == 0 && ferror (stream))
+        return 1;
+
+      /* If end of file is reached, end the loop.  */
+      if (n == 0)
+	break;
+
+      /* Process buffer with BLOCKSIZE bytes.  Note that
+			BLOCKSIZE % 64 == 0
+       */
+      md5_process_block (buffer, BLOCKSIZE, &ctx);
+    }
+
+  /* Add the last bytes if necessary.  */
+  if (sum > 0)
+    md5_process_bytes (buffer, sum, &ctx);
+
+  /* Construct result in desired memory.  */
+  md5_finish_ctx (&ctx, resblock);
+  return 0;
+}
+
+/* Compute MD5 message digest for LEN bytes beginning at BUFFER.  The
+   result is always in little endian byte order, so that a byte-wise
+   output yields to the wanted ASCII representation of the message
+   digest.  */
+void *
+md5_buffer (buffer, len, resblock)
+     const char *buffer;
+     size_t len;
+     void *resblock;
+{
+  struct md5_ctx ctx;
+
+  /* Initialize the computation context.  */
+  md5_init_ctx (&ctx);
+
+  /* Process whole buffer but last len % 64 bytes.  */
+  md5_process_bytes (buffer, len, &ctx);
+
+  /* Put result in desired memory area.  */
+  return md5_finish_ctx (&ctx, resblock);
+}
+
+
+void
+md5_process_bytes (buffer, len, ctx)
+     const void *buffer;
+     size_t len;
+     struct md5_ctx *ctx;
+{
+#define NUM_MD5_WORDS 1024
+  size_t add = 0;
+
+  /* When we already have some bits in our internal buffer concatenate
+     both inputs first.  */
+  if (ctx->buflen != 0)
+    {
+      size_t left_over = ctx->buflen;
+
+      add = 128 - left_over > len ? len : 128 - left_over;
+
+      memcpy (&ctx->buffer[left_over], buffer, add);
+      ctx->buflen += add;
+
+      if (left_over + add > 64)
+	{
+	  md5_process_block (ctx->buffer, (left_over + add) & ~63, ctx);
+	  /* The regions in the following copy operation cannot overlap.  */
+	  memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
+		  (left_over + add) & 63);
+	  ctx->buflen = (left_over + add) & 63;
+	}
+
+      buffer = (const char *) buffer + add;
+      len -= add;
+    }
+
+  /* Process available complete blocks.  */
+  if (len > 64)
+    {
+      if ((add & 3) == 0) /* buffer is still 32-bit aligned */
+        {
+          md5_process_block (buffer, len & ~63, ctx);
+          buffer = (const char *) buffer + (len & ~63);
+        }
+      else                /* buffer is not 32-bit aligned */
+        {
+          md5_uint32 md5_buffer[NUM_MD5_WORDS];
+          size_t num_bytes;
+          size_t buf_bytes;
+
+          num_bytes = len & ~63;
+          while (num_bytes > 0)
+            {
+              buf_bytes = (num_bytes < sizeof(md5_buffer)) ?
+                           num_bytes : sizeof(md5_buffer);
+              memcpy (md5_buffer, buffer, buf_bytes);
+              md5_process_block (md5_buffer, buf_bytes, ctx);
+              num_bytes -= buf_bytes;
+              buffer = (const char *) buffer + buf_bytes;
+            }
+        }
+
+      len &= 63;
+    }
+
+  /* Move remaining bytes in internal buffer.  */
+  if (len > 0)
+    {
+      memcpy (ctx->buffer, buffer, len);
+      ctx->buflen = len;
+    }
+}
+
+
+/* These are the four functions used in the four steps of the MD5 algorithm
+   and defined in the RFC 1321.  The first function is a little bit optimized
+   (as found in Colin Plumbs public domain implementation).  */
+/* #define FF(b, c, d) ((b & c) | (~b & d)) */
+#define FF(b, c, d) (d ^ (b & (c ^ d)))
+#define FG(b, c, d) FF (d, b, c)
+#define FH(b, c, d) (b ^ c ^ d)
+#define FI(b, c, d) (c ^ (b | ~d))
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+   It is assumed that LEN % 64 == 0.  */
+
+void
+md5_process_block (buffer, len, ctx)
+     const void *buffer;
+     size_t len;
+     struct md5_ctx *ctx;
+{
+  md5_uint32 correct_words[16];
+  const md5_uint32 *words = buffer;
+  size_t nwords = len / sizeof (md5_uint32);
+  const md5_uint32 *endp = words + nwords;
+  md5_uint32 A = ctx->A;
+  md5_uint32 B = ctx->B;
+  md5_uint32 C = ctx->C;
+  md5_uint32 D = ctx->D;
+
+  /* First increment the byte count.  RFC 1321 specifies the possible
+     length of the file up to 2^64 bits.  Here we only compute the
+     number of bytes.  Do a double word increment.  */
+  ctx->total[0] += len;
+  if (ctx->total[0] < len)
+    ++ctx->total[1];
+
+  /* Process all bytes in the buffer with 64 bytes in each round of
+     the loop.  */
+  while (words < endp)
+    {
+      md5_uint32 *cwp = correct_words;
+      md5_uint32 A_save = A;
+      md5_uint32 B_save = B;
+      md5_uint32 C_save = C;
+      md5_uint32 D_save = D;
+
+      /* First round: using the given function, the context and a constant
+	 the next context is computed.  Because the algorithms processing
+	 unit is a 32-bit word and it is determined to work on words in
+	 little endian byte order we perhaps have to change the byte order
+	 before the computation.  To reduce the work for the next steps
+	 we store the swapped words in the array CORRECT_WORDS.  */
+
+#define OP(a, b, c, d, s, T)						\
+      do								\
+        {								\
+	  a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T;		\
+	  ++words;							\
+	  CYCLIC (a, s);						\
+	  a += b;							\
+        }								\
+      while (0)
+
+      /* It is unfortunate that C does not provide an operator for
+	 cyclic rotation.  Hope the C compiler is smart enough.  */
+#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
+
+      /* Before we start, one word to the strange constants.
+	 They are defined in RFC 1321 as
+
+	 T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
+       */
+
+      /* Round 1.  */
+      OP (A, B, C, D,  7, 0xd76aa478);
+      OP (D, A, B, C, 12, 0xe8c7b756);
+      OP (C, D, A, B, 17, 0x242070db);
+      OP (B, C, D, A, 22, 0xc1bdceee);
+      OP (A, B, C, D,  7, 0xf57c0faf);
+      OP (D, A, B, C, 12, 0x4787c62a);
+      OP (C, D, A, B, 17, 0xa8304613);
+      OP (B, C, D, A, 22, 0xfd469501);
+      OP (A, B, C, D,  7, 0x698098d8);
+      OP (D, A, B, C, 12, 0x8b44f7af);
+      OP (C, D, A, B, 17, 0xffff5bb1);
+      OP (B, C, D, A, 22, 0x895cd7be);
+      OP (A, B, C, D,  7, 0x6b901122);
+      OP (D, A, B, C, 12, 0xfd987193);
+      OP (C, D, A, B, 17, 0xa679438e);
+      OP (B, C, D, A, 22, 0x49b40821);
+
+      /* For the second to fourth round we have the possibly swapped words
+	 in CORRECT_WORDS.  Redefine the macro to take an additional first
+	 argument specifying the function to use.  */
+#undef OP
+#define OP(f, a, b, c, d, k, s, T)					\
+      do 								\
+	{								\
+	  a += f (b, c, d) + correct_words[k] + T;			\
+	  CYCLIC (a, s);						\
+	  a += b;							\
+	}								\
+      while (0)
+
+      /* Round 2.  */
+      OP (FG, A, B, C, D,  1,  5, 0xf61e2562);
+      OP (FG, D, A, B, C,  6,  9, 0xc040b340);
+      OP (FG, C, D, A, B, 11, 14, 0x265e5a51);
+      OP (FG, B, C, D, A,  0, 20, 0xe9b6c7aa);
+      OP (FG, A, B, C, D,  5,  5, 0xd62f105d);
+      OP (FG, D, A, B, C, 10,  9, 0x02441453);
+      OP (FG, C, D, A, B, 15, 14, 0xd8a1e681);
+      OP (FG, B, C, D, A,  4, 20, 0xe7d3fbc8);
+      OP (FG, A, B, C, D,  9,  5, 0x21e1cde6);
+      OP (FG, D, A, B, C, 14,  9, 0xc33707d6);
+      OP (FG, C, D, A, B,  3, 14, 0xf4d50d87);
+      OP (FG, B, C, D, A,  8, 20, 0x455a14ed);
+      OP (FG, A, B, C, D, 13,  5, 0xa9e3e905);
+      OP (FG, D, A, B, C,  2,  9, 0xfcefa3f8);
+      OP (FG, C, D, A, B,  7, 14, 0x676f02d9);
+      OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
+
+      /* Round 3.  */
+      OP (FH, A, B, C, D,  5,  4, 0xfffa3942);
+      OP (FH, D, A, B, C,  8, 11, 0x8771f681);
+      OP (FH, C, D, A, B, 11, 16, 0x6d9d6122);
+      OP (FH, B, C, D, A, 14, 23, 0xfde5380c);
+      OP (FH, A, B, C, D,  1,  4, 0xa4beea44);
+      OP (FH, D, A, B, C,  4, 11, 0x4bdecfa9);
+      OP (FH, C, D, A, B,  7, 16, 0xf6bb4b60);
+      OP (FH, B, C, D, A, 10, 23, 0xbebfbc70);
+      OP (FH, A, B, C, D, 13,  4, 0x289b7ec6);
+      OP (FH, D, A, B, C,  0, 11, 0xeaa127fa);
+      OP (FH, C, D, A, B,  3, 16, 0xd4ef3085);
+      OP (FH, B, C, D, A,  6, 23, 0x04881d05);
+      OP (FH, A, B, C, D,  9,  4, 0xd9d4d039);
+      OP (FH, D, A, B, C, 12, 11, 0xe6db99e5);
+      OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8);
+      OP (FH, B, C, D, A,  2, 23, 0xc4ac5665);
+
+      /* Round 4.  */
+      OP (FI, A, B, C, D,  0,  6, 0xf4292244);
+      OP (FI, D, A, B, C,  7, 10, 0x432aff97);
+      OP (FI, C, D, A, B, 14, 15, 0xab9423a7);
+      OP (FI, B, C, D, A,  5, 21, 0xfc93a039);
+      OP (FI, A, B, C, D, 12,  6, 0x655b59c3);
+      OP (FI, D, A, B, C,  3, 10, 0x8f0ccc92);
+      OP (FI, C, D, A, B, 10, 15, 0xffeff47d);
+      OP (FI, B, C, D, A,  1, 21, 0x85845dd1);
+      OP (FI, A, B, C, D,  8,  6, 0x6fa87e4f);
+      OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
+      OP (FI, C, D, A, B,  6, 15, 0xa3014314);
+      OP (FI, B, C, D, A, 13, 21, 0x4e0811a1);
+      OP (FI, A, B, C, D,  4,  6, 0xf7537e82);
+      OP (FI, D, A, B, C, 11, 10, 0xbd3af235);
+      OP (FI, C, D, A, B,  2, 15, 0x2ad7d2bb);
+      OP (FI, B, C, D, A,  9, 21, 0xeb86d391);
+
+      /* Add the starting values of the context.  */
+      A += A_save;
+      B += B_save;
+      C += C_save;
+      D += D_save;
+    }
+
+  /* Put checksum in context given as argument.  */
+  ctx->A = A;
+  ctx->B = B;
+  ctx->C = C;
+  ctx->D = D;
+}

Added: gnucash/trunk/lib/libqof/qof/md5.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/md5.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/md5.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,163 @@
+/* md5.h - Declaration of functions and data types used for MD5 sum
+   computing library functions.
+   Copyright (C) 1995, 1996 Free Software Foundation, Inc.
+   NOTE: The canonical source of this file is maintained with the GNU C
+   Library.  Bugs can be reported to bug-glibc at prep.ai.mit.edu.
+
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by the
+   Free Software Foundation; either version 2, or (at your option) any
+   later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifndef _MD5_H
+#define _MD5_H 1
+
+#include <stddef.h>
+#include <stdio.h>
+
+#if defined HAVE_LIMITS_H || _LIBC
+# include <limits.h>
+#endif
+
+/* The following contortions are an attempt to use the C preprocessor
+   to determine an unsigned integral type that is 32 bits wide.  An
+   alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but
+   doing that would require that the configure script compile and *run*
+   the resulting executable.  Locally running cross-compiled executables
+   is usually not possible.  */
+
+#ifdef _LIBC
+# include <sys/types.h>
+typedef u_int32_t md5_uint32;
+#else
+# if defined __STDC__ && __STDC__
+#  define UINT_MAX_32_BITS 4294967295U
+# else
+#  define UINT_MAX_32_BITS 0xFFFFFFFF
+# endif
+
+/* If UINT_MAX isn't defined, assume it's a 32-bit type.
+   This should be valid for all systems GNU cares about because
+   that doesn't include 16-bit systems, and only modern systems
+   (that certainly have <limits.h>) have 64+-bit integral types.  */
+
+# ifndef UINT_MAX
+#  define UINT_MAX UINT_MAX_32_BITS
+# endif
+
+# if UINT_MAX == UINT_MAX_32_BITS
+   typedef unsigned int md5_uint32;
+# else
+#  if USHRT_MAX == UINT_MAX_32_BITS
+    typedef unsigned short md5_uint32;
+#  else
+#   if ULONG_MAX == UINT_MAX_32_BITS
+     typedef unsigned long md5_uint32;
+#   else
+     /* The following line is intended to evoke an error.
+        Using #error is not portable enough.  */
+     "Cannot determine unsigned 32-bit data type."
+#   endif
+#  endif
+# endif
+#endif
+
+#undef __P
+#if defined (__STDC__) && __STDC__
+#define	__P(x) x
+#else
+#define	__P(x) ()
+#endif
+
+/* Structure to save state of computation between the single steps.  */
+struct md5_ctx
+{
+  md5_uint32 A;
+  md5_uint32 B;
+  md5_uint32 C;
+  md5_uint32 D;
+
+  md5_uint32 total[2];
+  md5_uint32 buflen;
+  char buffer[128];
+};
+
+/*
+ * The following three functions are build up the low level used in
+ * the functions `md5_stream' and `md5_buffer'.
+ */
+
+/* Initialize structure containing state of computation.
+   (RFC 1321, 3.3: Step 3)  */
+extern void md5_init_ctx __P ((struct md5_ctx *ctx));
+
+
+/* Starting with the result of former calls of this function (or the
+   initialization function update the context for the next LEN bytes
+   starting at BUFFER.
+
+   It is necessary that LEN is a multiple of 64!!!
+
+   IMPORTANT: On some systems it is required that buffer be 32-bit
+   aligned.  */
+extern void md5_process_block __P ((const void *buffer, size_t len,
+				    struct md5_ctx *ctx));
+
+/* Starting with the result of former calls of this function (or the
+   initialization function) update the context for the next LEN bytes
+   starting at BUFFER.
+
+   It is NOT required that LEN is a multiple of 64.
+
+   IMPORTANT: On some systems it is required that buffer be 32-bit
+   aligned.  */
+extern void md5_process_bytes __P ((const void *buffer, size_t len,
+				    struct md5_ctx *ctx));
+
+/* Process the remaining bytes in the buffer and put result from CTX
+   in first 16 bytes following RESBUF.  The result is always in little
+   endian byte order, so that a byte-wise output yields to the wanted
+   ASCII representation of the message digest.
+
+   IMPORTANT: On some systems it is required that RESBUF be correctly
+   aligned for a 32 bits value.  */
+extern void *md5_finish_ctx __P ((struct md5_ctx *ctx, void *resbuf));
+
+
+/* Put result from CTX in first 16 bytes following RESBUF.  The result is
+   always in little endian byte order, so that a byte-wise output yields
+   to the wanted ASCII representation of the message digest.
+
+   IMPORTANT: On some systems it is required that RESBUF be correctly
+   aligned for a 32 bits value.  */
+extern void *md5_read_ctx __P ((const struct md5_ctx *ctx, void *resbuf));
+
+
+/* Compute MD5 message digest for bytes read from STREAM.  The
+   resulting message digest number will be written into the 16 bytes
+   beginning at RESBLOCK.
+
+   IMPORTANT: On some systems it is required that resblock be 32-bit
+   aligned.  */
+extern int md5_stream __P ((FILE *stream, void *resblock));
+
+
+/* Compute MD5 message digest for LEN bytes beginning at BUFFER.  The
+   result is always in little endian byte order, so that a byte-wise
+   output yields to the wanted ASCII representation of the message
+   digest.
+
+   IMPORTANT: On some systems it is required that buffer and resblock
+   be correctly 32-bit aligned.  */
+extern void *md5_buffer __P ((const char *buffer, size_t len, void *resblock));
+
+#endif

Added: gnucash/trunk/lib/libqof/qof/qof-be-utils.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof-be-utils.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qof-be-utils.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,186 @@
+/********************************************************************\
+ * qof-be-utils.h: api for data storage backend                     *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup Object
+    @{ */
+/** @addtogroup Backend
+   @{ */
+/**  @file qof-be-utils.h
+   @brief QOF Backend Utilities
+   @author Derek Atkins <derek at ihtfp.com>
+   @author Neil Williams <linux at codehelp.co.uk>
+
+  Common code used by objects to define begin_edit() and
+  commit_edit() functions.
+
+*/
+
+#ifndef QOF_BE_UTILS_H
+#define QOF_BE_UTILS_H
+
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "qofbackend-p.h"
+#include "qofbook.h"
+#include "qofinstance.h"
+
+/** begin_edit helper
+ *
+ * @param  inst: an instance of QofInstance
+ *
+ * The caller should use this macro first and then perform any other operations.
+
+ Uses newly created functions to allow the macro to be used
+ when QOF is linked as a library. qofbackend-p.h is a private header.
+ */
+
+#define QOF_BEGIN_EDIT(inst)                                        \
+  QofBackend * be;                                                  \
+  if (!(inst)) return;                                              \
+                                                                    \
+  (inst)->editlevel++;                                              \
+  if (1 < (inst)->editlevel) return;                                \
+                                                                    \
+  if (0 >= (inst)->editlevel)                                       \
+  {                                                                 \
+    PERR ("unbalanced call - resetting (was %d)", (inst)->editlevel); \
+    (inst)->editlevel = 1;                                          \
+  }                                                                 \
+  ENTER ("(inst=%p)", (inst));                                      \
+                                                                    \
+  /* See if there's a backend.  If there is, invoke it. */          \
+  be = qof_book_get_backend ((inst)->book);                         \
+    if (be && qof_backend_begin_exists((be))) {                     \
+     qof_backend_run_begin((be), (inst));                           \
+  } else {                                                          \
+     /* We tried and failed to start transaction! */                \
+     (inst)->dirty = TRUE;                                          \
+  }                                                                 \
+  LEAVE (" ");
+
+/** \brief function version of QOF_BEGIN_EDIT
+
+The macro cannot be used in a function that returns a value,
+this function can be used instead.
+*/
+gboolean qof_begin_edit(QofInstance *inst);
+
+/**
+ * commit_edit helpers
+ *
+ * The caller should call PART1 as the first thing, then
+ * perform any local operations prior to calling the backend.
+ * Then call PART2.
+ */
+
+/**
+ * part1 -- deal with the editlevel
+ *
+ * @param inst: an instance of QofInstance
+ */
+
+#define QOF_COMMIT_EDIT_PART1(inst) {                            \
+  if (!(inst)) return;                                           \
+                                                                 \
+  (inst)->editlevel--;                                           \
+  if (0 < (inst)->editlevel) return;                             \
+                                                                 \
+  /* The pricedb sufffers from delayed update...     */          \
+  /* This may be setting a bad precedent for other types, I fear. */ \
+  /* Other types probably really should handle begin like this. */ \
+  if ((-1 == (inst)->editlevel) && (inst)->dirty)                \
+  {                                                              \
+    QofBackend * be;                                             \
+    be = qof_book_get_backend ((inst)->book);                    \
+    if (be && qof_backend_begin_exists((be))) {                  \
+     qof_backend_run_begin((be), (inst));                        \
+    }                                                            \
+    (inst)->editlevel = 0;                                       \
+  }                                                              \
+  if (0 > (inst)->editlevel)                                     \
+  {                                                              \
+    PERR ("unbalanced call - resetting (was %d)", (inst)->editlevel); \
+    (inst)->editlevel = 0;                                       \
+  }                                                              \
+  ENTER ("(inst=%p) dirty=%d do-free=%d",                        \
+            (inst), (inst)->dirty, (inst)->do_free);             \
+}
+
+/** \brief function version of QOF_COMMIT_EDIT_PART1
+
+The macro cannot be used in a function that returns a value,
+this function can be used instead. Only Part1 is implemented.
+*/
+gboolean qof_commit_edit(QofInstance *inst);
+
+/**
+ * part2 -- deal with the backend
+ *
+ * @param inst: an instance of QofInstance
+ * @param on_error: a function called if there is a backend error.
+ *                void (*on_error)(inst, QofBackendError)
+ * @param on_done: a function called after the commit is complete 
+ *                but before the instect is freed. Perform any other
+ *                operations after the commit.
+ *                void (*on_done)(inst)
+ * @param on_free: a function called if inst->do_free is TRUE. 
+ *                void (*on_free)(inst)
+ */
+#define QOF_COMMIT_EDIT_PART2(inst,on_error,on_done,on_free) {   \
+  QofBackend * be;                                               \
+                                                                 \
+  /* See if there's a backend.  If there is, invoke it. */       \
+  be = qof_book_get_backend ((inst)->book);                      \
+  if (be && qof_backend_commit_exists((be)))                     \
+  {                                                              \
+    QofBackendError errcode;                                     \
+                                                                 \
+    /* clear errors */                                           \
+    do {                                                         \
+      errcode = qof_backend_get_error (be);                      \
+    } while (ERR_BACKEND_NO_ERR != errcode);                     \
+                                                                 \
+    qof_backend_run_commit((be), (inst));                        \
+    errcode = qof_backend_get_error (be);                        \
+    if (ERR_BACKEND_NO_ERR != errcode)                           \
+    {                                                            \
+      /* XXX Should perform a rollback here */                   \
+      (inst)->do_free = FALSE;                                   \
+                                                                 \
+      /* Push error back onto the stack */                       \
+      qof_backend_set_error (be, errcode);                       \
+      (on_error)((inst), errcode);                               \
+    }                                                            \
+    /* XXX the backend commit code should clear dirty!! */       \
+    (inst)->dirty = FALSE;                                       \
+  }                                                              \
+  (on_done)(inst);                                               \
+                                                                 \
+  LEAVE ("inst=%p, dirty=%d do-free=%d",                         \
+            (inst), (inst)->dirty, (inst)->do_free);             \
+  if ((inst)->do_free) {                                         \
+     (on_free)(inst);                                            \
+     return;                                                     \
+  }                                                              \
+}
+
+#endif /* QOF_BE_UTILS_H */
+/** @} */
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/qof.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qof.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,101 @@
+/********************************************************************\
+ * qof.h -- Master QOF public include file                          *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#ifndef QOF_H_
+#define QOF_H_
+/** @defgroup QOF Query Object Framework 
+ @{
+*/
+
+/**
+    @addtogroup Date Date:  Date and Time Printing, Parsing and Manipulation
+    @ingroup QOF
+*/
+/**
+    @addtogroup Entity Entity: Types, Identity and Instance Framework
+    @ingroup QOF
+
+*/
+/**
+    @addtogroup KVP KVP: Key-Value Pairs
+    @ingroup QOF
+*/
+/**
+    @addtogroup Math128 Math128: 128-bit Integer Math Library
+    @ingroup QOF
+*/
+/**
+    @addtogroup Numeric Numeric: Rational Number Handling w/ Rounding Error Control
+    @ingroup QOF
+*/
+/**
+    @addtogroup Object Object: Dynamic Object Class Framework
+    @ingroup QOF
+*/
+/** @addtogroup Choice Choice and collect : One to many links.
+	@ingroup QOF
+*/
+/**
+    @addtogroup Query Query: Querying for Objects
+    @ingroup QOF
+*/
+/**
+    @addtogroup Trace Trace: Error Reporting and Debugging
+    @ingroup QOF
+*/
+/** @addtogroup BookMerge Merging QofBook structures
+	@ingroup QOF
+*/
+/** @addtogroup Event Event: QOF event handlers.
+	@ingroup QOF
+*/
+/**
+    @addtogroup Utilities Misc Utilities
+    @ingroup QOF
+*/
+/** @} */
+
+#include "qofid.h"
+#include "gnc-trace.h"
+#include "gnc-date.h"
+#include "gnc-numeric.h"
+#include "gnc-event.h"
+#include "gnc-engine-util.h"
+#include "guid.h"
+#include "kvp_frame.h"
+#include "kvp-util.h"
+#include "kvp-util-p.h"
+#include "qofbackend.h"
+#include "qofid-p.h"
+#include "qofinstance-p.h"
+#include "qofbook.h"
+#include "qofclass.h"
+#include "qofobject.h"
+#include "qofquery.h"
+#include "qofquerycore.h"
+#include "qofsession.h"
+#include "qofsql.h"
+#include "qofchoice.h"
+#include "qof_book_merge.h"
+#include "qof-be-utils.h"
+#include "qofla-dir.h"
+
+#endif /* QOF_H_ */

Added: gnucash/trunk/lib/libqof/qof/qof_book_merge.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof_book_merge.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qof_book_merge.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,992 @@
+/*********************************************************************
+ * qof_book_merge.c -- api for QoFBook merge with collision handling *
+ * Copyright (C) 2004-2005 Neil Williams <linux at codehelp.co.uk>      *
+ *                                                                   *
+ * This program is free software; you can redistribute it and/or     *
+ * modify it under the terms of the GNU General Public License as    *
+ * published by the Free Software Foundation; either version 2 of    *
+ * the License, or (at your option) any later version.               *
+ *                                                                   *
+ * This program is distributed in the hope that it will be useful,   *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of    *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     *
+ * GNU General Public License for more details.                      *
+ *                                                                   *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, contact:                         *
+ *                                                                   *
+ * Free Software Foundation           Voice:  +1-617-542-5942        *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                    *
+ *                                                                   *
+ ********************************************************************/
+
+#include "gnc-trace.h"
+#include "qof_book_merge.h"
+#include "qofinstance-p.h"
+#include "qofchoice.h"
+#include "qofid-p.h"
+
+static QofLogModule log_module = QOF_MOD_MERGE;
+
+/* private rule iteration struct */
+struct qof_book_mergeRuleIterate {
+	qof_book_mergeRuleForeachCB   fcn;
+	qof_book_mergeData *data;
+	qof_book_mergeRule *rule;
+	GList *ruleList;
+	guint remainder;
+};
+
+/* Make string type parameters 3 times more
+	important in the match than default types.
+	i.e. even if two other parameters differ, 
+	a string match will still provide a better target
+	than when other types match and the string does not.
+*/
+#define DEFAULT_MERGE_WEIGHT    1
+#define QOF_STRING_WEIGHT       3
+#define QOF_DATE_STRING_LENGTH  MAX_DATE_LENGTH
+
+static qof_book_mergeRule*
+qof_book_mergeUpdateRule(qof_book_mergeRule *currentRule, gboolean match, gint weight)
+{
+	gboolean absolute;
+
+	absolute = currentRule->mergeAbsolute;
+	if(absolute && match && currentRule->mergeResult == MERGE_UNDEF) {
+			currentRule->mergeResult = MERGE_ABSOLUTE;
+	}
+	if(absolute && !match) { currentRule->mergeResult = MERGE_UPDATE; }
+	if(!absolute && match &&currentRule->mergeResult == MERGE_UNDEF) {
+			currentRule->mergeResult = MERGE_DUPLICATE;
+	}
+	if(!absolute && !match) {
+		currentRule->difference += weight;
+		if(currentRule->mergeResult == MERGE_DUPLICATE) {
+			currentRule->mergeResult = MERGE_REPORT;
+		}
+	}
+	return currentRule;
+}
+
+struct collect_list_s
+{
+	GSList *linkedEntList;
+};
+
+static void
+collect_reference_cb (QofEntity *ent, gpointer user_data)
+{
+	struct collect_list_s *s;
+
+	s = (struct collect_list_s*)user_data;
+	if(!ent || !s) { return; }
+	s->linkedEntList = g_slist_prepend(s->linkedEntList, ent);
+}
+
+static int 
+qof_book_mergeCompare( qof_book_mergeData *mergeData ) 
+{
+	qof_book_mergeRule *currentRule;
+	QofCollection *mergeColl, *targetColl;
+	gchar      *stringImport, *stringTarget, *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;
+	g_return_val_if_fail((currentRule != NULL), -1);
+	absolute = currentRule->mergeAbsolute;
+	mergeEnt = currentRule->importEnt;
+	targetEnt = currentRule->targetEnt;
+	paramList = currentRule->mergeParam;
+	currentRule->difference = 0;
+	currentRule->mergeResult = MERGE_UNDEF;
+	currentRule->linkedEntList = NULL;
+	g_return_val_if_fail((targetEnt)||(mergeEnt)||(paramList), -1);
+	kvpImport = kvp_frame_new();
+	kvpTarget = kvp_frame_new();
+	mergeError = FALSE;
+	while(paramList != NULL) {
+		mergeMatch = FALSE;
+		knowntype = FALSE;
+		qtparam = paramList->data;
+		mergeParamName = qtparam->param_name;
+		g_return_val_if_fail(mergeParamName != NULL, -1);
+		mergeType = qtparam->param_type;
+		if(safe_strcmp(mergeType, QOF_TYPE_STRING) == 0)  { 
+			stringImport = qtparam->param_getfcn(mergeEnt,qtparam);
+			stringTarget = qtparam->param_getfcn(targetEnt,qtparam);
+			/* very strict string matches may need to be relaxed. */
+			if(stringImport == NULL) { stringImport = ""; }
+			if(stringTarget == NULL) { stringTarget = ""; }
+			if(safe_strcmp(stringImport,stringTarget) == 0) { mergeMatch = TRUE; }
+			/* Give special weight to a string match */
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, QOF_STRING_WEIGHT);
+			stringImport = stringTarget = NULL;
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_DATE) == 0) {
+			date_getter = (Timespec (*)(QofEntity*, QofParam*))qtparam->param_getfcn;
+			tsImport = date_getter(mergeEnt, qtparam);
+			tsTarget = date_getter(targetEnt, qtparam);
+			if(timespec_cmp(&tsImport, &tsTarget) == 0) { mergeMatch = TRUE; }
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+			knowntype= TRUE;
+		}
+		if((safe_strcmp(mergeType, QOF_TYPE_NUMERIC) == 0)  ||
+		(safe_strcmp(mergeType, QOF_TYPE_DEBCRED) == 0)) { 
+			numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			numericImport = numeric_getter(mergeEnt,qtparam);
+			numericTarget = numeric_getter(targetEnt,qtparam);
+			if(gnc_numeric_compare (numericImport, numericTarget) == 0) { mergeMatch = TRUE; }
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_GUID) == 0) { 
+			guidImport = qtparam->param_getfcn(mergeEnt,qtparam);
+			guidTarget = qtparam->param_getfcn(targetEnt,qtparam);
+			if(guid_compare(guidImport, guidTarget) == 0) { mergeMatch = TRUE; }
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_INT32) == 0) { 
+			int32_getter = (gint32 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			i32Import = int32_getter(mergeEnt, qtparam);
+			i32Target = int32_getter(targetEnt, qtparam);
+			if(i32Target == i32Import) { mergeMatch = TRUE; }
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_INT64) == 0) { 
+			int64_getter = (gint64 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			i64Import = int64_getter(mergeEnt, qtparam);
+			i64Target = int64_getter(targetEnt, qtparam);
+			if(i64Target == i64Import) { mergeMatch = TRUE; }
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT); 
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_DOUBLE) == 0) { 
+			double_getter = (double (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			doubleImport = double_getter(mergeEnt, qtparam);
+			doubleTarget = double_getter(mergeEnt, qtparam);
+			if(doubleImport == doubleTarget) { mergeMatch = TRUE; }
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT); 
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_BOOLEAN) == 0){ 
+			boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			booleanImport = boolean_getter(mergeEnt, qtparam);
+			booleanTarget = boolean_getter(targetEnt, qtparam);
+			if(booleanImport != FALSE && booleanImport != TRUE) { booleanImport = FALSE; }
+			if(booleanTarget != FALSE && booleanTarget != TRUE) { booleanTarget = FALSE; }
+			if(booleanImport == booleanTarget) { mergeMatch = TRUE; }
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_KVP) == 0) { 
+			kvpImport = kvp_frame_copy(qtparam->param_getfcn(mergeEnt,qtparam));
+			kvpTarget = kvp_frame_copy(qtparam->param_getfcn(targetEnt,qtparam));
+			if(kvp_frame_compare(kvpImport, kvpTarget) == 0) { mergeMatch = TRUE; }
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT); 
+			knowntype= TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_CHAR) == 0) { 
+			charImport = qtparam->param_getfcn(mergeEnt,qtparam);
+			charTarget = qtparam->param_getfcn(targetEnt,qtparam);
+			if(charImport == charTarget) { mergeMatch = TRUE; }
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT); 
+			knowntype= TRUE;
+		}
+		/* No object should have QofSetterFunc defined for the book, but just to be safe, do nothing. */
+		if(safe_strcmp(mergeType, QOF_ID_BOOK) == 0) { knowntype= TRUE; }
+		if(safe_strcmp(mergeType, QOF_TYPE_COLLECT) == 0) {
+			struct collect_list_s s;
+			s.linkedEntList = NULL;
+			mergeColl = qtparam->param_getfcn(mergeEnt, qtparam);
+			targetColl = qtparam->param_getfcn(targetEnt, qtparam);
+			s.linkedEntList = g_slist_copy(currentRule->linkedEntList);
+			qof_collection_foreach(mergeColl, collect_reference_cb, &s);
+			currentRule->linkedEntList = g_slist_copy(s.linkedEntList);
+			if(0 == qof_collection_compare(mergeColl, targetColl)) { mergeMatch = TRUE; }
+			currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+			knowntype = TRUE;
+		}
+		if(safe_strcmp(mergeType, QOF_TYPE_CHOICE) ==0) {
+			referenceEnt = qtparam->param_getfcn(mergeEnt, qtparam);
+			currentRule->linkedEntList = g_slist_prepend(currentRule->linkedEntList, referenceEnt);
+			if(referenceEnt == qtparam->param_getfcn(targetEnt, qtparam)) { mergeMatch = TRUE; }
+			knowntype = TRUE;
+		}
+		if(knowntype == FALSE) {
+			referenceEnt = qtparam->param_getfcn(mergeEnt, qtparam);
+			if((referenceEnt != NULL)
+				&&(safe_strcmp(referenceEnt->e_type, mergeType) == 0)) {
+					currentRule->linkedEntList = g_slist_prepend(currentRule->linkedEntList, referenceEnt);
+					if(referenceEnt == qtparam->param_getfcn(targetEnt, qtparam)) { mergeMatch = TRUE; }
+					currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+			}
+		}
+		paramList = g_slist_next(paramList);
+	}
+	mergeData->currentRule = currentRule;
+	g_free(kvpImport);
+	g_free(kvpTarget);
+	return 0;
+}
+
+static void 
+qof_book_mergeCommitForeachCB(gpointer rule, gpointer arg)
+{
+	struct qof_book_mergeRuleIterate *iter;
+
+	g_return_if_fail(arg != NULL);
+	iter = (struct qof_book_mergeRuleIterate*)arg;
+	g_return_if_fail(iter->data != NULL);
+	iter->fcn (iter->data, (qof_book_mergeRule*)rule, iter->remainder);
+	iter->remainder--;
+}
+
+static void
+qof_book_mergeCommitForeach (
+			qof_book_mergeRuleForeachCB cb, 
+			qof_book_mergeResult mergeResult,
+			qof_book_mergeData *mergeData)
+{
+	struct qof_book_mergeRuleIterate iter;
+	qof_book_mergeRule *currentRule;
+	GList *subList;
+
+	g_return_if_fail(cb != NULL);
+	g_return_if_fail(mergeData != NULL);
+	currentRule = mergeData->currentRule;
+	g_return_if_fail(currentRule != NULL);
+	g_return_if_fail(mergeResult > 0);
+	g_return_if_fail((mergeResult != MERGE_INVALID)||(mergeResult != MERGE_UNDEF)||(mergeResult != MERGE_REPORT));
+
+	iter.fcn = cb;
+	subList = NULL;
+	iter.ruleList = g_list_copy(mergeData->mergeList);
+	while(iter.ruleList!=NULL) {
+		currentRule = iter.ruleList->data;
+		if(currentRule->mergeResult == mergeResult) {
+			subList = g_list_prepend(subList, currentRule);
+		}
+		iter.ruleList = g_list_next(iter.ruleList);
+	}
+	iter.remainder = g_list_length(subList);
+	iter.data = mergeData;
+	g_list_foreach (subList, qof_book_mergeCommitForeachCB, &iter);
+}
+
+/* build the table of target comparisons
+
+This can get confusing, so bear with me. (!)
+
+Whilst iterating through the entities in the mergeBook, qof_book_mergeForeach assigns
+a targetEnt to each mergeEnt (until it runs out of targetEnt or mergeEnt). Each match
+is made against the one targetEnt that best matches the mergeEnt. Fine so far.
+
+Any mergeEnt is only ever assigned a targetEnt if the calculated difference between
+the two is less than the difference between that targetEnt and any previous mergeEnt
+match.
+
+The next mergeEnt may be a much better match for that targetEnt and the target_table
+is designed to solve the issues that result from this conflict. The previous match
+must be re-assigned because if two mergeEnt's are matched with only one targetEnt,
+data loss \b WILL follow. Equally, the current mergeEnt must replace the previous
+one as it is a better match. qof_entity_rating holds the details required to identify
+the correct mergeEnt to be re-assigned and these mergeEnt entities are therefore
+orphaned - to be re-matched later.
+
+Meanwhile, the current mergeEnt is entered into target_table with it's difference and
+rule data, in case an even better match is found later in the mergeBook.
+
+Finally, each mergeEnt in the orphan_list is now put through the comparison again.
+
+*/
+static gboolean
+qof_book_merge_rule_cmp(gconstpointer a, gconstpointer b)
+{
+	qof_book_mergeRule *ra = (qof_book_mergeRule *) a;
+	qof_book_mergeRule *rb = (qof_book_mergeRule *) b;
+	if (ra->difference == rb->difference) { return TRUE; }
+	else return FALSE;
+}
+
+static void
+qof_book_merge_orphan_check(double difference, qof_book_mergeRule *mergeRule, qof_book_mergeData *mergeData)
+{
+	/* Called when difference is lower than previous
+		Lookup target to find previous match
+		and re-assign mergeEnt to orphan_list */
+	qof_book_mergeRule *rule;
+
+	g_return_if_fail(mergeRule != NULL);
+	g_return_if_fail(mergeData != NULL);
+	if(g_hash_table_size(mergeData->target_table) == 0) { return; }
+	rule = (qof_book_mergeRule*)g_hash_table_lookup(mergeData->target_table, mergeRule->targetEnt);
+	/* If NULL, no match was found. */
+	if(rule == NULL) { return; }
+	/* Only orphan if this is a better match than already exists. */
+	if(difference >= rule->difference) { return; }
+	rule->targetEnt = NULL;
+	rule->mergeResult = MERGE_UNDEF;
+	mergeData->orphan_list = g_slist_append(mergeData->orphan_list, rule);
+}
+
+static void
+qof_book_merge_match_orphans(qof_book_mergeData *mergeData)
+{
+	GSList *orphans, *targets;
+	qof_book_mergeRule *rule, *currentRule;
+	QofEntity *best_matchEnt;
+	double difference;
+
+	g_return_if_fail(mergeData != NULL);
+	currentRule = mergeData->currentRule;
+	g_return_if_fail(currentRule != NULL);
+	/* This routine does NOT copy the orphan list, it
+		is used recursively until empty. */
+	orphans = mergeData->orphan_list;
+	targets = g_slist_copy(mergeData->targetList);
+	while(orphans != NULL) {
+		rule = orphans->data;
+		g_return_if_fail(rule != NULL);
+		difference = g_slist_length(mergeData->mergeObjectParams);
+		if(rule->targetEnt == NULL) {
+			rule->mergeResult = MERGE_NEW;
+			rule->difference = 0;
+			mergeData->mergeList = g_list_prepend(mergeData->mergeList,rule);
+			orphans = g_slist_next(orphans);
+			continue;
+		}
+		mergeData->currentRule = rule;
+		g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
+		if(difference > mergeData->currentRule->difference) {
+			best_matchEnt = currentRule->targetEnt;
+			difference = currentRule->difference;
+			rule = currentRule;
+			mergeData->mergeList = g_list_prepend(mergeData->mergeList,rule);
+			qof_book_merge_orphan_check(difference, rule, mergeData);
+		}
+		orphans = g_slist_next(orphans);
+	}
+	g_slist_free(mergeData->orphan_list);
+	g_slist_free(targets);
+}
+
+static void 
+qof_book_mergeForeachTarget (QofEntity* targetEnt, gpointer user_data)
+{
+	qof_book_mergeData *mergeData;
+
+	g_return_if_fail(user_data != NULL);
+	mergeData = (qof_book_mergeData*)user_data;
+	g_return_if_fail(targetEnt != NULL);
+	mergeData->targetList = g_slist_prepend(mergeData->targetList,targetEnt);
+}
+
+static void 
+qof_book_mergeForeachTypeTarget ( QofObject* merge_obj, gpointer user_data) 
+{
+	qof_book_mergeData *mergeData;
+	qof_book_mergeRule *currentRule;
+
+	g_return_if_fail(user_data != NULL);
+	mergeData = (qof_book_mergeData*)user_data;
+	currentRule = mergeData->currentRule;
+	g_return_if_fail(currentRule != NULL);
+	g_return_if_fail(merge_obj != NULL);
+	if(safe_strcmp(merge_obj->e_type, currentRule->importEnt->e_type) == 0) {
+		qof_object_foreach(currentRule->importEnt->e_type, mergeData->targetBook, 
+			qof_book_mergeForeachTarget, user_data);
+	}
+}
+
+static void 
+qof_book_mergeForeach ( QofEntity* mergeEnt, gpointer user_data) 
+{
+	qof_book_mergeRule *mergeRule, *currentRule;
+	qof_book_mergeData *mergeData;
+	QofEntity *targetEnt, *best_matchEnt;
+	GUID *g;
+	double difference;
+	GSList *c;
+
+	g_return_if_fail(user_data != NULL);
+	mergeData = (qof_book_mergeData*)user_data;
+	g_return_if_fail(mergeEnt != NULL);
+	currentRule = mergeData->currentRule;
+	g_return_if_fail(currentRule != NULL);
+	g = guid_malloc();
+	*g = mergeEnt->guid;
+	mergeRule = g_new(qof_book_mergeRule,1);
+	mergeRule->importEnt = 		mergeEnt;
+	mergeRule->difference = 	difference = 0;
+	mergeRule->mergeAbsolute = 	FALSE;
+	mergeRule->mergeResult = 	MERGE_UNDEF;
+	mergeRule->updated = 		FALSE;
+	mergeRule->mergeType = 		mergeEnt->e_type;
+	mergeRule->mergeLabel = 	qof_object_get_type_label(mergeEnt->e_type);
+	mergeRule->mergeParam = 	g_slist_copy(mergeData->mergeObjectParams);
+	mergeRule->linkedEntList =	NULL;
+	mergeData->currentRule = mergeRule;
+	targetEnt = best_matchEnt = NULL;
+	targetEnt = qof_collection_lookup_entity (
+		qof_book_get_collection (mergeData->targetBook, mergeEnt->e_type), g);
+	if( targetEnt != NULL) { 
+		mergeRule->mergeAbsolute = TRUE;
+		mergeRule->targetEnt = targetEnt;
+		g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
+		mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+		mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
+		return;
+	}
+	/* no absolute match exists */
+	g_slist_free(mergeData->targetList);
+	mergeData->targetList = NULL;
+	qof_object_foreach_type(qof_book_mergeForeachTypeTarget, mergeData);
+	if(g_slist_length(mergeData->targetList) == 0) {
+		mergeRule->mergeResult = MERGE_NEW;
+	}
+	difference = g_slist_length(mergeRule->mergeParam);
+	c = g_slist_copy(mergeData->targetList);
+	while(c != NULL) {
+		mergeRule->targetEnt = c->data;
+		currentRule = mergeRule;
+		/* compare two entities and sum the differences */
+		g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
+		if(mergeRule->difference == 0) {
+			/* check if this is a better match than one already assigned */
+			best_matchEnt = mergeRule->targetEnt;
+			mergeRule->mergeResult = MERGE_DUPLICATE;
+			difference = 0;
+			mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+			g_slist_free(c);
+			guid_free(g);
+			/* exact match, return */
+			return;
+		}
+		if(difference > mergeRule->difference) {
+			/* The chosen targetEnt determines the parenting of any child object */
+			/* check if this is a better match than one already assigned */
+			best_matchEnt = mergeRule->targetEnt;
+			difference = mergeRule->difference;
+			/* Use match to lookup the previous entity that matched this targetEnt (if any)
+				and remove targetEnt from the rule for that mergeEnt.
+				Add the previous mergeEnt to orphan_list.
+			*/			
+			qof_book_merge_orphan_check(difference, mergeRule, mergeData);
+		}
+		c = g_slist_next(c);
+	}
+	g_slist_free(c);
+	if(best_matchEnt != NULL ) {
+		mergeRule->targetEnt = best_matchEnt;
+		mergeRule->difference = difference;
+		/* Set this entity in the target_table in case a better match can be made
+			with the next mergeEnt. */
+		g_hash_table_insert(mergeData->target_table, mergeRule->targetEnt, mergeRule);
+		/* compare again with the best partial match */
+		g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
+		mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+	}
+	else {
+		mergeRule->targetEnt = NULL;
+		mergeRule->difference = 0;
+		mergeRule->mergeResult = MERGE_NEW;
+		mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+	}
+	mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
+	guid_free(g);
+	/* return to qof_book_mergeInit */
+}
+
+static void 
+qof_book_mergeForeachParam( QofParam* param, gpointer user_data) 
+{
+	qof_book_mergeData *mergeData;
+
+	g_return_if_fail(user_data != NULL);
+	mergeData = (qof_book_mergeData*)user_data;
+	g_return_if_fail(param != NULL);
+	if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
+		mergeData->mergeObjectParams = g_slist_append(mergeData->mergeObjectParams, param);
+	}
+}
+
+static void 
+qof_book_mergeForeachType ( QofObject* merge_obj, gpointer user_data) 
+{
+	qof_book_mergeData *mergeData;
+
+	g_return_if_fail(user_data != NULL);
+	mergeData = (qof_book_mergeData*)user_data;
+	g_return_if_fail((merge_obj != NULL));
+	/* Skip unsupported objects */
+	if((merge_obj->create == NULL)||(merge_obj->foreach == NULL)){
+		DEBUG (" merge_obj QOF support failed %s", merge_obj->e_type);
+		return;
+	}
+	if(mergeData->mergeObjectParams != NULL) g_slist_free(mergeData->mergeObjectParams);
+	mergeData->mergeObjectParams = NULL;
+	qof_class_param_foreach(merge_obj->e_type, qof_book_mergeForeachParam , mergeData);
+	qof_object_foreach(merge_obj->e_type, mergeData->mergeBook, qof_book_mergeForeach, mergeData);
+}
+
+static void
+qof_book_mergeRuleCB(gpointer rule, gpointer arg)
+{
+	struct qof_book_mergeRuleIterate *iter;
+	qof_book_mergeData *mergeData;
+
+	g_return_if_fail(arg != NULL);
+	iter = (struct qof_book_mergeRuleIterate*)arg;
+	mergeData = iter->data;
+	g_return_if_fail(mergeData != NULL);
+	g_return_if_fail(mergeData->abort == FALSE);
+	iter->fcn (mergeData, (qof_book_mergeRule*)rule, iter->remainder);
+	iter->data = mergeData;
+	iter->remainder--;
+}
+
+static void 
+qof_book_mergeCommitRuleLoop(
+						qof_book_mergeData *mergeData,
+						qof_book_mergeRule *rule, 
+						guint remainder) 
+{ 
+	QofInstance *inst;
+	gboolean    registered_type;
+	QofEntity   *referenceEnt;
+	/* cm_ prefix used for variables that hold the data to commit */
+	QofCollection *cm_coll;
+	QofParam    *cm_param;
+	gchar       *cm_string;
+	const GUID  *cm_guid;
+	KvpFrame    *cm_kvp;
+	/* function pointers and variables for parameter getters that don't use pointers normally */
+	gnc_numeric  cm_numeric, (*numeric_getter)  (QofEntity*, QofParam*);
+	double       cm_double,  (*double_getter)   (QofEntity*, QofParam*);
+	gboolean     cm_boolean, (*boolean_getter)  (QofEntity*, QofParam*);
+	gint32       cm_i32,     (*int32_getter)    (QofEntity*, QofParam*);
+	gint64       cm_i64,     (*int64_getter)    (QofEntity*, QofParam*);
+	Timespec     cm_date,    (*date_getter)     (QofEntity*, QofParam*);
+	char         cm_char,    (*char_getter)     (QofEntity*, QofParam*);
+	/* function pointers to the parameter setters */
+	void (*string_setter)    (QofEntity*, const char*);
+	void (*date_setter)      (QofEntity*, Timespec);
+	void (*numeric_setter)   (QofEntity*, gnc_numeric);
+	void (*guid_setter)      (QofEntity*, const GUID*);
+	void (*double_setter)    (QofEntity*, double);
+	void (*boolean_setter)   (QofEntity*, gboolean);
+	void (*i32_setter)       (QofEntity*, gint32);
+	void (*i64_setter)       (QofEntity*, gint64);
+	void (*char_setter)      (QofEntity*, char);
+	void (*kvp_frame_setter) (QofEntity*, KvpFrame*);
+	void (*reference_setter) (QofEntity*, QofEntity*);
+	void (*collection_setter)(QofEntity*, QofCollection*);
+
+	g_return_if_fail(rule != NULL);
+	g_return_if_fail(mergeData != NULL);
+	g_return_if_fail(mergeData->targetBook != NULL);
+	g_return_if_fail((rule->mergeResult != MERGE_NEW)||(rule->mergeResult != MERGE_UPDATE));
+	/* create a new object for MERGE_NEW */
+	/* The new object takes the GUID from the import to retain an absolute match */
+	if(rule->mergeResult == MERGE_NEW) {
+		inst = (QofInstance*)qof_object_new_instance(rule->importEnt->e_type, mergeData->targetBook);
+		g_return_if_fail(inst != NULL);
+		rule->targetEnt = &inst->entity;
+		qof_entity_set_guid(rule->targetEnt, qof_entity_get_guid(rule->importEnt));
+	}
+	/* currentRule->targetEnt is now set,
+		1. by an absolute GUID match or
+		2. by best_matchEnt and difference or
+		3. by MERGE_NEW.
+	*/
+	while(rule->mergeParam != NULL) {
+		registered_type = FALSE;
+		g_return_if_fail(rule->mergeParam->data);
+		cm_param = rule->mergeParam->data;
+		rule->mergeType = cm_param->param_type;
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_STRING) == 0)  { 
+			cm_string = cm_param->param_getfcn(rule->importEnt, cm_param);
+			string_setter = (void(*)(QofEntity*, const char*))cm_param->param_setfcn;
+			if(string_setter != NULL) { string_setter(rule->targetEnt, cm_string); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_DATE) == 0) { 
+			date_getter = (Timespec (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+			cm_date = date_getter(rule->importEnt, cm_param);
+			date_setter = (void(*)(QofEntity*, Timespec))cm_param->param_setfcn;
+			if(date_setter != NULL) { date_setter(rule->targetEnt, cm_date); }
+			registered_type = TRUE;
+		}
+		if((safe_strcmp(rule->mergeType, QOF_TYPE_NUMERIC) == 0)  ||
+		(safe_strcmp(rule->mergeType, QOF_TYPE_DEBCRED) == 0)) { 
+			numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+			cm_numeric = numeric_getter(rule->importEnt, cm_param);
+			numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_param->param_setfcn;
+			if(numeric_setter != NULL) { numeric_setter(rule->targetEnt, cm_numeric); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_GUID) == 0) { 
+			cm_guid = cm_param->param_getfcn(rule->importEnt, cm_param);
+			guid_setter = (void(*)(QofEntity*, const GUID*))cm_param->param_setfcn;
+			if(guid_setter != NULL) { guid_setter(rule->targetEnt, cm_guid); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_INT32) == 0) { 
+			int32_getter = (gint32 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+			cm_i32 = int32_getter(rule->importEnt, cm_param);
+			i32_setter = (void(*)(QofEntity*, gint32))cm_param->param_setfcn;
+			if(i32_setter != NULL) { i32_setter(rule->targetEnt, cm_i32); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_INT64) == 0) { 
+			int64_getter = (gint64 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+			cm_i64 = int64_getter(rule->importEnt, cm_param);
+			i64_setter = (void(*)(QofEntity*, gint64))cm_param->param_setfcn;
+			if(i64_setter != NULL) { i64_setter(rule->targetEnt, cm_i64); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_DOUBLE) == 0) { 
+			double_getter = (double (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+			cm_double = double_getter(rule->importEnt, cm_param);
+			double_setter = (void(*)(QofEntity*, double))cm_param->param_setfcn;
+			if(double_setter != NULL) { double_setter(rule->targetEnt, cm_double); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_BOOLEAN) == 0){ 
+			boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+			cm_boolean = boolean_getter(rule->importEnt, cm_param);
+			boolean_setter = (void(*)(QofEntity*, gboolean))cm_param->param_setfcn;
+			if(boolean_setter != NULL) { boolean_setter(rule->targetEnt, cm_boolean); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_KVP) == 0) { 
+			cm_kvp = kvp_frame_copy(cm_param->param_getfcn(rule->importEnt,cm_param));
+			kvp_frame_setter = (void(*)(QofEntity*, KvpFrame*))cm_param->param_setfcn;
+			if(kvp_frame_setter != NULL) { kvp_frame_setter(rule->targetEnt, cm_kvp); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_CHAR) == 0) { 
+			char_getter = (char (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+			cm_char = char_getter(rule->importEnt,cm_param);
+			char_setter = (void(*)(QofEntity*, char))cm_param->param_setfcn;
+			if(char_setter != NULL) { char_setter(rule->targetEnt, cm_char); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_COLLECT) == 0) {
+			cm_coll = cm_param->param_getfcn(rule->importEnt, cm_param);
+			collection_setter = (void(*)(QofEntity*, QofCollection*))cm_param->param_setfcn;
+			if(collection_setter != NULL) { collection_setter(rule->targetEnt, cm_coll); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(rule->mergeType, QOF_TYPE_CHOICE) == 0) {
+			referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param);
+			reference_setter = (void(*)(QofEntity*, QofEntity*))cm_param->param_setfcn;
+			if(reference_setter != NULL) 
+			{ 
+				reference_setter(rule->targetEnt, referenceEnt); 
+			}
+			registered_type = TRUE;
+		}
+		if(registered_type == FALSE) {
+			referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param);
+			if(referenceEnt) {
+				reference_setter = (void(*)(QofEntity*, QofEntity*))cm_param->param_setfcn;
+				if(reference_setter != NULL) 
+				{ 
+					reference_setter(rule->targetEnt, referenceEnt); 
+				}
+			}
+		}
+		rule->mergeParam = g_slist_next(rule->mergeParam);
+	}
+}
+/* ================================================================ */
+/* API functions. */
+
+qof_book_mergeData*
+qof_book_mergeInit( QofBook *importBook, QofBook *targetBook) 
+{
+	qof_book_mergeData *mergeData;
+	qof_book_mergeRule *currentRule;
+	GList *check;
+
+	g_return_val_if_fail((importBook != NULL)&&(targetBook != NULL), NULL);
+	mergeData = g_new(qof_book_mergeData, 1);
+	mergeData->abort = FALSE;
+	mergeData->mergeList = NULL;
+	mergeData->targetList = NULL;
+	mergeData->mergeBook = importBook;
+	mergeData->targetBook = targetBook;
+	mergeData->mergeObjectParams = NULL;
+	mergeData->orphan_list = NULL;
+	mergeData->target_table = g_hash_table_new( g_direct_hash, qof_book_merge_rule_cmp);
+	currentRule = g_new(qof_book_mergeRule, 1);
+	mergeData->currentRule = currentRule;
+	qof_object_foreach_type(qof_book_mergeForeachType, mergeData);
+	g_return_val_if_fail(mergeData->mergeObjectParams, NULL);
+	if(mergeData->orphan_list != NULL) {
+		qof_book_merge_match_orphans(mergeData);
+	}
+	
+	check = g_list_copy(mergeData->mergeList);
+	while(check != NULL) {
+		currentRule = check->data;
+		if(currentRule->mergeResult == MERGE_INVALID) {
+			mergeData->abort = TRUE;
+			return(NULL);
+		}
+		check = g_list_next(check);
+	}
+	g_list_free(check);
+	return mergeData;
+}
+
+void
+qof_book_merge_abort (qof_book_mergeData *mergeData)
+{
+	qof_book_mergeRule *currentRule;
+
+	g_return_if_fail(mergeData != NULL);
+	while(mergeData->mergeList != NULL) {
+		currentRule = mergeData->mergeList->data;
+		g_slist_free(currentRule->linkedEntList);
+		g_slist_free(currentRule->mergeParam);
+		g_free(mergeData->mergeList->data);
+		if(currentRule) {
+			g_slist_free(currentRule->linkedEntList);
+			g_slist_free(currentRule->mergeParam);
+			g_free(currentRule);
+		}
+		mergeData->mergeList = g_list_next(mergeData->mergeList);
+	}
+	g_list_free(mergeData->mergeList);
+	g_slist_free(mergeData->mergeObjectParams);
+	g_slist_free(mergeData->targetList);
+	if(mergeData->orphan_list != NULL) { g_slist_free(mergeData->orphan_list); }
+	g_hash_table_destroy(mergeData->target_table);
+	g_free(mergeData);
+}
+
+/* The QOF_TYPE_DATE output format from
+qof_book_merge_param_as_string has been changed to QSF_XSD_TIME,
+a UTC formatted timestring: 2005-01-01T10:55:23Z
+If you change QOF_UTC_DATE_FORMAT, change 
+backend/file/qsf-xml.c : qsf_entity_foreach to
+reformat to QSF_XSD_TIME or the QSF XML will
+FAIL the schema validation and QSF exports will become invalid.
+
+The QOF_TYPE_BOOLEAN is lowercase for the same reason.
+*/
+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("%" G_GINT64_FORMAT, param_i64);
+			return param_string;
+		}
+		if(safe_strcmp(paramType, QOF_TYPE_DOUBLE) == 0) { 
+			double_getter = (double (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			param_double = double_getter(qtEnt, qtparam);
+			param_string = g_strdup_printf("%f", param_double);
+			return param_string;
+		}
+		if(safe_strcmp(paramType, QOF_TYPE_BOOLEAN) == 0){ 
+			boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+			param_boolean = boolean_getter(qtEnt, qtparam);
+			/* Boolean values need to be lowercase for QSF validation. */
+			if(param_boolean == TRUE) { param_string = g_strdup("true"); }
+			else { param_string = g_strdup("false"); }
+			return param_string;
+		}
+		/* "kvp" contains repeating values, cannot be a single string for the frame. */
+		if(safe_strcmp(paramType, QOF_TYPE_KVP) == 0) { return param_string; }
+		if(safe_strcmp(paramType, QOF_TYPE_CHAR) == 0) { 
+			char_getter = (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. */
+/* ==================================================================== */

Added: gnucash/trunk/lib/libqof/qof/qof_book_merge.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof_book_merge.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qof_book_merge.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,461 @@
+/*********************************************************************
+ * qof_book_merge.h -- api for QofBook merge with collision handling *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk>           *
+ *                                                                   *
+ * This program is free software; you can redistribute it and/or     *
+ * modify it under the terms of the GNU General Public License as    *
+ * published by the Free Software Foundation; either version 2 of    *
+ * the License, or (at your option) any later version.               *
+ *                                                                   *
+ * This program is distributed in the hope that it will be useful,   *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of    *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     *
+ * GNU General Public License for more details.                      *
+ *                                                                   *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, contact:                         *
+ *                                                                   *
+ * Free Software Foundation           Voice:  +1-617-542-5942        *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                    *
+ *                                                                   *
+ ********************************************************************/
+
+#ifndef QOFBOOKMERGE_H
+#define QOFBOOKMERGE_H
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#define QOF_MOD_MERGE "qof-merge"
+
+/** @addtogroup BookMerge
+
+<b>Collision handling principles.</b>\n
+\n
+	-# Always check for a ::GUID first and compare. qof_book_merge only accepts valid ::QofBook
+	data  and therefore ALL objects in the import book will	include valid GUID's.
+	-# If the original import data did not contain a GUID (e.g. an external non-GnuCash source)
+	the GUID values will have been created during the import and will not match any existing
+	GUID's in the target book so objects that do not have a GUID match cannot be assumed to
+	be ::MERGE_NEW - parameter values must be checked.
+	-# If import contains data from closed books, store the data from the closed
+	books in the current book as active. i.e. re-open the books. 
+
+- If a GUID match exists, set qof_book_mergeRule::mergeAbsolute to \a TRUE.
+	-# If ALL parameters in the import object match the target object with the same \a GUID, 
+	set ::qof_book_mergeResult to \a MERGE_ABSOLUTE.
+	-# If any parameters differ, set ::MERGE_UPDATE.
+- If the import object \a GUID does not match an existing object,
+mergeAbsolute is unchanged from the default \a FALSE
+The parameter values of the object are compared to other objects of the same
+type in the target book.
+	-# If the same data exists in the target book with a different GUID, the object 
+	is tagged as DUPLICATE.
+	-# If the data has changed, the object is tagged as REPORT. 
+	-# If the data does not match, the object is tagged as NEW
+
+More information is at http://code.neil.williamsleesmill.me.uk/
+
+Each foreach function uses g_return_if_fail checks to protect the target book. If
+any essential data is missing, the loop returns without changing the target book.
+Note that this will not set or return an error value. However, g_return is only 
+used for critical errors that arise from programming errors, not for invalid import data 
+which should be cleaned up before creating the import QofBook.
+
+Only ::qof_book_mergeUpdateResult and ::qof_book_mergeCommit return 
+any error values to the calling process. ::qof_book_mergeInit returns a
+pointer to the ::qof_book_mergeData struct - the calling process needs to
+make sure this is non-NULL to know that the Init has been successful.
+
+ @{
+*/
+/** @file  qof_book_merge.h
+    @brief API for merging two \c QofBook structures with collision handling
+    @author Copyright (c) 2004-2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#include <glib.h>
+#include "gnc-engine-util.h"
+#include "qofbook.h"
+#include "qofclass.h"
+#include "qofobject.h"
+#include "qofinstance.h"
+#include "gnc-trace.h"
+
+/** \brief Results of collisions and user resolution.
+
+All rules are initialised as ::MERGE_UNDEF.
+Once the comparison is complete, each object within the import will be
+updated.
+
+::MERGE_ABSOLUTE, ::MERGE_NEW, ::MERGE_DUPLICATE and ::MERGE_UPDATE can be reported
+to the user along with all ::MERGE_REPORT objects for confirmation.
+It may be useful later to allow \a MERGE_ABSOLUTE, \a MERGE_NEW, \a MERGE_DUPLICATE and
+\a MERGE_UPDATE to not be reported, if the user sets a preferences option
+for each result. (Always accept new items: Y/N default NO, ignores all
+MERGE_NEW if set to Y etc.) This option would not require any changes
+to qof_book_merge.
+
+\a MERGE_NEW, \a MERGE_DUPLICATE and \a MERGE_UPDATE are only actioned after 
+conflicts are resolved by the user using a dialog and all \a MERGE_REPORT objects are
+re-assigned to one of MERGE_NEW, MERGE_DUPLICATE or MERGE_UPDATE. There is no automatic
+merge, even if no entities are tagged as MERGE_REPORT, the calling process must still
+check for REPORT items using ::qof_book_mergeRuleForeach and call ::qof_book_mergeCommit.
+
+\a MERGE_INVALID data should be rare and allows for user-abort - the imported file/source
+ may be corrupted and the prescence of invalid data should raise concerns that
+ the rest of the data may be corrupted, damaged or otherwise altered. If any entity is 
+ tagged as MERGE_INVALID, the merge operation will abort and leave the target book
+ completely unchanged.
+
+\a MERGE_ABSOLUTE is only used for a complete match. The import object contains 
+the same data in the same parameters with no omissions or amendments. If any data is missing, 
+amended or added, the data is labelled \a MERGE_UPDATE. 
+
+ Every piece of data has a corresponding result. Only when the count of items labelled
+ \a MERGE_REPORT is equal to zero are \a MERGE_NEW and \a MERGE_UPDATE 
+ items added to the existing book.\n \a MERGE_DUPLICATE items are silently ignored.
+ Aborting the dialog/process (by the user or in a program crash) at any point before the
+ final commit leaves the existing book completely untouched.
+*/
+typedef enum { 
+	MERGE_UNDEF,     /**< default value before comparison is made. */
+	MERGE_ABSOLUTE,  /**< GUID exact match, no new data - \b ignore */
+	MERGE_NEW,       /**< import object does \b not exist in the target book - \b add */
+	MERGE_REPORT,    /**< import object needs user intervention - \b report */
+	MERGE_DUPLICATE, /**< import object with different GUID exactly matches existing GUID - \b ignore */
+	MERGE_UPDATE,    /**< import object matches an existing entity but includes new or 
+                             modified parameter data - \b update */
+	MERGE_INVALID    /**< import object didn't match registered object or parameter 
+                             types or user decided to abort - \b abort */
+}qof_book_mergeResult;
+
+/** \brief One rule per entity, built into a single GList for the entire merge 
+
+All rules are stored in the GList qof_book_mergeData::mergeList.
+
+If the ::GUID matches it's the always same semantic object,
+regardless of whether other data fields are changed.
+\n	
+The boolean value mergeAbsolute defaults to \c FALSE
+
+NOTE 1: if mergeAbsolute == \c TRUE, ::qof_book_mergeResult will still be set to 
+::MERGE_UPDATE if parameters within this entity have been modified.
+
+NOTE 2: ::qof_book_merge_param_as_string returns \b string representations of the parameter
+data that is causing a collision. These values must \b NOT be used to set the target
+parameter - the function is provided for display purposes only, to make it simple to
+explain the collision to the user using MERGE_REPORT and the dialog.
+
+The GHashTable targetTable in qof_book_mergeRule will probably replace the GSList of the
+same name in mergeData.
+
+*/
+
+typedef struct 
+{
+	/* internal counters and reference variables */
+	gboolean mergeAbsolute;   /**< Only set if the GUID of the import matches the target */
+	double difference;       /**< used to find best match in a book where no GUID matches */
+	gboolean updated;        /**< prevent the mergeResult from being overwritten. */
+	/* rule objects set from or by external calls */
+	QofIdType mergeType;     /**< type of comparison required for check for collision */
+	const char* mergeLabel;  /**< Descriptive label for the object type, useful for the
+                                 user intervention dialog. */
+	GSList *mergeParam;      /**< list of usable parameters for the object type */
+	GSList *linkedEntList;   /**< list of complex data types included in this object. 
+
+	linkedEntList contains an ::QofEntity reference to any parameter that is not
+	one of the core QOF_TYPE data types. This entity must be already registered with QOF
+	and the results of the comparison for the linked entity will modulate the mergeResult
+	of this object. e.g. if an invoice is the same value but for a different customer,
+	the invoice will be set to MERGE_REPORT and the customer as MERGE_NEW.
+	*/
+	qof_book_mergeResult mergeResult; /**< result of comparison with main ::QofBook */
+	QofEntity *importEnt;    /**< pointer to the current entity in the import book. */
+	QofEntity *targetEnt;    /**< pointer to the corresponding entity in the target book, if any. */
+}qof_book_mergeRule;
+
+
+/** \brief 	mergeData contains the essential context data for any merge.
+
+Used to dictate what to merge, how to merge it, where to get the new data and
+where to put the amended data. 
+
+Combines lists of \a ::QofParam, \a ::QofEntity and \a ::qof_book_mergeRule into one struct that
+can be easily passed between callbacks. Also holds the pointers to the import and target ::QofBook 
+structures.
+	
+- targetList and mergeObjectParams change each time a new object type is set for compare. 
+- mergeList is the complete list of rules for all objects in the import book.
+
+*/
+typedef struct
+{
+	GSList 	*mergeObjectParams;  /**< GSList of ::QofParam details for each parameter in the current object. */
+	GList 	*mergeList;          /**< GList of all ::qof_book_mergeRule rules for the merge operation. */
+	GSList 	*targetList;         /**< GSList of ::QofEntity * for each object of this type in the target book */
+	QofBook *mergeBook;          /**< pointer to the import book for this merge operation. */
+	QofBook *targetBook;         /**< pointer to the target book for this merge operation. */
+	gboolean abort;	             /**< set to TRUE if MERGE_INVALID is set. */
+	qof_book_mergeRule *currentRule; /**< placeholder for the rule currently being tested or applied. */
+	GSList *orphan_list;         /**< List of QofEntity's that need to be rematched.
+
+	When one QofEntity has a lower difference to the targetEnt than the previous best_match,
+	the new match takes precedence. This list holds those orphaned entities that are not a good
+	enough match so that these can be rematched later. The ranking is handled using
+	the private qof_entity_rating struct and the GHashTable ::qof_book_mergeData::target_table.
+	*/
+	GHashTable *target_table;    /**< The GHashTable to hold the qof_entity_rating values.  */
+
+}qof_book_mergeData;
+
+
+/* ======================================================================== */
+/** @name qof_book_merge API
+ @{
+*/
+/** \brief Initialise the qof_book_merge process
+
+	First function of the qof_book_merge API. Every merge must begin with Init.
+
+	Requires the book to import (::QofBook *) and the book to receive the import, the target book
+	(::QofBook *). Returns a pointer to ::qof_book_mergeData which must be checked for a
+	NULL before continuing. \n
+Process:
+
+ 	-# Invoke the callback ::qof_book_mergeForeachType on every registered object class definition. 
+	-# Callback obtains the registered parameter list for each object type. This provides run time 
+	access to all registered objects and all object parameters without any changes to
+	qof_book_merge - no registered object or parameter is omitted from any merge operation.
+	-# Use ::qof_object_foreach to invoke the callback ::qof_book_mergeForeach, one object at a time 
+	 on every instance stored in mergeBook. This is the first point where real data from the import 
+	 book is accessed.
+	-# qof_book_mergeForeach obtains the ::GUID for the object from the import book and runs the first
+	check on the original book, checking for any exact GUID match. With the full parameter list, 
+	the rules for this object can be created. If there is a GUID match, the data in each parameter 
+	of the import object is compared with the same semantic object in the original book. If there is
+	no GUID in the import object or no GUID match with the original book, the original book is 
+	searched to find a parameter match - checking for a ::MERGE_DUPLICATE result.
+	-# ::qof_book_mergeCompare sets the ::qof_book_mergeResult of the comparison.
+	-# Inserts the completed rule into qof_book_mergeData::mergeList GSList.
+
+\return NULL in case of error, otherwise a ::qof_book_mergeData* metadata context.
+
+*/
+qof_book_mergeData*
+qof_book_mergeInit( QofBook *importBook, QofBook *targetBook);
+
+
+/** \brief Definition of the dialog control callback routine
+
+All ::MERGE_REPORT rules must be offered for user intervention using this template.\n
+Commit will fail if any rules are still tagged as \a MERGE_REPORT.
+
+Calling processes are free to also offer MERGE_NEW, MERGE_UPDATE, MERGE_DUPLICATE and 
+MERGE_ABSOLUTE for user intervention. Attempting to query MERGE_INVALID rules
+will cause an error.
+
+For an example, consider test_rule_loop, declared as:
+
+<tt>void test_rule_loop(qof_book_mergeData *mergeData, qof_book_mergeRule *rule, guint remainder);\n
+void test_rule_loop(qof_book_mergeData *mergeData, qof_book_mergeRule *rule, guint remainder) \n
+{\n
+	g_return_if_fail(rule != NULL);\n
+	g_return_if_fail(mergeData != NULL);
+	printf("Rule Result %s", rule->mergeType);\n
+	qof_book_mergeUpdateResult(mergeData, rule, MERGE_UPDATE);\n
+}</tt>
+
+The dialog is free to call ::qof_book_mergeUpdateResult in the loop or at the end
+as long as the link between the rule and the result is maintained, e.g. by using a
+GHashTable. 
+\n
+The parameters are:
+	- data : pointer to the ::qof_book_mergeData metadata context returned by Init.
+	- rule : pointer to the ::qof_book_mergeRule that generated the collision report
+	- remainder : guint value returned from g_slist_length for the number of other
+		rules remaining with the same result. This might be useful for a progress dialog, it might not.
+		When updating MERGE_REPORT, remainder must equal zero before calling 
+		::qof_book_mergeCommit or the import will abort.
+\n
+
+If the dialog sets \b any rule result to ::MERGE_INVALID, the import will abort when
+::qof_book_mergeCommit is called. It is the responsibility of the calling function to
+handle the error code from ::qof_book_mergeCommit, close the dialog and return.
+The merge routines in these files will already have halted the merge operation and
+freed any memory allocated to merge structures before returning the error code.
+There is no need for the dialog process to report back to qof_book_merge in this situation.
+*/
+typedef void (* qof_book_mergeRuleForeachCB)( qof_book_mergeData*, qof_book_mergeRule*, guint);
+
+/** \brief Dialog Control Callback
+
+This function is designed to be used to iterate over all rules tagged with a specific
+::qof_book_mergeResult value.
+
+ at param	callback	external loop of type qof_book_mergeRuleForeachCB
+ at param	mergeResult	::qof_book_mergeResult value to look up.
+ at param	mergeData	::qof_book_mergeData merge context.
+
+\b Note : MERGE_NEW causes a new entity to be created in the target book at Commit
+which is then assigned as the targetEnt of that rule. If mergeResult == MERGE_NEW,
+the rules returned by qof_book_mergeRuleForeach will have a NULL set for the targetEnt.
+This is because Commit has not yet been called and no changes can be made to the target
+book. The calling process must handle the NULL targetEnt and NOT call any param_getfcn
+routines for the target entity. The import entity is available for display.
+
+Uses ::qof_book_get_collection with the qof_book_mergeRule::mergeType object type to
+return a collection of ::QofEntity entities from either the qof_book_mergeData::mergeBook or
+qof_book_mergeData::targetBook. Then uses ::qof_collection_lookup_entity to lookup 
+the qof_book_mergeRule::importEnt and again the qof_book_mergeRule::targetEnt to 
+return the two specific entities.
+
+*/
+void qof_book_mergeRuleForeach( qof_book_mergeData* mergeData,
+                                qof_book_mergeRuleForeachCB callback , 
+                                qof_book_mergeResult mergeResult);
+
+/** \brief provides easy string access to parameter data for dialog use
+
+Uses the param_getfcn to retrieve the parameter value as a string, suitable for
+display in dialogs and user intervention output. Within a qof_book_merge context,
+only the parameters used in the merge are available, i.e. parameters where both
+param_getfcn and param_setfcn are not NULL.
+
+Note that the object type description (a full text version of the object name) is
+also available to the dialog as qof_book_mergeRule::mergeLabel.
+
+This allows the dialog to display the description of the object and all parameter data.
+
+*/
+char* qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt);
+
+/** \brief called by dialog callback to set the result of user intervention
+
+Set \b any rule result to ::MERGE_INVALID to abort the import when
+::qof_book_mergeCommit is called, without changing the target book.
+
+The calling process should make it absolutely clear that a merge operation 
+\b cannot be undone and that a backup copy should always be available 
+\b before a merge is initialised.
+
+Recommended method: Only offer three options to the user per rule:
+
+-# Allow import data to be merged into target data
+	- change MERGE_REPORT to MERGE_UPDATE
+-# Allow import data without an exact match to be
+	added as new
+	- change MERGE_REPORT to MERGE_NEW \b IF mergeAbsolute = FALSE
+-# Ignore import data and leave target data unchanged
+	- change MERGE_REPORT to MERGE_ABSOLUTE or MERGE_DUPLICATE
+
+Handle the required result changes in code: Check the value of
+qof_book_mergeRule::mergeAbsolute and use these principles:
+
+To ignore entities tagged as:
+- MERGE_REPORT, you must check the value of mergeAbsolute.
+	- if mergeAbsolute is TRUE, change MERGE_REPORT to MERGE_ABSOLUTE
+	- if mergeAbsolute is FALSE, change MERGE_REPORT to MERGE_DUPLICATE
+- MERGE_NEW, set MERGE_DUPLICATE.
+- MERGE_UPDATE, you must check the value of mergeAbsolute.
+	- if mergeAbsolute is TRUE, change MERGE_UPDATE to MERGE_ABSOLUTE
+	- if mergeAbsolute is FALSE, change MERGE_UPDATE to MERGE_DUPLICATE
+
+To merge entities that are not pre-set to MERGE_NEW, set MERGE_UPDATE.\n
+Attempting to merge an entity when the pre-set value was MERGE_NEW will
+force a change back to MERGE_NEW because no suitable target exists for the
+merge.
+
+To add entities, check mergeAbsolute is FALSE and set MERGE_NEW.\n
+An entity \b only be added if mergeAbsolute is FALSE. Attempting to
+add an entity when mergeAbsolute is TRUE will always force a MERGE_UPDATE.
+
+It is not possible to update the same rule more than once.
+
+-# \b MERGE_NEW is reserved for new objects and is only pre-set if
+all parameters, including GUID, have already failed to match any 
+relevant object. ::qof_book_mergeCommit will create new 
+entities for all rules tagged as MERGE_NEW. 
+	- if mergeAbsolute is TRUE and the user wants to import the 
+		data, requests to set MERGE_NEW will be forced to MERGE_UPDATE 
+		because an entity with that GUID already exists in the target book.
+	- if MERGE_NEW is pre-set, requests to change to MERGE_UPDATE will be 
+		ignored because a new entity is needed.
+-# \b MERGE_UPDATE is reserved for existing objects - ::qof_book_mergeCommit 
+will require a matching entity to update and will force a change to back to 
+MERGE_NEW if none is known to exist, using the principle above.
+-# \b MERGE_INVALID will cause an abort of the merge process.
+-# \b MERGE_UNDEF and \b MERGE_REPORT cannot be set - the entity result will be unchanged.
+-# \b MERGE_DUPLICATE and \b MERGE_ABSOLUTE are handled identically but are semantically
+	different - qof_book_mergeRule::mergeAbsolute is used to dictate which to set:
+	- if mergeAbsolute is TRUE but MERGE_DUPLICATE is requested,
+		force a change to MERGE_ABSOLUTE.
+	- if mergeAbsolute is FALSE but MERGE_ABSOLUTE is requested,
+		force a change to MERGE_DUPLICATE.
+
+::qof_book_mergeCommit only commits entities tagged 
+with MERGE_NEW and MERGE_UPDATE results.
+\n
+Entities tagged with MERGE_ABSOLUTE and MERGE_DUPLICATE results are ignored.
+
+The calling process must check the return value and call
+::qof_book_merge_abort(mergeData) if non-zero.
+
+ at param	mergeData	the merge context, ::qof_book_mergeData*
+ at param	tag			the result to attempt to set, ::qof_book_mergeResult
+
+\return -1 if supplied parameters are invalid or NULL, 0 on success.
+		
+*/
+qof_book_mergeData*
+qof_book_mergeUpdateResult(qof_book_mergeData *mergeData, qof_book_mergeResult tag);
+
+
+/** \brief Commits the import data to the target book
+
+	The last function in the API and the final part of any qof_book_merge operation.
+
+qof_book_mergeCommit will abort the \b entire merge operation if any rule is set to
+::MERGE_INVALID. It is the responsibility of the calling 
+function to handle the error code from ::qof_book_mergeCommit, close the dialog
+and return. qof_book_mergeCommit will already have halted the merge 
+operation and freed any memory allocated to all merge structures before returning the error
+code. There is no way for the dialog process to report back to qof_book_merge in this situation.
+
+qof_book_mergeCommit checks for any entities still tagged as ::MERGE_REPORT and then proceeds
+to import all entities tagged as ::MERGE_UPDATE or ::MERGE_NEW into the target book.
+\n
+<b>This final process cannot be UNDONE!</b>\n
+\n
+
+ at param	mergeData	the merge context, ::qof_book_mergeData* 
+
+\return 
+	- -2 if any rules are tagged as ::MERGE_INVALID
+		- mergeData will have been g_free'd).
+		- note that this will be before any operations are done on the target
+			QofBook.
+	- -1 if mergeData is invalid or no merge has been initialised with
+		::qof_book_mergeInit - the calling process must check the value of mergeData
+	- +1 if some entities are still tagged as \a MERGE_REPORT - use 
+		::qof_book_mergeUpdateRule and try again (mergeData is retained).
+	- 0 on success - mergeData will have been freed.
+*/
+int
+qof_book_mergeCommit( qof_book_mergeData *mergeData );
+
+/** \brief Abort the merge and free all memory allocated by the merge
+
+Sometimes, setting ::MERGE_INVALID is insufficient: e.g. if the user aborts the
+merge from outside the functions dealing with the merge ruleset. This function
+causes an immediate abort - the calling process must start again at Init if 
+a new merge is required.
+*/
+void
+qof_book_merge_abort(qof_book_mergeData *mergeData);
+
+#endif // QOFBOOKMERGE_H
+/** @} */
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/qofbackend-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbackend-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbackend-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,410 @@
+/********************************************************************\
+ * qofbackend-p.h -- private api for data storage backend           *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup Object
+    @{ */
+/** @addtogroup Object_Private
+    Private interfaces, not meant to be used by applications.
+    @{ */
+/** @name  Backend_Private
+   Pseudo-object defining how the engine can interact with different
+   back-ends (which may be SQL databases, or network interfaces to 
+   remote GnuCash servers. File-io is just one type of backend).
+   
+   The callbacks will be called at the appropriate times during 
+   a book session to allow the backend to store the data as needed.
+
+   @file qofbackend-p.h
+   @brief private api for data storage backend
+   @author Copyright (c) 2000,2001,2004 Linas Vepstas <linas at linas.org> 
+   @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+@{ */
+
+#ifndef QOF_BACKEND_P_H
+#define QOF_BACKEND_P_H
+
+#include "config.h"
+#include "qof-be-utils.h"
+#include "qofbackend.h"
+#include "qofbook.h"
+#include "qofinstance-p.h"
+#include "qofquery.h"
+#include "qofsession.h"
+
+/**
+ * The session_begin() routine gives the backend a second initialization
+ *    opportunity.  It is suggested that the backend check that 
+ *    the URL is syntactically correct, and that it is actually
+ *    reachable.  This is probably(?) a good time to initialize
+ *    the actual network connection.
+ *
+ *    The 'ignore_lock' argument indicates whether the single-user
+ *    lock on the backend should be cleared.  The typical GUI sequence
+ *    leading to this is: (1) GUI attempts to open the backend
+ *    by calling this routine with FALSE==ignore_lock.  (2) If backend  
+ *    error'ed BACKEND_LOCK, then GUI asks user what to do. (3) if user 
+ *    answers 'break & enter' then this routine is called again with
+ *    TRUE==ignore_lock.
+ *
+ *    The 'create_if_nonexistent' argument indicates whether this
+ *    routine should create a new 'database', if it doesn't already
+ *    exist. For example, for a file-backend, this would create the
+ *    file, if it didn't already exist.  For an SQL backend, this
+ *    would create the database (the schema) if it didn't already 
+ *    exist.  This flag is used to implement the 'SaveAs' GUI, where
+ *    the user requests to save data to a new backend.
+ *
+ * The load() routine should load the minimal set of application data
+ *    needed for the application to be operable at initial startup.
+ *    It is assumed that the application will perform a 'run_query()'
+ *    to obtain any additional data that it needs.  For file-based
+ *    backends, it is acceptable for the backend to return all data
+ *    at load time; for SQL-based backends, it is acceptable for the
+ *    backend to return no data.
+ *
+ *    Thus, for example, for GnuCash, the postrges backend returns
+ *    the account tree, all currencies, and the pricedb, as these
+ *    are needed at startup.  It does not have to return any
+ *    transactions whatsoever, as these are obtained at a later stage
+ *    when a user opens a register, resulting in a query being sent to
+ *    the backend.
+ *
+ *    (Its OK to send over transactions at this point, but one should 
+ *    be careful of the network load; also, its possible that whatever 
+ *    is sent is not what the user wanted anyway, which is why its 
+ *    better to wait for the query).
+ *
+ * The begin() routine is called when the engine is about to
+ *    make a change to a data structure.  It can provide an advisory
+ *    lock on data.
+ *
+ * The commit() routine commits the changes from the engine to the
+ *    backend data storage.
+ *
+ * The rollback() routine is used to revert changes in the engine
+ *    and unlock the backend.  For transactions it is invoked in one
+ *    of two different ways.  In one case, the user may hit 'undo' in
+ *    the GUI, resulting in xaccTransRollback() being called, which in
+ *    turn calls this routine.  In this manner, xaccTransRollback()
+ *    implements a single-level undo convenience routine for the GUI.
+ *    The other way in which this routine gets invoked involves
+ *    conflicting edits by two users to the same transaction.  The
+ *    second user to make an edit will typically fail in
+ *    trans_commit_edit(), with trans_commit_edit() returning an error
+ *    code.  This causes xaccTransCommitEdit() to call
+ *    xaccTransRollback() which in turn calls this routine.  Thus,
+ *    this routine gives the backend a chance to clean up failed
+ *    commits.
+ *
+ *    If the second user tries to modify a transaction that
+ *    the first user deleted, then the backend should set the error
+ *    to ERR_BACKEND_MOD_DESTROY from this routine, so that the 
+ *    engine can properly clean up.
+ *
+ * The compile_query() method compiles a Gnucash query object into
+ *    a backend-specific data structure and returns the compiled
+ *    query.  For an SQL backend, the contents of the query object
+ *    need to be turned into a corresponding SQL query statement, and
+ *    sent to the database for evaluation.
+ *
+ * The free_query() method frees the data structure returned from 
+ *    compile_query()
+ *
+ * The run_query() callback takes a compiled query (generated by
+ *    compile_query) and runs the query in across the backend,
+ *    inserting the responses into the engine.  The database will
+ *    return a set of splits and transactions, and this callback needs
+ *    to poke these into the account-group hierarchy held by the query
+ *    object.
+ *
+ *    For a network-communications backend, essentially the same is 
+ *    done, except that this routine would convert the query to wire 
+ *    protocol, get an answer from the remote server, and push that
+ *    into the account-group object.
+ *
+ *    Note a peculiar design decision we've used here. The query
+ *    callback has returned a list of splits; these could be returned
+ *    directly to the caller. They are not.  By poking them into the
+ *    existing account hierarchy, we are essentially building a local
+ *    cache of the split data.  This will allow the GnuCash client to 
+ *    continue functioning even when disconnected from the server:
+ *    this is because it will have its local cache of data to work from.
+ *
+ * The sync() routine synchronizes the engine contents to the backend.
+ *    This is done by using version numbers (hack alert -- the engine
+ *    does not currently contain version numbers).
+ *    If the engine contents are newer than what's in the backend, the 
+ *    data is stored to the backend.  If the engine contents are older,
+ *    then the engine contents are updated.  
+ *
+ *    Note that this sync operation is only meant to apply to the 
+ *    current contents of the engine.  This routine is not intended
+ *    to be used to fetch account/transaction data from the backend.
+ *    (It might pull new splits from the backend, if this is what is
+ *    needed to update an existing transaction.  It might pull new 
+ *    currencies (??))
+ *
+ * The counter() routine increments the named counter and returns the
+ *    post-incremented value.  Returns -1 if there is a problem.
+ *
+ * The events_pending() routines should return true if there are
+ *    external events which need to be processed to bring the
+ *    engine up to date with the backend.
+ *
+ * The process_events() routine should process any events indicated
+ *    by the events_pending() routine. It should return TRUE if
+ *    the engine was changed while engine events were suspended.
+ *
+ * The last_err member indicates the last error that occurred.
+ *    It should probably be implemented as an array (actually,
+ *    a stack) of all the errors that have occurred.
+ *
+ * For support of book partitioning, use special "Book"  begin_edit()
+ *    and commit_edit() QOF_ID types.
+ *
+ *    Call the book begin() at the begining of a book partitioning.  A
+ *    'partitioning' is the splitting off of a chunk of the current
+ *    book into a second book by means of a query.  Every transaction
+ *    in that query is to be moved ('transfered') to the second book
+ *    from the existing book.  The argument of this routine is a
+ *    pointer to the second book, where the results of the query
+ *    should go.
+ *
+ *    Cann the book commit() to complete the book partitioning.
+ *
+ *    After the begin(), there will be a call to run_query(), followed
+ *    probably by a string of account and transaction calls, and
+ *    completed by commit().  It should be explicitly understood that
+ *    the results of that run_query() precisely constitute the set of
+ *    transactions that are to be moved between the initial and the
+ *    new book. This specification can be used by a clever backend to
+ *    avoid excess data movement between the server and the gnucash
+ *    client, as explained below.
+ *
+ *    There are several possible ways in which a backend may choose to
+ *    implement the book splitting process.  A 'file-type' backend may
+ *    choose to ignore this call, and the subsequent query, and simply
+ *    write out the new book to a file when the commit() call is made.
+ *    By that point, the engine will have performed all of the
+ *    nitty-gritty of moving transactions from one book to the other.
+ * 
+ *    A 'database-type' backend has several interesting choices.  One
+ *    simple choice is to simply perform the run_query() as it
+ *    normally would, and likewise treat the account and transaction
+ *    edits as usual.  In this scenario, the commit() is more or less
+ *    a no-op.  This implementation has a drawback, however: the
+ *    run_query() may cause the transfer of a *huge* amount of data
+ *    between the backend and the engine.  For a large dataset, this
+ *    is quite undesirable.  In addition, there are risks associated
+ *    with the loss of network connectivity during the transfer; thus
+ *    a partition might terminate half-finished, in some indeterminate
+ *    state, due to network errors.  That might be difficult to
+ *    recover from: the engine does not take any special transactional
+ *    safety measures during the transfer.
+ *
+ *    Thus, for a large database, an alternate implementation 
+ *    might be to use the run_query() call as an opportunity to 
+ *    transfer transactions between the two books in the database,
+ *    and not actually return any new data to the engine.  In
+ *    this scenario, the engine will attempt to transfer those 
+ *    transactions that it does know about.  It does not, however,
+ *    need to know about all the other transactions that also would  
+ *    be transfered over.  In this way, a backend could perform
+ *    a mass transfer of transactions between books without having
+ *    to actually move much (or any) data to the engine.
+ *
+ *
+ * To support configuration options from the frontend, the backend
+ *    can be passed a GHashTable - according to the allowed options
+ *    for that backend, using load_config(). Configuration can be
+ *    updated at any point - it is up to the frontend to load the
+ *    data in time for whatever the backend needs to do. e.g. an
+ *    option to save a new book in a compressed format need not be
+ *    loaded until the backend is about to save. If the configuration
+ *    is updated by the user, the frontend should call load_config
+ *    again to update the backend.
+ */
+
+struct QofBackendProvider_s
+{
+  /** Some arbitrary name given for this particular backend provider */
+  const char * provider_name;
+
+  /** The access method that this provider provides, for example,
+   *  http:// or postgres:// or rpc://, but without the :// at the end
+   */
+  const char * access_method;
+
+  /** \brief Partial QofBook handler
+	
+	TRUE if the backend handles external references
+	to entities outside this book and can save a QofBook that
+	does not contain any specific QOF objects.
+	*/
+  gboolean partial_book_supported;
+	
+  /** Return a new, initialized backend backend. */
+  QofBackend * (*backend_new) (void);
+
+/** \brief Distinguish two providers with same access method.
+  
+  More than 1 backend can be registered under the same access_method,
+  so each one is passed the path to the data (e.g. a file) and
+  should return TRUE only:
+-# if the backend recognises the type as one that it can load and write or 
+-# if the path contains no data but can be used (e.g. a new session).
+  
+  \note If the backend can cope with more than one type, the backend
+  should not try to store or cache the sub-type for this data.
+  It is sufficient only to return TRUE if any ONE of the supported
+  types match the incoming data. The backend should not assume that
+  returning TRUE will mean that the data will naturally follow.
+  */
+  gboolean (*check_data_type) (const char*);
+  
+  /** Free this structure, unregister this backend handler. */
+  void (*provider_free) (QofBackendProvider *);
+};
+
+struct QofBackend_s
+{
+  void (*session_begin) (QofBackend *be,
+                         QofSession *session,
+                         const char *book_id, 
+                         gboolean ignore_lock,
+                         gboolean create_if_nonexistent);
+  void (*session_end) (QofBackend *);
+  void (*destroy_backend) (QofBackend *);
+
+  void (*load) (QofBackend *, QofBook *);
+
+  void (*begin) (QofBackend *, QofInstance *);
+  void (*commit) (QofBackend *, QofInstance *);
+  void (*rollback) (QofBackend *, QofInstance *);
+
+  gpointer (*compile_query) (QofBackend *, QofQuery *);
+  void (*free_query) (QofBackend *, gpointer);
+  void (*run_query) (QofBackend *, gpointer);
+
+  void (*sync) (QofBackend *, QofBook *);
+  void (*load_config) (QofBackend *, KvpFrame *);
+  KvpFrame* (*get_config) (QofBackend *);
+  gint64 (*counter) (QofBackend *, const char *counter_name);
+
+  gboolean (*events_pending) (QofBackend *);
+  gboolean (*process_events) (QofBackend *);
+
+  QofBePercentageFunc percentage;
+  
+  QofBackendProvider *provider;
+
+  /** Document Me !!! what is this supposed to do ?? */
+  gboolean (*save_may_clobber_data) (QofBackend *);
+
+  QofBackendError last_err;
+  char * error_msg;
+
+  KvpFrame* backend_configuration;
+  gint config_count;
+  /** Each backend resolves a fully-qualified file path.
+   * This holds the filepath and communicates it to the frontends.
+   */
+  char * fullpath;
+
+#ifdef GNUCASH_MAJOR_VERSION
+  /** XXX price_lookup should be removed during the redesign
+   * of the SQL backend... prices can now be queried using
+   * the generic query mechanism.
+   *
+   * Note the correct signature for this call is 
+   * void (*price_lookup) (QofBackend *, GNCPriceLookup *);
+   * we use gpointer to avoid an unwanted include file dependency. 
+   */
+  void (*price_lookup) (QofBackend *, gpointer);
+
+  /** XXX Export should really _NOT_ be here, but is left here for now.
+   * I'm not sure where this should be going to. It should be
+   * removed ASAP.   This is a temporary hack-around until period-closing
+   * is fully implemented.
+   */
+  void (*export) (QofBackend *, QofBook *);
+#endif
+
+};
+
+/** Let the sytem know about a new provider of backends.  This function
+ *  is typically called by the provider library at library load time.
+ *  This function allows the backend library to tell the QOF infrastructure
+ *  that it can handle URL's of a certain type.  Note that a single
+ *  backend library may register more than one provider, if it is
+ *  capable of handling more than one URL access method.
+ */
+void qof_backend_register_provider (QofBackendProvider *);
+
+/** The qof_backend_set_error() routine pushes an error code onto the error
+ *  stack. (FIXME: the stack is 1 deep in current implementation).
+ */
+void qof_backend_set_error (QofBackend *be, QofBackendError err);
+
+/** The qof_backend_get_error() routine pops an error code off the error stack.
+ */
+QofBackendError qof_backend_get_error (QofBackend *be);
+
+/** The qof_backend_set_message() assigns a string to the backend error message.
+ */
+void qof_backend_set_message(QofBackend *be, const char *format, ...);
+
+/** The qof_backend_get_message() pops the error message string from
+ *  the Backend.  This string should be freed with g_free().
+ */
+char * qof_backend_get_message(QofBackend *be);
+
+void qof_backend_init(QofBackend *be);
+
+/** Allow backends to see if the book is open 
+
+ at return 'y' if book is open, otherwise 'n'.
+*/
+gchar qof_book_get_open_marker(QofBook *book);
+
+/** get the book version
+
+used for tracking multiuser updates in backends.
+
+ at return -1 if no book exists, 0 if the book is
+new, otherwise the book version number.
+*/
+gint32 qof_book_get_version (QofBook *book);
+
+/** get the book tag number
+
+used for kvp management in sql backends.
+*/
+guint32 qof_book_get_idata (QofBook *book);
+
+void qof_book_set_version (QofBook *book, gint32 version);
+
+void qof_book_set_idata(QofBook *book, guint32 idata);
+
+/* @} */
+/* @} */
+/* @} */
+#endif /* QOF_BACKEND_P_H */

Added: gnucash/trunk/lib/libqof/qof/qofbackend.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbackend.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbackend.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,427 @@
+/********************************************************************\
+ * qofbackend.c -- utility routines for dealing with the data backend  *
+ * Copyright (C) 2000 Linas Vepstas <linas at linas.org>               *
+ * Copyright (C) 2004-5 Neil Williams <linux at codehelp.co.uk>        *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#define _GNU_SOURCE
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <regex.h>
+#include <glib.h>
+#include <gmodule.h>
+#include <dlfcn.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include "qofbackend-p.h"
+
+static QofLogModule log_module = QOF_MOD_BACKEND;
+
+#define QOF_CONFIG_DESC    "desc"
+#define QOF_CONFIG_TIP     "tip"
+
+/********************************************************************\
+ * error handling                                                   *
+\********************************************************************/
+
+void 
+qof_backend_set_error (QofBackend *be, QofBackendError err)
+{
+   if (!be) return;
+
+   /* use stack-push semantics. Only the earliest error counts */
+   if (ERR_BACKEND_NO_ERR != be->last_err) return;
+   be->last_err = err;
+}
+
+QofBackendError 
+qof_backend_get_error (QofBackend *be)
+{
+   QofBackendError err;
+   if (!be) return ERR_BACKEND_NO_BACKEND;
+
+   /* use 'stack-pop' semantics */
+   err = be->last_err;
+   be->last_err = ERR_BACKEND_NO_ERR;
+   return err;
+}
+
+void
+qof_backend_set_message (QofBackend *be, const char *format, ...) 
+{
+   va_list args;
+   char * buffer;
+   
+   if (!be) return;
+  
+   /* If there's already something here, free it */
+   if (be->error_msg) g_free(be->error_msg);
+
+   if (!format) {
+       be->error_msg = NULL;
+       return;
+   }
+
+   va_start(args, format);
+   buffer = (char *)g_strdup_vprintf(format, args);
+   va_end(args);
+
+   be->error_msg = buffer;
+}
+
+/* This should always return a valid char * */
+char *
+qof_backend_get_message (QofBackend *be) 
+{
+   char * msg;
+   
+   if (!be) return g_strdup("ERR_BACKEND_NO_BACKEND");
+   if (!be->error_msg) return NULL;
+
+   /* 
+    * Just return the contents of the error_msg and then set it to
+    * NULL.  This is necessary, because the Backends don't seem to
+    * have a destroy_backend function to take care if freeing stuff
+    * up.  The calling function should free the copy.
+    * Also, this is consistent with the qof_backend_get_error() popping.
+    */
+
+   msg = be->error_msg;
+   be->error_msg = NULL;
+   return msg;
+}
+
+/***********************************************************************/
+/* Get a clean backend */
+void
+qof_backend_init(QofBackend *be)
+{
+    be->session_begin = NULL;
+    be->session_end = NULL;
+    be->destroy_backend = NULL;
+
+    be->load = NULL;
+
+    be->begin = NULL;
+    be->commit = NULL;
+    be->rollback = NULL;
+
+    be->compile_query = NULL;
+    be->free_query = NULL;
+    be->run_query = NULL;
+
+    be->sync = NULL;
+	be->load_config = NULL;
+
+    be->events_pending = NULL;
+    be->process_events = NULL;
+
+    be->last_err = ERR_BACKEND_NO_ERR;
+    if (be->error_msg) g_free (be->error_msg);
+    be->error_msg = NULL;
+    be->percentage = NULL;
+	be->backend_configuration = kvp_frame_new();
+
+#ifdef GNUCASH_MAJOR_VERSION
+    /* XXX remove these */
+    be->fullpath = NULL;
+    be->price_lookup = NULL;
+    be->export = NULL;
+#endif
+}
+
+void
+qof_backend_run_begin(QofBackend *be, QofInstance *inst)
+{
+	if(!be || !inst) { return; }
+	if(!be->begin) { return; }
+	(be->begin) (be, inst);
+}
+
+gboolean
+qof_backend_begin_exists(QofBackend *be)
+{
+	if(be->begin) { return TRUE; }
+	else { return FALSE; }
+}
+
+void
+qof_backend_run_commit(QofBackend *be, QofInstance *inst)
+{
+	if(!be || !inst) { return; }
+	if(!be->commit) { return; }
+	(be->commit) (be, inst);
+}
+
+/* =========== Backend Configuration ================ */
+
+void qof_backend_prepare_frame(QofBackend *be)
+{
+	g_return_if_fail(be);
+	if(!kvp_frame_is_empty(be->backend_configuration)) {
+		kvp_frame_delete(be->backend_configuration);
+		be->backend_configuration = kvp_frame_new();
+	}
+	be->config_count = 0;
+}
+
+void qof_backend_prepare_option(QofBackend *be, QofBackendOption *option)
+{
+	KvpValue *value;
+	gchar *temp;
+	gint count;
+
+	g_return_if_fail(be || option);
+	count = be->config_count;
+	count++;
+	value = NULL;
+	ENTER (" %d", count);
+	switch (option->type)
+	{
+		case KVP_TYPE_GINT64   : {
+			value = kvp_value_new_gint64(*(gint64*)option->value);
+			break; 
+		}
+		case KVP_TYPE_DOUBLE   : { 
+			value = kvp_value_new_double(*(double*)option->value);
+			break; 
+		}
+		case KVP_TYPE_NUMERIC  : {
+			value = kvp_value_new_numeric(*(gnc_numeric*)option->value);
+			break; 
+		}
+		case KVP_TYPE_STRING   : {
+			value = kvp_value_new_string((const char*)option->value);
+			break;
+		}
+		case KVP_TYPE_GUID     : { break; } /* unsupported */
+		case KVP_TYPE_TIMESPEC : {
+			value = kvp_value_new_timespec(*(Timespec*)option->value);
+			break;
+		}
+		case KVP_TYPE_BINARY   : { break; } /* unsupported */
+		case KVP_TYPE_GLIST    : { break; } /* unsupported */
+		case KVP_TYPE_FRAME    : { break; } /* unsupported */
+	}
+	if(value) {
+		temp = g_strdup_printf("/%s", option->option_name);
+		kvp_frame_set_value(be->backend_configuration, temp, value);
+		PINFO (" setting value at %s", temp);
+		g_free(temp);
+		temp = g_strdup_printf("/%s/%s", QOF_CONFIG_DESC, option->option_name);
+		PINFO (" setting description %s at %s", option->description, temp);
+		kvp_frame_set_string(be->backend_configuration, temp, option->description);
+		PINFO (" check= %s", kvp_frame_get_string(be->backend_configuration, temp));
+		g_free(temp);
+		temp = g_strdup_printf("/%s/%s", QOF_CONFIG_TIP, option->option_name);
+		PINFO (" setting tooltip %s at %s", option->tooltip, temp);
+		kvp_frame_set_string(be->backend_configuration, temp, option->tooltip);
+		PINFO (" check= %s", kvp_frame_get_string(be->backend_configuration, temp));
+		g_free(temp);
+		/* only increment the counter if successful */
+		be->config_count = count;
+	}
+	LEAVE (" ");
+}
+
+KvpFrame* qof_backend_complete_frame(QofBackend *be)
+{
+	g_return_val_if_fail(be, NULL);
+	be->config_count = 0;
+	return be->backend_configuration;
+}
+
+struct config_iterate {
+	QofBackendOptionCB fcn;
+	gpointer           data;
+	gint               count;
+	KvpFrame          *recursive;
+};
+
+static void
+config_foreach_cb (const char *key, KvpValue *value, gpointer data)
+{
+	QofBackendOption option;
+	gint64 int64;
+	double db;
+	gnc_numeric num;
+	Timespec ts;
+	gchar *parent;
+	struct config_iterate *helper;
+
+	g_return_if_fail(key || value || data);
+	helper = (struct config_iterate*)data;
+	if(!helper->recursive) { PERR (" no parent frame"); return;	}
+	// skip the presets.
+	if(0 == safe_strcmp(key, QOF_CONFIG_DESC)) { return; }
+	if(0 == safe_strcmp(key, QOF_CONFIG_TIP)) { return; }
+	ENTER (" key=%s", key);
+	option.option_name = key;
+	option.type = kvp_value_get_type(value);
+	if(!option.type) { return; }
+	switch (option.type)
+	{
+		case KVP_TYPE_GINT64   : {
+			int64 = kvp_value_get_gint64(value);
+			option.value = (gpointer)&int64;
+			break; 
+		}
+		case KVP_TYPE_DOUBLE   : {
+			db = kvp_value_get_double(value);
+			option.value = (gpointer)&db;
+			break; 
+		}
+		case KVP_TYPE_NUMERIC  : {
+			num = kvp_value_get_numeric(value);
+			option.value = (gpointer)&num;
+			break; 
+		}
+		case KVP_TYPE_STRING   : {
+			option.value = (gpointer)kvp_value_get_string(value);
+			break;
+		}
+		case KVP_TYPE_GUID     : { break; } /* unsupported */
+		case KVP_TYPE_TIMESPEC : {
+			ts = kvp_value_get_timespec(value);
+			option.value = (gpointer)&ts;
+			break;
+		}
+		case KVP_TYPE_BINARY   : { break; } /* unsupported */
+		case KVP_TYPE_GLIST    : { break; } /* unsupported */
+		case KVP_TYPE_FRAME    : { break; } /* unsupported */
+	}
+	parent = g_strdup_printf("/%s/%s", QOF_CONFIG_DESC, key);
+	option.description = kvp_frame_get_string(helper->recursive, parent);
+	g_free(parent);
+	parent = g_strdup_printf("/%s/%s", QOF_CONFIG_TIP, key);
+	option.tooltip = kvp_frame_get_string(helper->recursive, parent);
+	helper->count++;
+	helper->fcn (&option, helper->data);
+	LEAVE (" desc=%s tip=%s", option.description, option.tooltip);
+}
+
+void qof_backend_option_foreach(KvpFrame *config, QofBackendOptionCB cb, gpointer data)
+{
+	struct config_iterate helper;
+
+	if(!config || !cb) { return; }
+	ENTER (" ");
+	helper.fcn = cb;
+	helper.count = 1;
+	helper.data = data;
+	helper.recursive = config;
+	kvp_frame_for_each_slot(config, config_foreach_cb, &helper);
+	LEAVE (" ");
+}
+
+void
+qof_backend_load_config(QofBackend *be, KvpFrame *config)
+{
+	if(!be || !config) { return; }
+	if(!be->load_config) { return; }
+	(be->load_config) (be, config);
+}
+
+KvpFrame*
+qof_backend_get_config(QofBackend *be)
+{
+	if(!be) { return NULL; }
+	if(!be->get_config) { return NULL; }
+	return (be->get_config) (be);
+}
+
+gboolean
+qof_backend_commit_exists(QofBackend *be)
+{
+	if(!be) { return FALSE; }
+	if(be->commit) { return TRUE; }
+	else { return FALSE; }
+}
+
+gboolean
+qof_begin_edit(QofInstance *inst)
+{
+  QofBackend * be;
+
+  if (!inst) { return FALSE; }
+  (inst->editlevel)++;
+  if (1 < inst->editlevel) { return FALSE; }
+  if (0 >= inst->editlevel) { inst->editlevel = 1; }
+  be = qof_book_get_backend (inst->book);
+    if (be && qof_backend_begin_exists(be)) {
+     qof_backend_run_begin(be, inst);
+  } else { inst->dirty = TRUE; }
+  return TRUE;
+}
+
+gboolean qof_commit_edit(QofInstance *inst)
+{
+  QofBackend * be;
+
+  if (!inst) { return FALSE; }
+  (inst->editlevel)--;
+  if (0 < inst->editlevel) { return FALSE; }
+  if ((-1 == inst->editlevel) && inst->dirty)
+  {
+    be = qof_book_get_backend ((inst)->book);
+    if (be && qof_backend_begin_exists(be)) {
+     qof_backend_run_begin(be, inst);
+    }
+    inst->editlevel = 0;
+  }
+  if (0 > inst->editlevel) { inst->editlevel = 0; }
+  return TRUE;
+}
+
+gboolean
+qof_load_backend_library (const char *directory, 
+				const char* filename, const char* init_fcn)
+{
+	struct stat sbuf;
+	gchar *fullpath;
+	typedef void (* backend_init) (void);
+	GModule *backend;
+	backend_init gmod_init;
+	gpointer g;
+
+	g_return_val_if_fail(g_module_supported(), FALSE);
+	fullpath = g_module_build_path(directory, filename);
+	PINFO (" fullpath=%s", fullpath);
+	g_return_val_if_fail((stat(fullpath, &sbuf) == 0), FALSE);
+	backend = g_module_open(fullpath, G_MODULE_BIND_LAZY);
+	if(!backend) { 
+		g_message ("%s: %s\n", PACKAGE, g_module_error ());
+		return FALSE;
+	}
+	g = &gmod_init;
+	if (!g_module_symbol (backend, init_fcn, g))
+	{
+		g_message ("%s: %s\n", PACKAGE, g_module_error ());
+		return FALSE;
+	}
+	g_module_make_resident(backend);
+	gmod_init();
+	return TRUE;
+}
+
+/************************* END OF FILE ********************************/

Added: gnucash/trunk/lib/libqof/qof/qofbackend.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbackend.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbackend.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,265 @@
+/********************************************************************\
+ * qofbackend.h: api for data storage backend                       *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup Backend
+
+    The QOF Backend is a pseudo-object providing an interface between the
+    engine and a persistant data store (e.g. a server, a database, or
+    a file).   Backends are not meant to be used directly by an
+    application; instead the Session should be used to make a 
+    connection with some particular backend.
+    There are no backend functions that are 'public' to
+    users of the engine.  The backend can, however, report errors to
+    the GUI & other front-end users.  This file defines these errors.
+   
+    Backends are used to save and restore Entities in a Book.
+    @{ 
+*/
+/** @file qofbackend.h
+    @brief API for data storage Backend
+    @author Copyright (C) 2000-2001 Linas Vepstas <linas at linas.org>
+    @author Copyright 2004-2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#ifndef QOF_BACKEND_H
+#define QOF_BACKEND_H
+
+#include "qofinstance.h"
+
+#define QOF_MOD_BACKEND "qof-backend"
+
+/** \brief The errors that can be reported to the GUI & other front-end users
+ *  \warning (GnuCash) If you modify QofBackendError, please update 
+ *   src/engine/gw-engine-spec.scm 
+*/
+typedef enum {
+  ERR_BACKEND_NO_ERR = 0,
+  ERR_BACKEND_NO_HANDLER,   /**< no backend handler found for this access method (ENOSYS) */
+  ERR_BACKEND_NO_BACKEND,   /**< Backend * pointer was unexpectedly null */
+  ERR_BACKEND_BAD_URL,      /**< Can't parse url */
+  ERR_BACKEND_NO_SUCH_DB,   /**< the named database doesn't exist */
+  ERR_BACKEND_CANT_CONNECT, /**< bad dbname/login/passwd or network failure */
+  ERR_BACKEND_CONN_LOST,    /**< Lost connection to server */
+  ERR_BACKEND_LOCKED,       /**< in use by another user (ETXTBSY) */
+  ERR_BACKEND_READONLY,	    /**< cannot write to file/directory */
+  ERR_BACKEND_TOO_NEW,      /**< file/db version newer than what we can read */
+  ERR_BACKEND_DATA_CORRUPT, /**< data in db is corrupt */
+  ERR_BACKEND_SERVER_ERR,   /**< error in response from server */
+  ERR_BACKEND_ALLOC,        /**< internal memory allocation failure */
+  ERR_BACKEND_PERM,         /**< user login successful, but no permissions 
+                               to access the desired object */
+  ERR_BACKEND_MODIFIED,     /**< commit of object update failed because 
+                               another user has modified the object */
+  ERR_BACKEND_MOD_DESTROY,  /**< commit of object update failed because 
+                               another user has deleted the object */
+  ERR_BACKEND_MISC,         /**< undetermined error */
+
+  /* QSF add-ons */
+  ERR_QSF_INVALID_OBJ, 		/**< The QSF object failed to validate against the QSF object schema */
+  ERR_QSF_INVALID_MAP, 		/**< The QSF map failed to validate against the QSF map schema */
+  ERR_QSF_BAD_OBJ_GUID,		/**< The QSF object contains one or more invalid GUIDs. */
+  ERR_QSF_BAD_QOF_VERSION,	/**< QSF map or object doesn't match the current QOF_OBJECT_VERSION. */
+  ERR_QSF_BAD_MAP,			/**< The selected map validates but is unusable.
+  
+  This is usually because not all the required parameters for the defined objects 
+  have calculations described in the map.
+  */
+  ERR_QSF_NO_MAP,		/**< The QSF object file was loaded without a map
+  
+  The QSF Object file requires a map but it was not provided.
+  */
+  ERR_QSF_WRONG_MAP,		/**< The selected map validates but is for different objects.
+  
+  The list of objects defined in this map does not include all the objects described in
+  the current QSF object file.
+  */
+  ERR_QSF_MAP_NOT_OBJ,		/**< Selected file is a QSF map and cannot be opened as a QSF object */
+  ERR_QSF_OVERFLOW,			/**< EOVERFLOW - generated by strtol or strtoll.
+
+  When converting XML strings into numbers, an overflow has been detected. The XML file
+  contains invalid data in a field that is meant to hold a signed long integer or signed long long
+  integer.
+  */
+  ERR_QSF_OPEN_NOT_MERGE,	/** QSF files cannot be opened alone. The data must be merged.
+
+  This error is more of a warning that can be ignored by any routine
+  that uses qof_book_merge on the new session.
+  */
+  /* fileio errors */
+  ERR_FILEIO_FILE_BAD_READ = 1000,  /**< read failed or file prematurely truncated */
+  ERR_FILEIO_FILE_EMPTY,     /**< file exists, is readable, but is empty */
+  ERR_FILEIO_FILE_LOCKERR,   /**< mangled locks (unspecified error) */
+  ERR_FILEIO_FILE_NOT_FOUND, /**< not found / no such file */
+  ERR_FILEIO_FILE_TOO_OLD,   /**< file version so old we can't read it */
+  ERR_FILEIO_UNKNOWN_FILE_TYPE, /**< didn't recognize the file type */
+  ERR_FILEIO_PARSE_ERROR,    /**< couldn't parse the data in the file */
+  ERR_FILEIO_BACKUP_ERROR,   /**< couldn't make a backup of the file */
+  ERR_FILEIO_WRITE_ERROR,    /**< couldn't write to the file */
+
+  /* network errors */
+  ERR_NETIO_SHORT_READ = 2000,  /**< not enough bytes received */
+  ERR_NETIO_WRONG_CONTENT_TYPE, /**< wrong kind of server, wrong data served */
+  ERR_NETIO_NOT_GNCXML,         /**< whatever it is, we can't parse it. */
+ 
+  /* database errors */
+  ERR_SQL_MISSING_DATA = 3000,  /**< database doesn't contain expected data */
+  ERR_SQL_DB_TOO_OLD,           /**< database is old and needs upgrading */
+  ERR_SQL_DB_BUSY,              /**< database is busy, cannot upgrade version */
+  
+  /* RPC errors */
+  ERR_RPC_HOST_UNK = 4000,      /**< Host unknown */
+  ERR_RPC_CANT_BIND,            /**< can't bind to address */
+  ERR_RPC_CANT_ACCEPT,          /**< can't accept connection */
+  ERR_RPC_NO_CONNECTION,        /**< no connection to server */
+  ERR_RPC_BAD_VERSION,          /**< RPC Version Mismatch */
+  ERR_RPC_FAILED,               /**< Operation failed */
+  ERR_RPC_NOT_ADDED,            /**< object not added */
+} QofBackendError;
+
+/**
+ * A structure that declares backend services that can be gotten.
+ * The Provider specifies a URL access method, and specifies the
+ * function to create a backend that can handle that URL access
+ * function.
+ */
+typedef struct QofBackendProvider_s QofBackendProvider;
+
+/** \brief Pseudo-object providing an interface between the
+ * engine and a persistant data store (e.g. a server, a database, 
+ * or a file).  
+ *
+ * There are no backend functions that are 'public' to users of the
+ * engine.  The backend can, however, report errors to the GUI & other
+ * front-end users.  
+ */
+typedef struct QofBackend_s QofBackend;
+
+/** \brief DOCUMENT ME! */
+typedef void (*QofBePercentageFunc) (const char *message, double percent);
+
+/** @name Allow access to the begin routine for this backend.
+
+QOF_BEGIN_EDIT and QOF_COMMIT_EDIT_PART1 and part2 rely on 
+calling QofBackend *be->begin and be->commit. This means the
+QofBackend struct becomes part of the public API.
+These function replaces those calls to allow the macros to be
+used when QOF is built as a library. */
+//@{
+
+void qof_backend_run_begin(QofBackend *be, QofInstance *inst);
+
+gboolean qof_backend_begin_exists(QofBackend *be);
+
+void qof_backend_run_commit(QofBackend *be, QofInstance *inst);
+
+gboolean qof_backend_commit_exists(QofBackend *be);
+//@}
+
+/** @name Backend Configuration using KVP
+
+The backend uses qof_backend_get_config to pass back a KvpFrame of QofBackendOption
+that includes the \b translated strings that serve as description and
+tooltip for that option. i.e. backends need to run gettext in the init function.
+
+qof_backend_prepare_frame, qof_backend_prepare_option and qof_backend_complete_frame
+are intended to be used by the backend itself to create the options.
+
+qof_backend_get_config, qof_backend_option_foreach and qof_backend_load_config
+are intended for either the backend or the frontend to retrieve the option data
+from the frame or set new data.
+
+@{
+*/
+
+/** A single Backend Configuration Option. */
+typedef struct QofBackendOption_s {
+	KvpValueType type;        /**< Only GINT64, DOUBLE, NUMERIC, STRING and TIMESPEC supported. */
+	const char *option_name;  /**< non-translated, key. */
+	const char *description;  /**< translatable description. */
+	const char *tooltip;      /**< translatable tooltip */
+	gpointer value;           /**< The value of the option. */
+}QofBackendOption;
+
+/** Initialise the backend_configuration */
+void qof_backend_prepare_frame(QofBackend *be);
+
+/** Add an option to the backend_configuration. Repeat for more. */
+void qof_backend_prepare_option(QofBackend *be, QofBackendOption *option);
+
+/** Complete the backend_configuration and return the frame. */
+KvpFrame* qof_backend_complete_frame(QofBackend *be);
+
+/** Backend configuration option foreach callback prototype. */
+typedef void (*QofBackendOptionCB)(QofBackendOption*, gpointer data);
+
+/** Iterate over the frame and process each option. */
+void qof_backend_option_foreach(KvpFrame *config, QofBackendOptionCB cb, gpointer data);
+
+/** \brief Load configuration options specific to this backend.
+
+ at param be The backend to configure.
+ at param config A KvpFrame of QofBackendOptions that this backend
+will recognise. Each backend needs to document their own config
+types and acceptable values.
+
+*/
+void qof_backend_load_config (QofBackend *be, KvpFrame *config);
+
+/** \brief Get the available configuration options
+
+To retrieve the options from the returned KvpFrame, the caller
+needs to parse the XML file that documents the option names and
+data types. The XML file itself is part of the backend and is
+installed in a directory determined by the backend. Therefore,
+loading a new backend requires two paths: the path to the .la file
+and the path to the xml. Both paths are available by including a
+generated header file, e.g. gncla-dir.h defines GNC_LIB_DIR for
+the location of the .la file and GNC_XML_DIR for the xml.
+
+ at param be The QofBackend to be configured.
+
+ at return A new KvpFrame containing the available options or
+NULL on failure.
+
+*/
+KvpFrame* qof_backend_get_config(QofBackend *be);
+//@}
+
+/** \brief Load a QOF-compatible backend shared library.
+
+\param directory Can be NULL if filename is a complete path.
+\param filename  Name of the .la file that describes the
+	shared library. This provides platform independence,
+	courtesy of libtool.
+\param init_fcn  The QofBackendProvider init function.
+
+\return FALSE in case or error, otherwise TRUE.
+*/
+gboolean
+qof_load_backend_library (const char *directory, 
+			const char* filename, const char* init_fcn);
+
+/** \brief Retrieve the backend used by this book */
+QofBackend* qof_book_get_backend (QofBook *book);
+
+void qof_book_set_backend (QofBook *book, QofBackend *);
+
+#endif /* QOF_BACKEND_H */
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/qofbook-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbook-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbook-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,114 @@
+/********************************************************************\
+ * qof-book-p.h -- private functions for QOF books.                 *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup Object
+    @{ */
+/** @addtogroup Object_Private
+    Private interfaces, not meant to be used by applications.
+    @{ */
+/** @name  Book_Private
+    @{ */
+/*
+ * HISTORY:
+ * Created 2001 by Rob Browning
+ * Copyright (c) 2001 Rob Browning
+ * Copyright (c) 2001,2003 Linas Vepstas <linas at linas.org>
+ */
+
+#ifndef QOF_BOOK_P_H
+#define QOF_BOOK_P_H
+
+#include "kvp_frame.h"
+#include "qofbackend.h"
+#include "qofbook.h"
+#include "qofid.h"
+#include "qofid-p.h"
+#include "qofinstance-p.h"
+
+/** Book structure */
+struct _QofBook
+{
+  QofInstance   inst;     /**< Unique guid for this book. */
+
+  /** The entity table associates the GUIDs of all the objects
+   * belonging to this book, with their pointers to the respective
+   * objects.  This allows a lookup of objects based on thier guid.
+   */
+  GHashTable * hash_of_collections;
+
+  /** In order to store arbitrary data, for extensibility, add a table
+   * that will be used to hold arbitrary pointers.
+   */
+  GHashTable *data_tables;
+
+  /** Hash table of destroy callbacks for the data table. */
+  GHashTable *data_table_finalizers;
+
+  /** state flag: 'y' means 'open for editing', 
+   * 'n' means 'book is closed'  
+   * xxxxx shouldn't this be replaced by the instance editlevel ??? 
+   */
+  char book_open;
+
+  /** a flag denoting whether the book is closing down, used to
+   * help the QOF objects shut down cleanly without maintaining
+   * internal consistency.
+   * XXX shouldn't this be replaced by instance->do_free ??? 
+   */
+  gboolean shutting_down;
+  
+  /** version number, used for tracking multiuser updates */
+  gint32  version;
+
+  /** To be technically correct, backends belong to sessions and
+   * not books.  So the pointer below "really shouldn't be here", 
+   * except that it provides a nice convenience, avoiding a lookup 
+   * from the session.  Better solutions welcome ... */ 
+  QofBackend *backend;
+
+  /* -------------------------------------------------------------- */
+  /** Backend private expansion data */
+  guint32  idata;     /**< used by the sql backend for kvp management */
+};
+
+/**
+ * These qof_book_set_*() routines are used by backends to 
+ *    initialize the pointers in the book structure to 
+ *    something that contains actual data.  These routines 
+ *    should not be used otherwise.  (Its somewhat questionable
+ *    if the backends should even be doing this much, but for
+ *    backwards compatibility, we leave these here.)
+ */
+void qof_book_set_schedxactions( QofBook *book, GList *newList );
+
+void qof_book_set_backend (QofBook *book, QofBackend *be);
+
+/** Register books with the engine */
+gboolean qof_book_register (void);
+
+/** @deprecated */
+#define qof_book_set_guid(book,guid)    \
+         qof_entity_set_guid(QOF_ENTITY(book), guid)
+
+/* @} */
+/* @} */
+/* @} */
+#endif /* QOF_BOOK_P_H */

Added: gnucash/trunk/lib/libqof/qof/qofbook.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbook.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbook.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,385 @@
+/********************************************************************\
+ * qofbook.c -- dataset access (set of accounting books)            *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+/*
+ * FILE:
+ * qofbook.c
+ *
+ * FUNCTION:
+ * Encapsulate all the information about a gnucash dataset.
+ * See src/doc/books.txt for design overview.
+ *
+ * HISTORY:
+ * Created by Linas Vepstas December 1998
+ * Copyright (c) 1998-2001,2003 Linas Vepstas <linas at linas.org>
+ * Copyright (c) 2000 Dave Peticolas
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "gnc-event.h"
+#include "gnc-event-p.h"
+#include "gnc-trace.h"
+#include "qofbackend-p.h"
+#include "qofbook.h"
+#include "qofbook-p.h"
+#include "qofclass.h"
+#include "qofid-p.h"
+#include "qofobject-p.h"
+#include "gnc-engine-util.h"
+
+#include "guid.h"
+
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+/* ====================================================================== */
+/* constructor / destructor */
+
+static void coll_destroy(gpointer col)
+{
+  qof_collection_destroy((QofCollection *) col);
+}
+
+static void
+qof_book_init (QofBook *book)
+{
+  if (!book) return;
+
+  book->hash_of_collections = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                                    gnc_string_cache_remove,
+                                                    coll_destroy);
+
+  qof_instance_init (&book->inst, QOF_ID_BOOK, book);
+
+  book->data_tables = g_hash_table_new (g_str_hash, g_str_equal);
+  book->data_table_finalizers = g_hash_table_new (g_str_hash, g_str_equal);
+  
+  book->book_open = 'y';
+  book->version = 0;
+  book->idata = 0;
+}
+
+QofBook *
+qof_book_new (void)
+{
+  QofBook *book;
+
+  ENTER (" ");
+  book = g_new0(QofBook, 1);
+  qof_book_init(book);
+  qof_object_book_begin (book);
+
+  gnc_engine_gen_event (&book->inst.entity, GNC_EVENT_CREATE);
+  LEAVE ("book=%p", book);
+  return book;
+}
+
+static void
+book_final (gpointer key, gpointer value, gpointer booq)
+{
+  QofBookFinalCB cb = value;
+  QofBook *book = booq;
+
+  gpointer user_data = g_hash_table_lookup (book->data_tables, key);
+  (*cb) (book, key, user_data);
+}
+
+void
+qof_book_destroy (QofBook *book) 
+{
+  if (!book) return;
+  ENTER ("book=%p", book);
+
+  book->shutting_down = TRUE;
+  gnc_engine_force_event (&book->inst.entity, GNC_EVENT_DESTROY);
+
+  /* Call the list of finalizers, let them do thier thing. 
+   * Do this before tearing into the rest of the book.
+   */
+  g_hash_table_foreach (book->data_table_finalizers, book_final, book);
+
+  qof_object_book_end (book);
+
+  g_hash_table_destroy (book->data_table_finalizers);
+  g_hash_table_destroy (book->data_tables);
+
+  qof_instance_release (&book->inst);
+
+  g_hash_table_destroy (book->hash_of_collections);
+  book->hash_of_collections = NULL;
+
+  g_free (book);
+  LEAVE ("book=%p", book);
+}
+
+/* ====================================================================== */
+/* XXX this should probably be calling is_equal callbacks on gncObject */
+
+gboolean
+qof_book_equal (QofBook *book_1, QofBook *book_2)
+{
+  if (book_1 == book_2) return TRUE;
+  if (!book_1 || !book_2) return FALSE;
+  return TRUE;
+}
+
+/* ====================================================================== */
+
+gboolean
+qof_book_not_saved(QofBook *book)
+{
+  if (!book) return FALSE;
+
+  return(book->inst.dirty || qof_object_is_dirty (book));
+}
+
+void
+qof_book_mark_saved(QofBook *book)
+{
+  if (!book) return;
+
+  book->inst.dirty = FALSE;
+  qof_object_mark_clean (book);
+}
+
+/* ====================================================================== */
+/* getters */
+
+QofBackend * 
+qof_book_get_backend (QofBook *book)
+{
+   if (!book) return NULL;
+   return book->backend;
+}
+
+gboolean
+qof_book_shutting_down (QofBook *book)
+{
+  if (!book) return FALSE;
+  return book->shutting_down;
+}
+
+/* ====================================================================== */
+/* setters */
+
+void
+qof_book_set_backend (QofBook *book, QofBackend *be)
+{
+  if (!book) return;
+  ENTER ("book=%p be=%p", book, be);
+  book->backend = be;
+  LEAVE (" ");
+}
+
+void qof_book_kvp_changed (QofBook *book)
+{
+  if (!book) return;
+  book->inst.dirty = TRUE;
+}
+
+/* ====================================================================== */
+
+/* Store arbitrary pointers in the QofBook for data storage extensibility */
+/* XXX if data is NULL, we should remove the key from the hash table!
+ */
+void 
+qof_book_set_data (QofBook *book, const char *key, gpointer data)
+{
+  if (!book || !key) return;
+  g_hash_table_insert (book->data_tables, (gpointer)key, data);
+}
+
+void 
+qof_book_set_data_fin (QofBook *book, const char *key, gpointer data, QofBookFinalCB cb)
+{
+  if (!book || !key) return;
+  g_hash_table_insert (book->data_tables, (gpointer)key, data);
+
+  if (!cb) return;
+  g_hash_table_insert (book->data_table_finalizers, (gpointer)key, cb);
+}
+
+gpointer 
+qof_book_get_data (QofBook *book, const char *key)
+{
+  if (!book || !key) return NULL;
+  return g_hash_table_lookup (book->data_tables, (gpointer)key);
+}
+
+/* ====================================================================== */
+
+QofCollection *
+qof_book_get_collection (QofBook *book, QofIdType entity_type)
+{
+  QofCollection *col;
+
+  if (!book || !entity_type) return NULL;
+
+  col = g_hash_table_lookup (book->hash_of_collections, entity_type);
+  if (col) return col;
+
+  col = qof_collection_new (entity_type);
+
+  g_hash_table_insert (book->hash_of_collections,
+			  gnc_string_cache_insert((gpointer) entity_type), col);
+
+  return col;
+}
+
+struct _iterate {
+  QofCollectionForeachCB  fn;
+  gpointer                data;
+};
+
+static void 
+foreach_cb (gpointer key, gpointer item, gpointer arg)
+{
+  struct _iterate *iter = arg;
+  QofCollection *col = item;
+
+  iter->fn (col, iter->data);
+}
+
+void 
+qof_book_foreach_collection (QofBook *book, 
+                             QofCollectionForeachCB cb, gpointer user_data)
+{
+  struct _iterate iter;
+
+  g_return_if_fail (book);
+  g_return_if_fail (cb);
+
+  iter.fn = cb;
+  iter.data = user_data;
+
+  g_hash_table_foreach (book->hash_of_collections, foreach_cb, &iter);
+}
+
+/* ====================================================================== */
+
+void qof_book_mark_closed (QofBook *book)
+{
+	if(!book) { return; }
+	book->book_open = 'n';
+}
+
+gchar qof_book_get_open_marker(QofBook *book)
+{
+	if(!book) { return 'n'; }
+	return book->book_open;
+}
+
+gint32 qof_book_get_version (QofBook *book)
+{
+	if(!book) { return -1; }
+	return book->version;
+}
+
+guint32 qof_book_get_idata (QofBook *book)
+{
+	if(!book) { return 0; }
+	return book->idata;
+}
+
+void qof_book_set_version (QofBook *book, gint32 version)
+{
+	if(!book && version < 0) { return; }
+	book->version = version;
+}
+
+void qof_book_set_idata(QofBook *book, guint32 idata)
+{
+	if(!book && idata < 0) { return; }
+	book->idata = idata;
+}
+
+gint64
+qof_book_get_counter (QofBook *book, const char *counter_name)
+{
+  QofBackend *be;
+  KvpFrame *kvp;
+  KvpValue *value;
+  gint64 counter;
+
+  if (!book) {
+    PWARN ("No book!!!");
+    return -1;
+  }
+
+  if (!counter_name || *counter_name == '\0') {
+    PWARN ("Invalid counter name.");
+    return -1;
+  }
+
+  /* If we've got a backend with a counter method, call it */
+  be = book->backend;
+  if (be && be->counter)
+    return ((be->counter)(be, counter_name));
+
+  /* If not, then use the KVP in the book */
+  kvp = qof_book_get_slots (book);
+
+  if (!kvp) {
+    PWARN ("Book has no KVP_Frame");
+    return -1;
+  }
+
+  value = kvp_frame_get_slot_path (kvp, "counters", counter_name, NULL);
+  if (value) {
+    /* found it */
+    counter = kvp_value_get_gint64 (value);
+  } else {
+    /* New counter */
+    counter = 0;
+  }
+
+  /* Counter is now valid; increment it */
+  counter++;
+
+  /* Save off the new counter */
+  value = kvp_value_new_gint64 (counter);
+  kvp_frame_set_slot_path (kvp, value, "counters", counter_name, NULL);
+  kvp_value_delete (value);
+
+  /* and return the value */
+  return counter;
+}
+
+/* QofObject function implementation and registration */
+gboolean qof_book_register (void)
+{
+  static QofParam params[] = {
+    { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_entity_get_guid, NULL },
+    { QOF_PARAM_KVP,  QOF_TYPE_KVP,  (QofAccessFunc)qof_instance_get_slots, NULL },
+    { NULL },
+  };
+
+  qof_class_register (QOF_ID_BOOK, NULL, params);
+
+  return TRUE;
+}
+
+/* ========================== END OF FILE =============================== */

Added: gnucash/trunk/lib/libqof/qof/qofbook.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbook.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbook.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,177 @@
+/********************************************************************\
+ * qofbook.h -- Encapsulate all the information about a dataset.    *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup Object
+    @{ */
+/** @addtogroup Book
+    A QOF Book is a dataset.  It provides a single handle
+    through which all the various collections of entities
+    can be found.   In particular, given only the type of
+    the entity, the collection can be found.
+
+    Books also provide the 'natural' place to working with
+    a storage backend, as a book can encapsulate everything
+    held in storage.
+    @{ */
+/** @file qofbook.h
+ * @brief Encapsulate all the information about a dataset.
+ *
+ * @author Copyright (c) 1998, 1999, 2001, 2003 Linas Vepstas <linas at linas.org>
+ * @author Copyright (c) 2000 Dave Peticolas
+ */
+
+#ifndef QOF_BOOK_H
+#define QOF_BOOK_H
+
+#include <glib.h>
+#include "qofid.h"
+#include "kvp_frame.h"
+
+/** @brief Encapsulates all the information about a dataset
+ * manipulated by GnuCash.  This is the top-most structure
+ * used for anchoring data.
+ */
+
+/** Lookup an entity by guid, returning pointer to the entity */
+#define QOF_BOOK_LOOKUP_ENTITY(book,guid,e_type,c_type) ({  \
+  QofEntity *val = NULL;                                    \
+  if (guid && book) {                                       \
+    QofCollection *col;                                     \
+    col = qof_book_get_collection (book, e_type);           \
+    val = qof_collection_lookup_entity (col, guid);         \
+  }                                                         \
+  (c_type *) val;                                           \
+})
+
+/** \brief QofBook reference */
+typedef struct _QofBook       QofBook;
+
+/** GList of QofBook */
+typedef GList                 QofBookList;
+
+typedef void (*QofBookFinalCB) (QofBook *, gpointer key, gpointer user_data);
+
+/** Register the book object with the QOF object system. */
+gboolean qof_book_register (void);
+
+/** Allocate, initialise and return a new QofBook.  Books contain references
+ *  to all of the top-level object containers. */
+QofBook * qof_book_new (void);
+
+/** End any editing sessions associated with book, and free all memory
+    associated with it. */
+void      qof_book_destroy (QofBook *book);
+
+/** Close a book to editing.
+
+It is up to the application to check this flag,
+and once marked closed, books cannnot be marked as open.
+*/
+void qof_book_mark_closed (QofBook *book);
+
+/** \return The table of entities of the given type.
+ *
+ *  When an object's constructor calls qof_instance_init(), a
+ *  reference to the object is stored in the book.  The book stores
+ *  all the references to initialized instances, sorted by type.  This
+ *  function returns a collection of the references for the specified
+ *  type.
+ *
+ *  If the collection doesn't yet exist for the indicated type,
+ *  it is created.  Thus, this routine is gaurenteed to return
+ *  a non-NULL value.  (Unless the system malloc failed (out of
+ *  memory) in which case what happens??).
+ */
+QofCollection  * qof_book_get_collection (QofBook *, QofIdType);
+
+/** Invoke the indicated callback on each collection in the book. */
+typedef void (*QofCollectionForeachCB) (QofCollection *, gpointer user_data);
+void qof_book_foreach_collection (QofBook *, QofCollectionForeachCB, gpointer);
+
+/** \return The kvp data for the book.
+ *  Note that the book KVP data is persistant, and is stored/retrieved
+ *  from the file/database.  Thus, the book KVP is the correct place to
+ *  store data that needs to be persistant accross sessions (or shared
+ *  between multiple users).  To store application runtime data, use
+ *  qof_book_set_data() instead.
+ */
+#define qof_book_get_slots(book) qof_instance_get_slots(QOF_INSTANCE(book))
+
+/** The qof_book_set_data() allows arbitrary pointers to structs
+ *    to be stored in QofBook. This is the "prefered" method for
+ *    extending QofBook to hold new data types.  This is also
+ *    the ideal location to store other arbitrary runtime data
+ *    that the application may need.
+ *
+ *    The book data differs from the book KVP in that the contents
+ *    of the book KVP are persistant (are saved and restored to file
+ *    or database), whereas the data pointers exist only at runtime.
+ */
+void qof_book_set_data (QofBook *book, const char *key, gpointer data);
+
+/** Same as qof_book_set_data(), except that the callback will be called
+ *  when the book is destroyed.  The argument to the callback will be
+ *  the book followed by the data pointer.
+ */
+void qof_book_set_data_fin (QofBook *book, const char *key, gpointer data, QofBookFinalCB);
+
+/** Retrieves arbitrary pointers to structs stored by qof_book_set_data. */
+gpointer qof_book_get_data (QofBook *book, const char *key);
+
+/** Is the book shutting down? */
+gboolean qof_book_shutting_down (QofBook *book);
+
+/** qof_book_not_saved() will return TRUE if any
+ *    data in the book hasn't been saved to long-term storage.
+ *    (Actually, that's not quite true.  The book doesn't know
+ *    anything about saving.  Its just that whenever data is modified,
+ *    the 'dirty' flag is set.  This routine returns the value of the
+ *    'dirty' flag.  Its up to the backend to periodically reset this
+ *    flag, when it actually does save the data.)
+ */
+gboolean qof_book_not_saved (QofBook *book);
+
+/** The qof_book_mark_saved() routine marks the book as having been
+ *    saved (to a file, to a database). Used by backends to mark the
+ *    notsaved flag as FALSE just after loading.  Also used by the
+ *    main window code when the used has said to abandon any changes.
+ */
+void qof_book_mark_saved(QofBook *book);
+
+/** Call this function when you change the book kvp, to make sure the book
+ * is marked 'dirty'. */
+void qof_book_kvp_changed (QofBook *book);
+
+/** The qof_book_equal() method returns TRUE if books are equal.
+ * XXX this routine is broken, and does not currently compare data.
+ */
+gboolean qof_book_equal (QofBook *book_1, QofBook *book_2);
+
+/** This will 'get and increment' the named counter for this book.
+ * The return value is -1 on error or the incremented counter.
+ */
+gint64 qof_book_get_counter (QofBook *book, const char *counter_name);
+
+/** deprecated */
+#define qof_book_get_guid(X) qof_entity_get_guid (QOF_ENTITY(X))
+
+#endif /* QOF_BOOK_H */
+/** @} */
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/qofchoice.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofchoice.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofchoice.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,109 @@
+/***************************************************************************
+ *            qofchoice.c
+ *
+ *  Thu Jul  7 12:24:30 2005
+ *  Copyright  2005  Neil Williams
+ *  linux at codehelp.co.uk
+ ****************************************************************************/
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <glib.h>
+#include "qofchoice.h"
+
+static GHashTable *qof_choice_table = NULL;
+
+/* To initialise, call qof_choice_add_class in
+qof_object_register for the choice object. */
+static gboolean qof_choice_is_initialized(void)
+{
+	if(!qof_choice_table)
+	{
+		qof_choice_table = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+	if(!qof_choice_table) { return FALSE; }
+	return TRUE;
+}
+
+gboolean qof_object_is_choice(QofIdType type)
+{
+	gpointer value, check;
+
+	value = NULL;
+	check = NULL;
+	g_return_val_if_fail(qof_choice_is_initialized(), FALSE);
+	g_return_val_if_fail(type != NULL, FALSE);
+	value = g_hash_table_lookup(qof_choice_table, type);
+	if((GHashTable*)value) { return TRUE; }
+	g_message("DEBUG: QOF_TYPE_CHOICE setup failed for %s\n", type);
+	return FALSE;
+}
+
+gboolean
+qof_choice_create(char* type)
+{
+	GHashTable *param_table;
+
+	g_return_val_if_fail(type != NULL, FALSE);
+	g_return_val_if_fail(qof_choice_is_initialized() == TRUE, FALSE);
+	param_table = g_hash_table_new(g_str_hash, g_str_equal);
+	g_hash_table_insert(qof_choice_table, type, param_table);
+	return TRUE;
+}
+
+gboolean qof_choice_add_class(char* select, char* option, char* param_name)
+{
+	GHashTable *param_table;
+	GList *option_list;
+
+	option_list = NULL;
+	param_table = NULL;
+	g_return_val_if_fail(select != NULL, FALSE);
+	g_return_val_if_fail(qof_object_is_choice(select), FALSE);
+	param_table = (GHashTable*)g_hash_table_lookup(qof_choice_table, select);
+	g_return_val_if_fail(param_table, FALSE);
+	option_list = (GList*)g_hash_table_lookup(param_table, param_name);
+	option_list = g_list_append(option_list, option);
+	g_hash_table_insert(param_table, param_name, option_list);
+	return TRUE;
+}
+
+GList* qof_object_get_choices(QofIdType type, QofParam *param)
+{
+	GList *choices;
+	GHashTable *param_table;
+
+	g_return_val_if_fail(type != NULL, NULL);
+	g_return_val_if_fail(qof_choice_is_initialized() == TRUE, FALSE);
+	choices = NULL;
+	param_table = g_hash_table_lookup(qof_choice_table, type);
+	choices = g_hash_table_lookup(param_table, param->param_name);
+	return choices;
+}
+
+gboolean qof_choice_check(char* choice_obj, char *param_name, char* choice )
+{
+	GList *choices, *result;
+	GHashTable *param_table;
+
+	choices = result = NULL;
+	g_return_val_if_fail(qof_object_is_choice(choice_obj), FALSE);
+	param_table = g_hash_table_lookup(qof_choice_table, choice_obj);
+	choices = g_hash_table_lookup(param_table, param_name);
+	result = g_list_find(choices, choice);
+	if(!result) { return FALSE; }
+	return TRUE;
+}

Added: gnucash/trunk/lib/libqof/qof/qofchoice.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofchoice.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofchoice.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,151 @@
+/***************************************************************************
+ *            qofchoice.h
+ *
+ *  Thu Jul  7 12:25:24 2005
+ *  Copyright  2005  Neil Williams
+ *  linux at codehelp.co.uk
+ ****************************************************************************/
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _QOFCHOICE_H
+#define _QOFCHOICE_H
+
+/** @addtogroup Choice
+
+Objects can be linked together one-to-one by simply using the name of
+the related object as the parameter type in the QofClass parameter list.
+
+\verbatim
+{ FOO_PARAM, BAR_ID, (QofAccessFunc)qofFooGetBar, (QofSetterFunc)qofFooSetBar },
+\endverbatim
+
+This is limited as each FOO entity can contain only one reference to a
+single BAR entity per parameter. Also, this parameter cannot be used to
+link to a similar object, OBJ. This requires "one to many" links.
+
+There are two types of one-to-many links in QOF.
+
+-# ::QOF_TYPE_COLLECT - one to many entities all of only one type.
+	- Handles links between one object and a series of objects
+	that are \b all of the same type, e.g. a simple list.
+-# ::QOF_TYPE_CHOICE - one to a single entity of many possible types.
+	- Handles links between one object and a series of
+\b dissimilar objects, one of each type.
+
+Currently, there is no explicit way to support many-to-many links
+but existing methods can be combined to give approximately the same results.
+
+A QOF_TYPE_CHOICE object is like a C++ template. QOF_TYPE_CHOICE doesn't
+really exist by itself:
+\verbatim
+QOF_TYPE_CHOICE<QOF_X, QOF_Y, QOF_Z>
+\endverbatim
+It holds a single entity of type X, Y, or Z for the purposes of QOF
+or ::QSF. For querying the object it queries as if it's an X, Y, or Z.
+
+Each choice type has it's own definition of the allowable objects -
+each of which need to be registered as normal. Objects can declare
+themselves to be one option of a particular choice. There is no
+requirement for any object to be either a choice or an option for a
+choice object.
+
+-# Each ::QOF_TYPE_CHOICE parameter provides access to \b ONE entity of
+a pre-determined set of object types.
+-# The entity type within the choice can be determined at run time.
+-# Each entity can have a different *type* of entity to it's siblings,
+provided that it is one of the pre-determined object types.
+-# Objects declare themselves as containing choices and other objects
+can add themselves to the list of acceptable choices of suitable objects.
+-# QOF_TYPE_CHOICE is transparent - objects should retrieve the e_type of
+the received entity and handle the entity appropriately.
+-# The number of different entity types that can be pre-determined for
+any one QOF_TYPE_CHOICE parameter is not fixed. You can have as many
+types as the ::QofAccessFunc and ::QofSetterFunc can handle.
+-# Any one parameter can only have one QOF type. You cannot have a parameter
+that is both ::QOF_TYPE_COLLECT and QOF_TYPE_CHOICE any more than you can have
+one parameter that is both ::QOF_TYPE_BOOLEAN and ::QOF_TYPE_NUMERIC.
+-# When setting references using QOF_TYPE_CHOICE, QOF passes a single entity
+to the QofSetterFunc and it is left to the function to determine how to
+handle that entity type.  When retrieving references using QOF_TYPE_CHOICE,
+the object must return a single entity matching one of the choice types.
+
+ @{
+*/
+
+/** @file qofchoice.h
+	@brief Linking one entity to a single entity of many possible types.
+	@author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+*/
+#include "qofclass.h"
+#include "qofobject.h"
+
+/** \brief Identify an object as containing a choice. */
+#define QOF_TYPE_CHOICE "choice"
+
+/** \brief Does this object contain a choice parameter?
+
+Returns TRUE if any parameter in the object definition
+uses a choice of elements, whether or not those
+parameters contain any data.
+
+ at param type Type of object/entity.
+
+ at return TRUE if one or more choice parameters has been
+registered using the object definition, otherwise FALSE.
+*/
+gboolean qof_object_is_choice(QofIdType type);
+
+/** \brief Set an object as using QOF_TYPE_CHOICE. */
+gboolean  qof_choice_create(char* type);
+
+/** \brief Add the choices for this parameter to the object.
+
+ at param choice The choice object.
+ at param add  The object to be added as an option.
+ at param param_name The parameter that will be used to get or set options.
+
+ at return FALSE if object is not a choice object or on error
+	otherwise TRUE.
+*/
+gboolean qof_choice_add_class(char* choice, char* add, char* param_name);
+
+/** \brief Return the list of all object types usable with this parameter.
+
+ at param type The choice object type.
+ at param param The name of the parameter that will be used to
+	get or set options.
+
+ at return NULL on error or if no options exist for this parameter,
+	otherwise a GList of all QofIdType object type(s) available
+	via this choice object using this parameter.
+*/
+GList* qof_object_get_choices(QofIdType type, QofParam *param);
+
+/** \brief Is the choice valid for this param_name?
+
+ at param choice_obj The object containing the QOF_TYPE_CHOICE parameter.
+ at param param_name The name of a QOF_TYPE_CHOICE parameter in this object.
+ at param choice The QofIdType to look for in the list of choices.
+
+ at return TRUE if choice is found in the list of allowed choices for
+this parameter of this object. Otherwise, FALSE
+*/
+gboolean qof_choice_check(char* choice_obj, char *param_name, char* choice);
+
+
+/** @} */
+#endif /* _QOFCHOICE_H */

Added: gnucash/trunk/lib/libqof/qof/qofclass-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofclass-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofclass-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,44 @@
+/********************************************************************\
+ * qofclass-p.h -- Private API for registering queriable objects    *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup Object
+    @{ */
+/** @addtogroup Object_Private
+    Private interfaces, not meant to be used by applications.
+    @{ */
+/** @name  Class_Private
+    @{ */
+
+#ifndef QOF_CLASSP_H
+#define QOF_CLASSP_H
+
+#include "qofclass.h"
+
+void qof_class_init(void);
+void qof_class_shutdown (void);
+
+QofSortFunc qof_class_get_default_sort (QofIdTypeConst obj_name);
+
+/* @} */
+/* @} */
+/* @} */
+#endif /* QOF_CLASSP_H */

Added: gnucash/trunk/lib/libqof/qof/qofclass.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofclass.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofclass.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,294 @@
+/********************************************************************\
+ * qofclass.c -- provide QOF paramterized data objects              *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "qofclass.h"
+#include "qofclass-p.h"
+#include "qofquery.h"
+
+static QofLogModule log_module = QOF_MOD_CLASS;
+
+static GHashTable *classTable = NULL;
+static GHashTable *sortTable = NULL;
+static gboolean initialized = FALSE;
+
+static gboolean clear_table (gpointer key, gpointer value, gpointer user_data)
+{
+  g_hash_table_destroy (value);
+  return TRUE;
+}
+
+/********************************************************************/
+/* PUBLISHED API FUNCTIONS */
+
+void
+qof_class_register (QofIdTypeConst obj_name,
+                    QofSortFunc default_sort_function,
+                    const QofParam *params)
+{
+  GHashTable *ht;
+  int i;
+
+  if (!obj_name) return;
+
+  if (default_sort_function)
+  {
+    g_hash_table_insert (sortTable, (char *)obj_name, default_sort_function);
+  }
+
+  ht = g_hash_table_lookup (classTable, obj_name);
+
+  /* If it doesn't already exist, create a new table for this object */
+  if (!ht)
+  {
+    ht = g_hash_table_new (g_str_hash, g_str_equal);
+    g_hash_table_insert (classTable, (char *)obj_name, ht);
+  }
+
+  /* At least right now, we allow dummy, paramterless objects,
+   * for testing purposes.  Although I suppose that should be
+   * an error..  */
+  /* Now insert all the parameters */
+  if (params)
+  {
+    for (i = 0; params[i].param_name; i++)
+      g_hash_table_insert (ht,
+               (char *)params[i].param_name,
+               (gpointer)&(params[i]));
+  }
+}
+
+void
+qof_class_init(void)
+{
+  if (initialized) return;
+  initialized = TRUE;
+
+  classTable = g_hash_table_new (g_str_hash, g_str_equal);
+  sortTable = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+void
+qof_class_shutdown (void)
+{
+  if (!initialized) return;
+  initialized = FALSE;
+
+  g_hash_table_foreach_remove (classTable, clear_table, NULL);
+  g_hash_table_destroy (classTable);
+  g_hash_table_destroy (sortTable);
+}
+
+gboolean
+qof_class_is_registered (QofIdTypeConst obj_name)
+{
+  if (!obj_name) return FALSE;
+
+  if (g_hash_table_lookup (classTable, obj_name)) return TRUE;
+
+  return FALSE;
+}
+
+const QofParam *
+qof_class_get_parameter (QofIdTypeConst obj_name,
+                          const char *parameter)
+{
+  GHashTable *ht;
+
+  g_return_val_if_fail (obj_name, NULL);
+  g_return_val_if_fail (parameter, NULL);
+
+  ht = g_hash_table_lookup (classTable, obj_name);
+  if (!ht)
+  {
+    PWARN ("no object of type %s", obj_name);
+    return NULL;
+  }
+
+  return (g_hash_table_lookup (ht, parameter));
+}
+
+QofAccessFunc
+qof_class_get_parameter_getter (QofIdTypeConst obj_name,
+                                 const char *parameter)
+{
+  const QofParam *prm;
+
+  g_return_val_if_fail (obj_name, NULL);
+  g_return_val_if_fail (parameter, NULL);
+
+  prm = qof_class_get_parameter (obj_name, parameter);
+  if (prm)
+    return prm->param_getfcn;
+
+  return NULL;
+}
+
+QofSetterFunc
+qof_class_get_parameter_setter (QofIdTypeConst obj_name,
+                                 const char *parameter)
+{
+  const QofParam *prm;
+
+  g_return_val_if_fail (obj_name, NULL);
+  g_return_val_if_fail (parameter, NULL);
+
+  prm = qof_class_get_parameter (obj_name, parameter);
+  if (prm)
+    return prm->param_setfcn;
+
+  return NULL;
+}
+
+QofType
+qof_class_get_parameter_type (QofIdTypeConst obj_name,
+                               const char *param_name)
+{
+  const QofParam *prm;
+
+  if (!obj_name || !param_name) return NULL;
+
+  prm = qof_class_get_parameter (obj_name, param_name);
+  if (!prm) return NULL;
+
+  return (prm->param_type);
+}
+
+QofSortFunc
+qof_class_get_default_sort (QofIdTypeConst obj_name)
+{
+  if (!obj_name) return NULL;
+  return g_hash_table_lookup (sortTable, obj_name);
+}
+
+/* ================================================================ */
+
+struct class_iterate {
+  QofClassForeachCB   fcn;
+  gpointer            data;
+};
+
+static void
+class_foreach_cb (gpointer key, gpointer item, gpointer arg)
+{
+  struct class_iterate *iter = arg;
+  QofIdTypeConst id = key;
+
+  iter->fcn (id, iter->data);
+}
+
+void
+qof_class_foreach (QofClassForeachCB cb, gpointer user_data)
+{
+  struct class_iterate iter;
+
+  if (!cb) return;
+  if (!classTable) return;
+
+  iter.fcn = cb;
+  iter.data = user_data;
+
+  g_hash_table_foreach (classTable, class_foreach_cb, &iter);
+}
+
+/* ================================================================ */
+
+struct parm_iterate {
+  QofParamForeachCB   fcn;
+  gpointer            data;
+};
+
+static void
+param_foreach_cb (gpointer key, gpointer item, gpointer arg)
+{
+  struct parm_iterate *iter = arg;
+  QofParam *parm = item;
+
+  iter->fcn (parm, iter->data);
+}
+
+void
+qof_class_param_foreach (QofIdTypeConst obj_name,
+                         QofParamForeachCB cb, gpointer user_data)
+{
+  struct parm_iterate iter;
+  GHashTable *param_ht;
+
+  if (!obj_name || !cb) return;
+  if (!classTable) return;
+  param_ht = g_hash_table_lookup (classTable, obj_name);
+  if (!param_ht) return;
+
+  iter.fcn = cb;
+  iter.data = user_data;
+
+  g_hash_table_foreach (param_ht, param_foreach_cb, &iter);
+}
+
+struct param_ref_list
+{
+	GList *list;
+};
+
+static void
+find_reference_param_cb(QofParam *param, gpointer user_data)
+{
+	struct param_ref_list *b;
+
+	b = (struct param_ref_list*)user_data;
+	if((param->param_getfcn == NULL)||(param->param_setfcn == NULL)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_STRING)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_NUMERIC)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_DATE)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_CHAR)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_DEBCRED)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_GUID)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_INT32)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_INT64)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_DOUBLE)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_KVP)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_TYPE_BOOLEAN)) { return; }
+	if(0 == safe_strcmp(param->param_type, QOF_ID_BOOK)) { return; }
+	b->list = g_list_append(b->list, param);
+}
+
+GList*
+qof_class_get_referenceList(QofIdTypeConst type)
+{
+	GList *ref_list;
+	struct param_ref_list b;
+
+	ref_list = NULL;
+	b.list = NULL;
+	qof_class_param_foreach(type, find_reference_param_cb, &b);
+	ref_list = g_list_copy(b.list);
+	return ref_list;
+}
+
+
+/* ============================= END OF FILE ======================== */

Added: gnucash/trunk/lib/libqof/qof/qofclass.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofclass.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofclass.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,268 @@
+/********************************************************************\
+ * qofclass.h -- API for registering parameters on objects          *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+/** @addtogroup Object
+    @{ */
+
+/** @addtogroup Class
+  This file defines a class messaging system reminiscent of
+  traditional OO-style setter and getter interfaces to object 
+  properties.  A C-language object can declare a collection
+  of setters and getters on itself that can then be used 
+  to perform run-time (as opposed to compile-time) bindings 
+  to the object.
+
+  To put it differently, a QOF class is a set of parameter
+  getters and setters that are associated with an object type. 
+  Given a pointer to some thing, the setters and getters can
+  be used to get and set values out of that thing.  Note
+  that the pointer to "some thing" need not be a pointer
+  to a QOF Entity or Instance (although QOF classes are
+  more interesting when used with Entities/Instances).
+  What "some thing" is defined entirely by the programmer
+  declaring a new QOF Class.
+
+  Because a QOF Class associates getters and setters with
+  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 
+  object to describe itself.
+
+  The QOF Query subsystem depends on QOF classes having been
+  declared; the Query uses the getters to get values associated
+  with particular instances.
+  
+  A QofAccessFunc or QofSetterFunc do not need to be public 
+  functions, if you need to add functions to an object with an
+  established API, define the additional QOF routines as static.
+  Only the register routine needs to be public.
+@{ */
+
+/** @file qofclass.h
+    @brief API for registering paramters on objects 
+    @author Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>
+    @author Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+    @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#ifndef QOF_CLASS_H
+#define QOF_CLASS_H
+
+#include "qofid.h"
+
+#define QOF_MOD_CLASS "qof-class"
+
+/** \name Core types
+
+Core data types for objects that can be used in parameters.
+Note that QofIdTypes may also be used and will create a 
+single reference between two known objects.
+
+ @{
+ */
+
+#define QOF_TYPE_STRING    "string"
+#define QOF_TYPE_DATE      "date"
+#define QOF_TYPE_NUMERIC   "numeric"
+#define QOF_TYPE_DEBCRED   "debcred"
+#define QOF_TYPE_GUID      "guid"
+#define QOF_TYPE_INT32     "gint32"
+#define QOF_TYPE_INT64     "gint64"
+#define QOF_TYPE_DOUBLE    "double"
+#define QOF_TYPE_BOOLEAN   "boolean"
+#define QOF_TYPE_KVP       "kvp"
+#define QOF_TYPE_CHAR      "character"
+#define QOF_TYPE_COLLECT   "collection" /**< secondary collections
+are used for one-to-many references between entities and are
+implemented using ::QofCollection.
+These are \b NOT the same as the main collections in the QofBook.
+
+-# Each ::QofCollection contains one or many entities - *all* of a single type.
+-# The entity type within the collection can be determined at run time.
+-# Easy conversions to GList or whatever in the param_setfcn handler.
+-# Each parameter can have it's own collection.
+-# Each entity can have a different *type* of collection to it's siblings, 
+provided that it is acceptable to the set function.
+-# Each object decides which types are acceptable for which parameter in the 
+set functions. This is then part of the API for that object.
+
+QOF_TYPE_COLLECT has two functions, both related to one-to-many 
+links:
+- Represent a reference between 2 entities with a list of acceptable types.
+        (one object linked to many types of single entities)
+- Represent a reference between one entity and many entities of another type.
+        (one object linked to many entities of a single type.)
+
+If the set function can handle it, it could also be used for true one-to-many 
+links: one object linked to many entities of many types.
+
+n.b. Always subject to each collection holding only one type at runtime.
+(otherwise use books).
+
+*/
+/** @} */
+/** Type of Paramters (String, Date, Numeric, GUID, etc.) */
+typedef const char * QofType;
+
+typedef struct _QofParam QofParam;
+
+/** The QofAccessFunc defines an arbitrary function pointer
+ *  for access functions.  This is needed because C doesn't have
+ *  templates, so we just cast a lot.  Real functions must be of
+ *  the form:
+ *
+ *        param_type getter_func (object_type *self);
+ *  or
+ *        param_type getter_func (object_type *self, QofParam *param);
+ *
+ * The additional argument 'param' allows generic getter functions
+ * to be implemented, because this argument provides for a way to
+ * identify the expected getter_func return type at runtime.  It
+ * also provides a place for the user to hang additional user-defined
+ * data.
+ */
+typedef gpointer (*QofAccessFunc)(gpointer object, const QofParam *param);
+
+/** The QofSetterFunc defines an function pointer for parameter
+ *  setters. Real functions must be of the form:
+ *
+ * void setter_func (object_type *self, param_type *param);
+ */
+typedef void (*QofSetterFunc) (gpointer, gpointer);
+
+/** This structure is for each queriable parameter in an object
+ *
+ * -- param_name is the name of the parameter.
+ * -- param_type is the type of the parameter, which can be either another
+ *    object (QofIdType) or it can be a core data type (QofType).
+ * -- param_getfcn is the function to actually obtain the parameter
+ * -- param_setfcn is the function to actually set the parameter
+ * -- param_userdata is a place where the object author can place any 
+ *    desired object-author-defined data (and thus can be used by the 
+ *    author-defined setter/getter).
+ *
+ * Either the getter or the setter may be NULL.
+ *
+ *  XXX todo/fixme: need to define a destroy callback, so that when
+ * the param memory is freed, the callback can be used to release the 
+ * user-defined data.
+ */
+struct _QofParam 
+{
+  const char       * param_name;
+  QofType            param_type;
+  QofAccessFunc      param_getfcn;
+  QofSetterFunc      param_setfcn;
+  gpointer           param_userdata;
+};
+
+/** This function is the default sort function for a particular object type */
+typedef int (*QofSortFunc)(gpointer, gpointer);
+
+/** This function registers a new object class with the Qof subsystem.
+ *  In particular, it registers the set of setters and getters for
+ *  controlling the object.   The getters are typically used by the
+ *  query subsystem to query type specific data.   Note that there
+ *  is no particular requirement for there to be a setter for every
+ *  getter or even vice-versa, nor is there any requeirement for these
+ *  to map 'cleanly' or orthogonaly to the underlying object.  The
+ *  parameters are really just a set of value setting and getting 
+ *  routines.
+ *
+ *  The "params" argument must be a NULL-terminated array of QofParam. 
+ *  It may be NULL if there are no parameters to be registered.
+ */
+void qof_class_register (QofIdTypeConst obj_name,
+                         QofSortFunc default_sort_fcn,
+                         const QofParam *params);
+
+/** An example:
+ *
+ * #define MY_OBJ_MEMO     "memo"
+ * #define MY_OBJ_VALUE    "value"
+ * #define MY_OBJ_DATE     "date"
+ * #define MY_OBJ_ACCOUNT  "account"
+ * #define MY_OBJ_TRANS    "trans"
+ *
+ * static QofParam myParams[] = {
+ * { MY_OBJ_MEMO, QOF_TYPE_STRING, myMemoGetter, NULL },
+ * { MY_OBJ_VALUE, QOF_TYPE_NUMERIC, myValueGetter, NULL },
+ * { MY_OBJ_DATE, QOF_TYPE_DATE, myDateGetter, NULL },
+ * { MY_OBJ_ACCOUNT, GNC_ID_ACCOUNT, myAccountGetter, NULL },
+ * { MY_OBJ_TRANS, GNC_ID_TRANS, myTransactionGetter, NULL },
+ * NULL };
+ *
+ * qof_class_register ("myObjectName", myObjectCompare, &myParams);
+ */
+
+/** Return true if the the indicated type is registered, 
+ *  else return FALSE.
+ */
+gboolean qof_class_is_registered (QofIdTypeConst obj_name);
+
+/** Return the core datatype of the specified object's parameter */
+QofType qof_class_get_parameter_type (QofIdTypeConst obj_name,
+                                      const char *param_name);
+
+/** Return the registered Parameter Definition for the requested parameter */
+const QofParam * qof_class_get_parameter (QofIdTypeConst obj_name,
+                                          const char *parameter);
+
+/** Return the object's parameter getter function */
+QofAccessFunc qof_class_get_parameter_getter (QofIdTypeConst obj_name,
+                                              const char *parameter);
+
+/** Return the object's parameter setter function */
+QofSetterFunc qof_class_get_parameter_setter (QofIdTypeConst obj_name,
+                                              const char *parameter);
+
+/** Type definition for the class callback function. */
+typedef void (*QofClassForeachCB) (QofIdTypeConst, gpointer);
+
+/** Call the callback once for each object class that is registered
+ *  with the system.  The 'user_data' is passed back to the callback.
+ */
+void qof_class_foreach (QofClassForeachCB, gpointer user_data);
+
+/** Type definition for the paramter callback function. */
+typedef void (*QofParamForeachCB) (QofParam *, gpointer user_data);
+
+/** Call the callback once for each parameter on the indicated 
+ *  object class.  The 'user_data' is passed back to the callback.
+ */
+void qof_class_param_foreach (QofIdTypeConst obj_name,
+                              QofParamForeachCB, gpointer user_data);
+
+/** \brief List of the parameters that could be references.
+
+Simple check to return a GList of all parameters
+of this object type that are not known QOF data types.
+Used for partial QofBook support, see ::QofEntityReference
+*/
+GList* qof_class_get_referenceList(QofIdTypeConst type);
+
+
+#endif /* QOF_CLASS_H */
+/** @} */
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/qofgobj.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofgobj.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofgobj.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,326 @@
+/********************************************************************\
+ * qofgobj.c -- QOF to GLib GObject mapping                         *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#include "qof.h"
+#include "qofgobj.h"
+
+static QofLogModule log_module = QOF_MOD_QUERY;
+
+static gboolean initialized = FALSE;
+static GSList *paramList = NULL;
+static GSList *classList = NULL;
+
+/* =================================================================== */
+
+#if 0
+static gboolean 
+clear_table (gpointer key, gpointer value, gpointer user_data)
+{
+  g_slist_free (value);
+  return TRUE;
+}
+#endif
+
+void 
+qof_gobject_init(void)
+{
+  if (initialized) return;
+  initialized = TRUE;
+                                                                                
+  // gobjectClassTable = g_hash_table_new (g_str_hash, g_str_equal);
+
+  /* Init the other subsystems that we need */
+  qof_object_initialize();
+  qof_query_init ();
+}
+
+void 
+qof_gobject_shutdown (void)
+{
+  GSList *n;
+  
+  if (!initialized) return;
+  initialized = FALSE;
+                                                                                
+//  GSList *n;
+  for (n=paramList; n; n=n->next) g_free(n->data);
+  g_slist_free (paramList);
+
+  for (n=classList; n; n=n->next) g_free(n->data);
+  g_slist_free (classList);
+
+#if 0
+  // XXX also need to walk over books, and collection and delete
+  // the collection get_data instance lists !!
+  // without this we have a memory leak !!
+  g_hash_table_foreach_remove (gobjectParamTable, clear_table, NULL);
+  g_hash_table_destroy (gobjectParamTable);
+#endif
+}
+
+/* =================================================================== */
+
+#define GOBJECT_TABLE  "GobjectTable"
+
+void 
+qof_gobject_register_instance (QofBook *book, QofType type, GObject *gob)
+{
+  QofCollection *coll;
+  GSList *instance_list;
+  
+  if (!book || !type) return;
+
+  coll = qof_book_get_collection (book, type);
+
+  instance_list = qof_collection_get_data (coll);
+  instance_list = g_slist_prepend (instance_list, gob);
+  qof_collection_set_data (coll, instance_list);
+}
+
+/* =================================================================== */
+
+static gpointer
+qof_gobject_getter (gpointer data, QofParam *getter)
+{
+  GObject *gob = data;
+  const char *str;
+
+  GParamSpec *gps = getter->param_userdata;
+
+  /* Note that the return type must actually be of type
+   * getter->param_type but we just follow the hard-coded 
+   * mapping below ... */
+  if (G_IS_PARAM_SPEC_STRING(gps))
+  {
+    GValue gval = {G_TYPE_INVALID};
+    g_value_init (&gval, G_TYPE_STRING);
+    g_object_get_property (gob, getter->param_name, &gval);
+
+    str = g_value_get_string (&gval);
+    return (gpointer) str;
+  }
+  else
+  if (G_IS_PARAM_SPEC_INT(gps))
+  {
+    int ival;
+    
+    GValue gval = {G_TYPE_INVALID};
+    g_value_init (&gval, G_TYPE_INT);
+    g_object_get_property (gob, getter->param_name, &gval);
+
+    ival = g_value_get_int (&gval);
+    return (gpointer) ival;
+  }
+  else
+  if (G_IS_PARAM_SPEC_UINT(gps))
+  {
+    int ival;
+    GValue gval = {G_TYPE_INVALID};
+    g_value_init (&gval, G_TYPE_UINT);
+    g_object_get_property (gob, getter->param_name, &gval);
+
+    ival = g_value_get_uint (&gval);
+    return (gpointer) ival;
+  }
+  else
+  if (G_IS_PARAM_SPEC_BOOLEAN(gps))
+  {
+    int ival;
+    
+    GValue gval = {G_TYPE_INVALID};
+    g_value_init (&gval, G_TYPE_BOOLEAN);
+    g_object_get_property (gob, getter->param_name, &gval);
+
+    ival = g_value_get_boolean (&gval);
+    return (gpointer) ival;
+  }
+
+  PWARN ("unhandled parameter type %s for paramter %s", 
+          G_PARAM_SPEC_TYPE_NAME(gps), getter->param_name);
+  return NULL;
+}
+
+static double
+qof_gobject_double_getter (gpointer data, QofParam *getter)
+{
+  GObject *gob = data;
+   double fval;
+   
+  GParamSpec *gps = getter->param_userdata;
+
+  /* Note that the return type must actually be of type
+   * getter->param_type but we just follow the hard-coded 
+   * mapping below ... */
+  if (G_IS_PARAM_SPEC_FLOAT(gps))
+  {
+    GValue gval = {G_TYPE_INVALID};
+    g_value_init (&gval, G_TYPE_FLOAT);
+    g_object_get_property (gob, getter->param_name, &gval);
+
+    fval = g_value_get_float (&gval);
+    return fval;
+  }
+  else
+  if (G_IS_PARAM_SPEC_DOUBLE(gps))
+  {
+    GValue gval = {G_TYPE_INVALID};
+    g_value_init (&gval, G_TYPE_DOUBLE);
+    g_object_get_property (gob, getter->param_name, &gval);
+
+    fval = g_value_get_double (&gval);
+    return fval;
+  } 
+
+  PWARN ("unhandled parameter type %s for paramter %s", 
+          G_PARAM_SPEC_TYPE_NAME(gps), getter->param_name);
+  return 0.0;
+}
+
+/* =================================================================== */
+/* Loop over every instance of the given type in the collection
+ * of instances that we have on hand.
+ */
+static void
+qof_gobject_foreach (QofCollection *coll, QofEntityForeachCB cb, gpointer ud)
+{
+  GSList *n;
+  n = qof_collection_get_data (coll);
+  for (; n; n=n->next)
+  {
+    cb (n->data, ud);
+  }
+}
+                                                                                
+/* =================================================================== */
+
+void
+qof_gobject_register (QofType e_type, GObjectClass *obclass)
+{
+  int i;
+  int j;
+  QofParam *qof_param_list, *qpar;
+  QofObject *class_def;
+  GParamSpec **prop_list, *gparam;
+  guint n_props;
+
+  /* Get the GObject properties, convert to QOF properties */
+  prop_list = g_object_class_list_properties (obclass, &n_props);
+
+  qof_param_list = g_new0 (QofParam, n_props);
+  paramList = g_slist_prepend (paramList, qof_param_list);
+
+  PINFO ("object %s has %d props", e_type, n_props);
+  j=0;
+  for (i=0; i<n_props; i++)
+  {
+    gparam = prop_list[i];
+    qpar = &qof_param_list[j];
+
+    PINFO ("param %d %s is type %s", 
+          i, gparam->name, G_PARAM_SPEC_TYPE_NAME(gparam));
+
+    qpar->param_name = g_param_spec_get_name (gparam);
+    qpar->param_getfcn = (QofAccessFunc)qof_gobject_getter;
+    qpar->param_setfcn = NULL;
+    qpar->param_userdata = gparam;
+    if ((G_IS_PARAM_SPEC_INT(gparam))  ||
+        (G_IS_PARAM_SPEC_UINT(gparam)) ||
+        (G_IS_PARAM_SPEC_ENUM(gparam)) ||
+        (G_IS_PARAM_SPEC_FLAGS(gparam)))
+    {
+      qpar->param_type = QOF_TYPE_INT32;
+      j++;
+    } 
+    else
+    if ((G_IS_PARAM_SPEC_INT64(gparam)) ||
+        (G_IS_PARAM_SPEC_UINT64(gparam)))
+    {
+      qpar->param_type = QOF_TYPE_INT64;
+      j++;
+    } 
+    else
+    if (G_IS_PARAM_SPEC_BOOLEAN(gparam))
+    {
+      qpar->param_type = QOF_TYPE_BOOLEAN;
+      j++;
+    } 
+    else
+    if (G_IS_PARAM_SPEC_STRING(gparam))
+    {
+      qpar->param_type = QOF_TYPE_STRING;
+      j++;
+    } 
+    else
+    if ((G_IS_PARAM_SPEC_POINTER(gparam)) ||
+        (G_IS_PARAM_SPEC_OBJECT(gparam)))
+    {
+      /* No-op, silently ignore.  Someday we should handle this ...  */
+    } 
+    else
+    if ((G_IS_PARAM_SPEC_FLOAT(gparam)) ||
+        (G_IS_PARAM_SPEC_DOUBLE(gparam)))
+    {
+      qpar->param_getfcn = (QofAccessFunc) qof_gobject_double_getter;
+      qpar->param_type = QOF_TYPE_DOUBLE;
+      j++;
+    } 
+    else
+    if (G_IS_PARAM_SPEC_CHAR(gparam))
+    {
+      qpar->param_type = QOF_TYPE_CHAR;
+      j++;
+    } 
+    else
+    {
+      PWARN ("Unknown/unhandled parameter type %s on %s:%s\n", 
+      G_PARAM_SPEC_TYPE_NAME(gparam), e_type, qpar->param_name);
+    }
+  }
+
+  /* NULL-terminated list! */
+  qof_param_list[j].param_type = NULL;
+
+  qof_class_register (e_type, NULL, qof_param_list);
+
+  /* ------------------------------------------------------ */
+   /* Now do the class itself */
+  class_def = g_new0 (QofObject, 1);
+  classList = g_slist_prepend (classList, class_def);
+
+  class_def->interface_version = QOF_OBJECT_VERSION;
+  class_def->e_type = e_type;
+  /* We could let the user specify a "nick" here, but
+   * the actual class name seems reasonable, e.g. for debugging. */
+  class_def->type_label = G_OBJECT_CLASS_NAME (obclass);
+  class_def->create = NULL;
+  class_def->book_begin = NULL;
+  class_def->book_end = NULL;
+  class_def->is_dirty = NULL;
+  class_def->mark_clean = NULL;
+  class_def->foreach = qof_gobject_foreach;
+  class_def->printable = NULL;
+  class_def->version_cmp = NULL;
+ 
+  qof_object_register (class_def);
+}
+
+/* ======================= END OF FILE ================================ */

Added: gnucash/trunk/lib/libqof/qof/qofgobj.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofgobj.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofgobj.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,87 @@
+/********************************************************************\
+ * qofgobj.h -- QOF to GLib GObject mapping                         *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#ifndef QOF_GOBJ_H
+#define QOF_GOBJ_H
+
+/** @addtogroup Object
+    @{ */
+/** @addtogroup GObject GLib GObjects
+    The API defined in this file allows a user to register any
+    GLib GObject (and any object derived from one, e.g. GTK/Gnome)
+    with the QOF system, as a QOF Object Class.  This allows
+    the QOF Query routines to be used to search over collections
+    of GObjects.
+ 
+    XXX Only GObject properties are searchable, data and other 
+    hanging off the GObject is not.  Fix this. This needs fixing.
+
+@{ */
+/** @file qofgobj.h
+    @brief QOF to GLib GObject mapping
+    @author Copyright (C) 2004 Linas Vepstas <linas at linas.org>
+*/
+
+
+#include <glib-object.h>
+#include "qofbook.h"
+#include "qofclass.h"
+
+/** Initalize and shut down this subsystem. */
+void qof_gobject_init(void);
+void qof_gobject_shutdown (void);
+
+/** Register a GObject class with the QOF subsystem.
+ *  Doing this will make the properties associated with
+ *  this GObject searchable using the QOF subsystem.
+ *
+ *  The QofType can be any string you desire, although typically
+ *  you might want to set it to G_OBJECT_CLASS_NAME() of the 
+ *  object class.  Note that this type will become the name of
+ *  the "table" that is searched by SQL queries:
+ *  e.g. in order to be able to say "SELECT * FROM MyStuff;"
+ *  you must first say:
+ *   qof_gobject_register ("MyStuff", gobj_class);
+ */
+void qof_gobject_register (QofType type, GObjectClass *obclass);
+
+/** Register an instance of a GObject with the QOF subsystem.
+ *
+ *  The QofType can be any string you desire, although typically
+ *  you might want to set it to G_OBJECT_CLASS_NAME() of the 
+ *  object class.  Note that this type will become the name of
+ *  the "table" that is searched by SQL queries:
+ *  e.g. in order to be able to say "SELECT * FROM MyStuff;"
+ *  you must first say:
+ *   qof_gobject_register_instance (book, "MyStuff", obj);
+ *
+ *  The 'book' argument specifies an anchor point for the collection
+ *  of all of the registered instances.  By working with disjoint books,
+ *  you can have multiple disjoint searchable sets of objects.
+ */
+
+void qof_gobject_register_instance (QofBook *book, QofType, GObject *);
+
+#endif /* QOF_GOBJ_H */
+/** @} */
+/** @} */
+

Added: gnucash/trunk/lib/libqof/qof/qofid-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofid-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofid-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,59 @@
+/********************************************************************\
+ * qofid-p.h -- QOF entity identifier engine-only API               *
+ * Copyright (C) 2000 Dave Peticolas <peticola at cs.ucdavis.edu>      *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup Object
+    @{ */
+/** @addtogroup Object_Private
+    Private interfaces, not meant to be used by applications.
+    @{ */
+/** @name  Entity_Private
+    @{ */
+
+#ifndef QOF_ID_P_H
+#define QOF_ID_P_H 
+
+#include <glib.h>
+
+#include "qofid.h"
+
+/* This file defines an engine-only API for using gnucash entity
+ * identifiers. */
+
+/** Set the ID of the entity, over-riding teh previous ID. 
+ *  Very dangerous, use only for file i/o work. 
+ */
+void qof_entity_set_guid (QofEntity *ent, const GUID *guid);
+
+/** Take entity, remove it from whatever collection its currently
+ *  in, and place it in a new collection.  To be used only for
+ *  moving entity from one book to another.
+ */
+void qof_collection_insert_entity (QofCollection *, QofEntity *);
+
+/** reset value of dirty flag */
+void qof_collection_mark_clean (QofCollection *);
+void qof_collection_mark_dirty (QofCollection *);
+
+/* @} */
+/* @} */
+/* @} */
+#endif /* QOF_ID_P_H */

Added: gnucash/trunk/lib/libqof/qof/qofid.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofid.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofid.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,396 @@
+/********************************************************************\
+ * qofid.c -- QOF entity identifier implementation                  *
+ * Copyright (C) 2000 Dave Peticolas <dave at krondo.com>              *
+ * Copyright (C) 2003 Linas Vepstas <linas at linas.org>               *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#include "config.h"
+
+#include <string.h>
+#include <glib.h>
+
+#include "qofid.h"
+#include "qofid-p.h"
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+
+#define CACHE_INSERT(str) g_cache_insert(gnc_engine_get_string_cache(), (gpointer)(str));
+#define CACHE_REMOVE(str) g_cache_remove(gnc_engine_get_string_cache(), (gpointer)(str));
+
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+struct QofCollection_s
+{
+  QofIdType    e_type;
+  gboolean     is_dirty;
+  
+  GHashTable * hash_of_entities;
+  gpointer     data;       /* place where object class can hang arbitrary data */
+};
+
+/* =============================================================== */
+
+static void qof_collection_remove_entity (QofEntity *ent);
+
+void
+qof_entity_init (QofEntity *ent, QofIdType type, QofCollection * tab)
+{
+  g_return_if_fail (NULL != tab);
+  
+  /* XXX We passed redundant info to this routine ... but I think that's
+	* OK, it might eliminate programming errors. */
+  if (safe_strcmp(tab->e_type, type))
+  {
+    PERR ("attempt to insert \"%s\" into \"%s\"", type, tab->e_type);
+	 return;
+  }
+  ent->e_type = CACHE_INSERT (type);
+
+  do
+  {
+    guid_new(&ent->guid);
+
+    if (NULL == qof_collection_lookup_entity (tab, &ent->guid)) break;
+
+    PWARN("duplicate id created, trying again");
+  } while(1);
+ 
+  ent->collection = tab;
+
+  qof_collection_insert_entity (tab, ent);
+}
+
+void
+qof_entity_release (QofEntity *ent)
+{
+  if (!ent->collection) return;
+  qof_collection_remove_entity (ent);
+  CACHE_REMOVE (ent->e_type);
+  ent->e_type = NULL;
+}
+
+
+/* This is a restricted function, should be used only during 
+ * read from file */
+void
+qof_entity_set_guid (QofEntity *ent, const GUID *guid)
+{
+  QofCollection *col;
+  if (guid_equal (guid, &ent->guid)) return;
+
+  col = ent->collection;
+  qof_collection_remove_entity (ent);
+  ent->guid = *guid;
+  qof_collection_insert_entity (col, ent);
+}
+
+const GUID *
+qof_entity_get_guid (QofEntity *ent)
+{
+  if (!ent) return guid_null();
+  return &ent->guid;
+}
+
+/* =============================================================== */
+
+static guint
+id_hash (gconstpointer key)
+{
+  const GUID *guid = key;
+
+  if (key == NULL)
+    return 0;
+
+  /* Compiler should optimize this all away! */
+  if (sizeof(guint) <= 16)
+    return *((guint *) guid->data);
+  else
+  {
+    guint hash = 0;
+    unsigned int i, j;
+
+    for (i = 0, j = 0; i < sizeof(guint); i++, j++)
+    {
+      if (j == 16)
+        j = 0;
+
+      hash <<= 4;
+      hash |= guid->data[j];
+    }
+
+    return hash;
+  }
+}
+
+static gboolean
+id_compare(gconstpointer key_1, gconstpointer key_2)
+{
+  return guid_equal (key_1, key_2);
+}
+
+QofCollection *
+qof_collection_new (QofIdType type)
+{
+  QofCollection *col;
+  col = g_new0(QofCollection, 1);
+  col->e_type = CACHE_INSERT (type);
+  col->hash_of_entities = g_hash_table_new (id_hash, id_compare);
+  col->data = NULL;
+  return col;
+}
+
+void
+qof_collection_destroy (QofCollection *col)
+{
+  CACHE_REMOVE (col->e_type);
+  g_hash_table_destroy(col->hash_of_entities);
+  col->e_type = NULL;
+  col->hash_of_entities = NULL;
+  col->data = NULL;   /** XXX there should be a destroy notifier for this */
+  g_free (col);
+}
+
+/* =============================================================== */
+/* getters */
+
+QofIdType
+qof_collection_get_type (QofCollection *col)
+{
+  return col->e_type;
+}
+
+/* =============================================================== */
+
+static void
+qof_collection_remove_entity (QofEntity *ent)
+{
+  QofCollection *col;
+  if (!ent) return;
+  col = ent->collection;
+  if (!col) return;
+  g_hash_table_remove (col->hash_of_entities, &ent->guid);
+  ent->collection = NULL;
+}
+
+void
+qof_collection_insert_entity (QofCollection *col, QofEntity *ent)
+{
+  if (!col || !ent) return;
+  if (guid_equal(&ent->guid, guid_null())) return;
+  g_return_if_fail (col->e_type == ent->e_type);
+  qof_collection_remove_entity (ent);
+  g_hash_table_insert (col->hash_of_entities, &ent->guid, ent);
+  ent->collection = col;
+}
+
+gboolean
+qof_collection_add_entity (QofCollection *coll, QofEntity *ent)
+{
+	QofEntity *e;
+
+	e = NULL;
+	if (!coll || !ent) { return FALSE; }
+	if (guid_equal(&ent->guid, guid_null())) { return FALSE; }
+	g_return_val_if_fail (coll->e_type == ent->e_type, FALSE);
+	e = qof_collection_lookup_entity(coll, &ent->guid);
+	if ( e != NULL ) { return FALSE; }
+	g_hash_table_insert (coll->hash_of_entities, &ent->guid, ent);
+	return TRUE;
+}
+
+static void
+collection_merge_cb (QofEntity *ent, gpointer data)
+{
+	QofCollection *target;
+
+	target = (QofCollection*)data;
+	qof_collection_add_entity(target, ent);	
+}
+
+gboolean
+qof_collection_merge (QofCollection *target, QofCollection *merge)
+{
+	if(!target || !merge) { return FALSE; }
+	g_return_val_if_fail (target->e_type == merge->e_type, FALSE);
+	qof_collection_foreach(merge, collection_merge_cb, target);
+	return TRUE;
+}
+
+static void
+collection_compare_cb (QofEntity *ent, gpointer user_data)
+{
+	QofCollection *target;
+	QofEntity *e;
+	gint value;
+
+	e = NULL;
+	target = (QofCollection*)user_data;
+	if (!target || !ent) { return; }
+	value = *(gint*)qof_collection_get_data(target);
+	if (value != 0) { return; }
+	if (guid_equal(&ent->guid, guid_null())) 
+	{
+		value = -1;
+		qof_collection_set_data(target, &value);
+		return; 
+	}
+	g_return_if_fail (target->e_type == ent->e_type);
+	e = qof_collection_lookup_entity(target, &ent->guid);
+	if ( e == NULL )
+	{
+		value = 1;
+		qof_collection_set_data(target, &value);
+		return;
+	}
+	value = 0;
+	qof_collection_set_data(target, &value);
+}
+
+gint
+qof_collection_compare (QofCollection *target, QofCollection *merge)
+{
+	gint value;
+
+	value = 0;
+	if (!target && !merge) { return 0; }
+	if (target == merge) { return 0; }
+	if (!target && merge) { return -1; }
+	if (target && !merge) { return 1; }
+	if(target->e_type != merge->e_type) { return -1; }
+	qof_collection_set_data(target, &value);
+	qof_collection_foreach(merge, collection_compare_cb, target);
+	value = *(gint*)qof_collection_get_data(target);
+	if(value == 0) {
+		qof_collection_set_data(merge, &value);
+		qof_collection_foreach(target, collection_compare_cb, merge);
+		value = *(gint*)qof_collection_get_data(merge);
+	}
+	return value;
+}
+
+QofEntity *
+qof_collection_lookup_entity (QofCollection *col, const GUID * guid)
+{
+  QofEntity *ent;
+  g_return_val_if_fail (col, NULL);
+  if (guid == NULL) return NULL;
+  ent = g_hash_table_lookup (col->hash_of_entities, guid->data);
+  return ent;
+}
+
+QofCollection *
+qof_collection_from_glist (QofIdType type, GList *glist)
+{
+	QofCollection *coll;
+	QofEntity *ent;
+	GList *list;
+
+	coll = qof_collection_new(type);
+	for(list = glist; list != NULL; list = list->next)
+	{
+		ent = (QofEntity*)list->data;
+		if(FALSE == qof_collection_add_entity(coll, ent))
+		{
+			return NULL;
+		}
+	}
+	return coll;
+}
+
+guint
+qof_collection_count (QofCollection *col)
+{
+	guint c;
+
+	c = g_hash_table_size(col->hash_of_entities);
+	return c;
+}
+
+/* =============================================================== */
+
+gboolean 
+qof_collection_is_dirty (QofCollection *col)
+{
+   if (!col) return FALSE;
+   return col->is_dirty;
+}
+
+void 
+qof_collection_mark_clean (QofCollection *col)
+{
+   if (!col) return;
+   col->is_dirty = FALSE;
+}
+
+void 
+qof_collection_mark_dirty (QofCollection *col)
+{
+   if (!col) return;
+   col->is_dirty = TRUE;
+}
+
+/* =============================================================== */
+
+gpointer 
+qof_collection_get_data (QofCollection *col)
+{
+   if (!col) return NULL;
+   return col->data;
+}
+
+void 
+qof_collection_set_data (QofCollection *col, gpointer user_data)
+{
+   if (!col) return;
+   col->data = user_data;
+}
+
+/* =============================================================== */
+
+struct _iterate {
+  QofEntityForeachCB      fcn;
+  gpointer                data;
+};
+
+static void foreach_cb (gpointer key, gpointer item, gpointer arg)
+{
+  struct _iterate *iter = arg;
+  QofEntity *ent = item;
+
+  iter->fcn (ent, iter->data);
+}
+
+void
+qof_collection_foreach (QofCollection *col, 
+                   QofEntityForeachCB cb_func, gpointer user_data)
+{
+  struct _iterate iter;
+
+  g_return_if_fail (col);
+  g_return_if_fail (cb_func);
+
+  iter.fcn = cb_func;
+  iter.data = user_data;
+
+  g_hash_table_foreach (col->hash_of_entities, foreach_cb, &iter);
+}
+
+/* =============================================================== */

Added: gnucash/trunk/lib/libqof/qof/qofid.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofid.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofid.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,264 @@
+/********************************************************************\
+ * qofid.h -- QOF entity type identification system                 *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#ifndef QOF_ID_H
+#define QOF_ID_H 
+
+/** @addtogroup Entity 
+    @{ */
+/** @addtogroup Entities
+
+    This file defines an API that adds types to the GUID's.
+    GUID's with types can be used to identify and reference 
+    typed entities.  
+
+    The idea here is that a GUID can be used to uniquely identify
+    some thing.  By adding a type, one can then talk about the 
+    type of thing identified.  By adding a collection, one can
+    then work with a handle to a collection of things of a given
+    type, each uniquely identified by a given ID.  QOF Entities
+    can be used independently of any other part of the system.
+    In particular, Entities can be useful even if one is not using
+    the Query ond Object parts of the QOF system.
+  
+    Identifiers are globally-unique and permanent, i.e., once
+    an entity has been assigned an identifier, it retains that same
+    identifier for its lifetime.
+    Identifiers can be encoded as hex strings. 
+   
+    GUID Identifiers are 'typed' with strings.  The native ids used 
+    by QOF are defined below. An id with type QOF_ID_NONE does not 
+    refer to any entity, although that may change (???). An id with 
+    type QOF_ID_NULL does not refer to any entity, and will never refer
+    to any entity. An identifier with any other type may refer to an
+    actual entity, but that is not guaranteed (??? Huh?).  If an id 
+    does refer to an entity, the type of the entity will match the 
+    type of the identifier. 
+
+    If you have a type name, and you want to have a way of finding
+    a collection that is associated with that type, then you must use
+    Books.
+
+	Entities can refer to other entities as well as to the basic
+	QOF types, using the qofclass parameters.
+
+ @{ */
+/** @file qofid.h
+    @brief QOF entity type identification system 
+    @author Copyright (C) 2000 Dave Peticolas <peticola at cs.ucdavis.edu> 
+    @author Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+*/
+
+#include <string.h>
+#include "guid.h"
+
+/** QofIdType declaration */
+typedef const char * QofIdType;
+/** QofIdTypeConst declaration */
+typedef const char * QofIdTypeConst;
+/** QofLogModule declaration */
+typedef const gchar* QofLogModule;
+
+#define QOF_ID_NONE           NULL
+#define QOF_ID_NULL           "null"
+
+#define QOF_ID_BOOK           "Book"
+#define QOF_ID_SESSION        "Session"
+
+/** simple,cheesy cast but holds water for now */
+#define QOF_ENTITY(object) ((QofEntity *)(object))
+
+/** Inline string comparision; compiler will optimize away most of this */
+#define QSTRCMP(da,db) ({                \
+  int val = 0;                           \
+  if ((da) && (db)) {                    \
+    if ((da) != (db)) {                  \
+      val = strcmp ((da), (db));         \
+    }                                    \
+  } else                                 \
+  if ((!(da)) && (db)) {                 \
+    val = -1;                            \
+  } else                                 \
+  if ((da) && (!(db))) {                 \
+    val = 1;                             \
+  }                                      \
+  val; /* block assumes value of last statment */  \
+})
+
+/** return TRUE if object is of the given type */
+#define QOF_CHECK_TYPE(obj,type) (0 == QSTRCMP((type),(((QofEntity *)(obj))->e_type)))
+
+/** cast object to the indicated type, print error message if its bad  */
+#define QOF_CHECK_CAST(obj,e_type,c_type) (                   \
+  QOF_CHECK_TYPE((obj),(e_type)) ?                            \
+  (c_type *) (obj) :                                          \
+  (c_type *) ({                                               \
+     g_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,               \
+       "Error: Bad QofEntity at %s:%d", __FILE__, __LINE__);  \
+     (obj);                                                   \
+  }))
+
+/** QofEntity declaration */
+typedef struct QofEntity_s QofEntity;
+/** QofCollection declaration 
+
+ at param e_type QofIdType
+ at param is_dirty gboolean
+ at param hash_of_entities GHashTable
+ at param data gpointer, place where object class can hang arbitrary data
+
+*/
+typedef struct QofCollection_s QofCollection;
+
+/** QofEntity structure
+
+ at param e_type 	Entity type
+ at param guid		GUID for the entity
+ at param collection	Entity collection
+*/
+
+struct QofEntity_s
+{
+   QofIdType        e_type;
+	GUID             guid;
+	QofCollection  * collection;
+};
+
+/** @name QOF Entity Initialization & Shutdown 
+ @{ */
+/** Initialise the memory associated with an entity */
+void qof_entity_init (QofEntity *, QofIdType, QofCollection *);
+                                                                                
+/** Release the data associated with this entity. Dont actually free
+ * the memory associated with the instance. */
+void qof_entity_release (QofEntity *);
+/** @} */
+
+/** Return the GUID of this entity */
+const GUID * qof_entity_get_guid (QofEntity *);
+
+/** @name Collections of Entities 
+ @{ */
+
+/** create a new collection of entities of type */
+QofCollection * qof_collection_new (QofIdType type);
+
+/** return the number of entities in the collection. */
+guint qof_collection_count (QofCollection *col);
+
+/** destroy the collection */
+void qof_collection_destroy (QofCollection *col);
+
+/** return the type that the collection stores */
+QofIdType qof_collection_get_type (QofCollection *);
+
+/** Find the entity going only from its guid */
+QofEntity * qof_collection_lookup_entity (QofCollection *, const GUID *);
+
+/** Callback type for qof_entity_foreach */
+typedef void (*QofEntityForeachCB) (QofEntity *, gpointer user_data);
+
+/** Call the callback for each entity in the collection. */
+void qof_collection_foreach (QofCollection *, 
+                       QofEntityForeachCB, gpointer user_data);
+
+/** Store and retreive arbitrary object-defined data 
+ *
+ * XXX We need to add a callback for when the collection is being
+ * destroyed, so that the user has a chance to clean up anything
+ * that was put in the 'data' member here.
+ */
+gpointer qof_collection_get_data (QofCollection *col);
+void qof_collection_set_data (QofCollection *col, gpointer user_data);
+
+/** Return value of 'dirty' flag on collection */
+gboolean qof_collection_is_dirty (QofCollection *col);
+
+/** @name QOF_TYPE_COLLECT: Linking one entity to many of one type
+
+\note These are \b NOT the same as the main collections in the book.
+
+QOF_TYPE_COLLECT is a secondary collection, used to select entities
+of one object type as references of another entity.
+\sa QOF_TYPE_CHOICE.
+
+@{
+*/
+/** \brief Add an entity to a QOF_TYPE_COLLECT.
+
+\note These are \b NOT the same as the main collections in the book.
+
+Entities can be
+freely added and merged across these secondary collections, they
+will not be removed from the original collection as they would
+by using ::qof_entity_insert_entity or ::qof_entity_remove_entity. 
+
+*/
+gboolean
+qof_collection_add_entity (QofCollection *coll, QofEntity *ent);
+
+/** \brief Merge two QOF_TYPE_COLLECT of the same type.
+
+\note \b NOT the same as the main collections in the book.
+
+QOF_TYPE_COLLECT uses a secondary collection, independent of
+those in the book. Entities will not be removed from the
+original collection as when using ::qof_entity_insert_entity
+or ::qof_entity_remove_entity.
+
+*/
+gboolean
+qof_collection_merge (QofCollection *target, QofCollection *merge);
+
+/** \brief Compare two secondary collections.
+
+Performs a deep comparision of the collections. Each QofEntity in
+each collection is looked up in the other collection, via the GUID.
+
+\return 0 if the collections are identical or both are NULL
+otherwise -1 if target is NULL or either collection contains an entity with an invalid
+GUID or if the types of the two collections do not match,
+or +1 if merge is NULL or if any entity exists in one collection but
+not in the other.
+*/
+gint
+qof_collection_compare (QofCollection *target, QofCollection *merge);
+
+/** \brief Create a secondary collection from a GList
+
+ at param type The QofIdType of the QofCollection \b and of 
+	\b all entities in the GList.
+ at param glist GList of entities of the same QofIdType.
+
+ at return NULL if any of the entities fail to match the
+	QofCollection type, else a pointer to the collection
+	on success.
+*/
+QofCollection*
+qof_collection_from_glist (QofIdType type, GList *glist);
+
+/** @} */
+/** @} */
+
+#endif /* QOF_ID_H */
+/** @} */
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/qofinstance-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofinstance-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofinstance-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,97 @@
+/********************************************************************\
+ * qofinstance-p.h -- private fields common to all object instances *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/*
+ * Object instance holds many common fields that most
+ * gnucash objects use.
+ * 
+ * Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+ */
+
+/** @addtogroup Object
+    @{ */
+/** @addtogroup Object_Private
+    Private interfaces, not meant to be used by applications.
+    @{ */
+/** @name  Instance_Private
+    @{ */
+
+#ifndef QOF_INSTANCE_P_H
+#define QOF_INSTANCE_P_H
+
+#include "qofinstance.h"
+
+/**
+ * UNDER CONSTRUCTION!
+ * This is mostly scaffolding for now,
+ * eventually, it may hold more fields, such as refrence counting...
+ *
+ */
+struct QofInstance_s
+{
+   /** Globally unique id identifying this instance */
+   QofEntity entity;
+
+   /** The entity_table in which this instance is stored */
+   QofBook * book;
+
+  /** kvp_data is a key-value pair database for storing arbirtary
+   * information associated with this instance.  
+   * See src/engine/kvp_doc.txt for a list and description of the 
+   * important keys. */
+   KvpFrame *kvp_data;
+
+   /** Timestamp used to track the last modification to this 
+    *  instance.  Typically used to compare two versions of the
+    *  same object, to see which is newer.  When used with the 
+    *  SQL backend, this field is reserved for SQL use, to compare
+    *  the version in local memory to the remote, server version.
+    */
+   Timespec last_update;
+
+   /** Keep track of nesting level of begin/end edit calls */
+   int    editlevel;
+
+   /** In process of being destroyed */
+   gboolean  do_free;
+
+   /** dirty/clean flag. If dirty, then this instance has been modified,
+    * but has not yet been written out to storage (file/database)
+    */
+   gboolean  dirty;
+};
+
+/** reset the dirty flag */
+void qof_instance_mark_clean (QofInstance *);
+
+void qof_instance_set_slots (QofInstance *, KvpFrame *);
+
+/** Set the last_update time. Reserved for use by the SQL backend;
+ *  used for comparing version in local memory to that in remote 
+ *  server. 
+ */
+void qof_instance_set_last_update (QofInstance *inst, Timespec ts);
+
+/* @} */
+/* @} */
+/* @} */
+
+#endif /* QOF_INSTANCE_P_H */

Added: gnucash/trunk/lib/libqof/qof/qofinstance.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofinstance.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofinstance.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,245 @@
+/********************************************************************\
+ * qofinstance.c -- handler for fields common to all objects        *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+/*
+ * Object instance holds many common fields that most
+ * gnucash objects use.
+ *
+ * Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+ */
+
+#include "gnc-trace.h"
+
+#include "kvp-util-p.h"
+#include "qofbook.h"
+#include "qofbook-p.h"
+#include "qofid.h"
+#include "qofid-p.h"
+#include "qofinstance.h"
+#include "qofinstance-p.h"
+
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+/* ========================================================== */
+
+QofInstance*
+qof_instance_create (QofIdType type, QofBook *book)
+{
+	QofInstance *inst;
+
+	inst = g_new0(QofInstance, 1);
+	qof_instance_init(inst, type, book);
+	return inst;
+}
+
+void
+qof_instance_init (QofInstance *inst, QofIdType type, QofBook *book)
+{
+	QofCollection *col;
+
+	inst->book = book;
+	inst->kvp_data = kvp_frame_new();
+	inst->last_update.tv_sec = 0;
+	inst->last_update.tv_nsec = -1;
+	inst->editlevel = 0;
+	inst->do_free = FALSE;
+	inst->dirty = FALSE;
+
+	col = qof_book_get_collection (book, type);
+	qof_entity_init (&inst->entity, type, col);
+}
+
+void
+qof_instance_release (QofInstance *inst)
+{
+	kvp_frame_delete (inst->kvp_data);
+	inst->editlevel = 0;
+	inst->do_free = FALSE;
+	inst->dirty = FALSE;
+	qof_entity_release (&inst->entity);
+}
+
+const GUID *
+qof_instance_get_guid (QofInstance *inst)
+{
+	if (!inst) return NULL;
+	return &inst->entity.guid;
+}
+
+QofBook *
+qof_instance_get_book (QofInstance *inst)
+{
+	if (!inst) return NULL;
+	return inst->book;
+}
+
+KvpFrame*
+qof_instance_get_slots (QofInstance *inst)
+{
+  if (!inst) return NULL;
+  return inst->kvp_data;
+}
+
+Timespec
+qof_instance_get_last_update (QofInstance *inst)
+{
+	if (!inst)
+	{
+		Timespec ts = {0,-1};
+		return ts;
+	}
+	return inst->last_update;
+}
+
+int
+qof_instance_version_cmp (QofInstance *left, QofInstance *right)
+{
+	if (!left && !right) return 0;
+	if (!left) return -1;
+	if (!right) return +1;
+	if (left->last_update.tv_sec < right->last_update.tv_sec) return -1;
+	if (left->last_update.tv_sec > right->last_update.tv_sec) return +1;
+	if (left->last_update.tv_nsec < right->last_update.tv_nsec) return -1;
+	if (left->last_update.tv_nsec > right->last_update.tv_nsec) return +1;
+	return 0;
+}
+
+gboolean
+qof_instance_is_dirty (QofInstance *inst)
+{
+	QofCollection *coll;
+
+	if (!inst) { return FALSE; }
+	coll = inst->entity.collection;
+	if(qof_collection_is_dirty(coll)) { return inst->dirty; }
+	inst->dirty = FALSE;
+	return FALSE;
+}
+
+void
+qof_instance_set_dirty(QofInstance* inst)
+{
+	QofCollection *coll;
+
+	inst->dirty = TRUE;
+	coll = inst->entity.collection;
+	qof_collection_mark_dirty(coll);
+}
+
+gboolean
+qof_instance_check_edit(QofInstance *inst)
+{
+	if(inst->editlevel > 0) { return TRUE; }
+	return FALSE;
+}
+
+gboolean
+qof_instance_do_free(QofInstance *inst)
+{
+	return inst->do_free;
+}
+
+void
+qof_instance_mark_free(QofInstance *inst)
+{
+	inst->do_free = TRUE;
+}
+
+/* ========================================================== */
+/* setters */
+
+void
+qof_instance_mark_clean (QofInstance *inst)
+{
+  if(!inst) return;
+  inst->dirty = FALSE;
+}
+
+void
+qof_instance_set_slots (QofInstance *inst, KvpFrame *frm)
+{
+  if (!inst) return;
+  if (inst->kvp_data && (inst->kvp_data != frm))
+  {
+    kvp_frame_delete(inst->kvp_data);
+  }
+
+  inst->dirty = TRUE;
+  inst->kvp_data = frm;
+}
+
+void
+qof_instance_set_last_update (QofInstance *inst, Timespec ts)
+{
+  if (!inst) return;
+  inst->last_update = ts;
+}
+
+/* ========================================================== */
+
+void
+qof_instance_gemini (QofInstance *to, QofInstance *from)
+{
+  time_t now;
+
+  /* Books must differ for a gemini to be meaningful */
+  if (!from || !to || (from->book == to->book)) return;
+
+  now = time(0);
+
+  /* Make a note of where the copy came from */
+  gnc_kvp_bag_add (to->kvp_data, "gemini", now,
+                                  "inst_guid", &from->entity.guid,
+                                  "book_guid", &from->book->inst.entity.guid,
+                                  NULL);
+  gnc_kvp_bag_add (from->kvp_data, "gemini", now,
+                                  "inst_guid", &to->entity.guid,
+                                  "book_guid", &to->book->inst.entity.guid,
+                                  NULL);
+
+  to->dirty = TRUE;
+}
+
+QofInstance *
+qof_instance_lookup_twin (QofInstance *src, QofBook *target_book)
+{
+	QofCollection *col;
+	KvpFrame *fr;
+	GUID * twin_guid;
+	QofInstance * twin;
+
+	if (!src || !target_book) return NULL;
+	ENTER (" ");
+
+	fr = gnc_kvp_bag_find_by_guid (src->kvp_data, "gemini",
+	                             "book_guid", &target_book->inst.entity.guid);
+
+	twin_guid = kvp_frame_get_guid (fr, "inst_guid");
+
+	col = qof_book_get_collection (target_book, src->entity.e_type);
+	twin = (QofInstance *) qof_collection_lookup_entity (col, twin_guid);
+
+	LEAVE (" found twin=%p", twin);
+	return twin;
+}
+
+/* ========================== END OF FILE ======================= */

Added: gnucash/trunk/lib/libqof/qof/qofinstance.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofinstance.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofinstance.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,130 @@
+/********************************************************************\
+ * qofinstance.h -- fields common to all object instances           *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup Entity
+    @{ */
+/** @addtogroup Instance
+    Qof Instances are a derived type of QofEntity.  The Instance
+    adds some common features and functions that most objects
+    will want to use.
+
+    @{ */
+/** @file qofinstance.h 
+ *  @brief Object instance holds common fields that most gnucash objects use.
+ * 
+ *  @author Copyright (C) 2003,2004 Linas Vepstas <linas at linas.org>
+ */
+
+#ifndef QOF_INSTANCE_H
+#define QOF_INSTANCE_H
+
+#include "guid.h"
+#include "gnc-date.h"
+#include "kvp_frame.h"
+#include "qofbook.h"
+#include "qofid.h"
+
+/* --- type macros --- */
+/* cheesy, but will do for now, eventually should be more gtk-like, handle
+ * thunks, etc.  */
+#define QOF_INSTANCE(object) ((QofInstance *)(object))
+
+typedef struct QofInstance_s QofInstance;
+
+/** Initialise the memory associated with an instance */
+void qof_instance_init (QofInstance *, QofIdType, QofBook *);
+
+/** release the data associated with this instance. Dont actually free 
+ * the memory associated with the instance. */
+void qof_instance_release (QofInstance *inst);
+
+/** Return the book pointer */
+QofBook * qof_instance_get_book (QofInstance *);
+
+/** Return the GUID of this instance */
+const GUID * qof_instance_get_guid (QofInstance *);
+
+/** Return the pointer to the kvp_data */
+KvpFrame* qof_instance_get_slots (QofInstance *);
+
+/** Return the last time this instance was modified.  If QofInstances
+ *  are used with the QofObject storage backends, then the instance
+ *  update times are reserved for use by the backend, for managing
+ *  multi-user updates.  Non-backend code should not set the update 
+ *  times. 
+ */
+Timespec qof_instance_get_last_update (QofInstance *inst);
+
+/** Compare two instances, based on thier last update times. 
+ *  Returns a negative, zero or positive value, respectively, 
+ *  if 'left' is earlier, same as or later than 'right'.  
+ *  Accepts NULL pointers, NULL's are by definition earlier
+ *  than any value.
+ */
+int qof_instance_version_cmp (QofInstance *left, QofInstance *right);
+
+/** Return value of is_dirty flag */
+gboolean qof_instance_is_dirty (QofInstance *);
+
+/** \brief Set the dirty flag
+
+Sets this instance AND the collection as dirty.
+*/
+void qof_instance_set_dirty(QofInstance* inst);
+
+gboolean qof_instance_check_edit(QofInstance *inst);
+
+gboolean qof_instance_do_free(QofInstance *inst);
+
+void qof_instance_mark_free(QofInstance *inst);
+
+QofInstance* qof_instance_create (QofIdType type, QofBook *book);
+
+/** Pair things up.  This routine inserts a kvp value into each instance
+ *  containing the guid of the other.  In this way, if one has one of the
+ *  pair, one can always find the other by looking up it's guid.  Typically,
+ *  you will want to use qof_instance_lookup_twin() to find the twin.
+ *  (The current implementation assumes the two instances belong to different
+ *  books, and will not add gemini kvp's unless the books differ.  Note that
+ *  the gemini kvp includes the book guid as well, so that the right book can
+ *  be found.
+ */
+void qof_instance_gemini (QofInstance *to, QofInstance *from);
+
+/** The qof_instance_lookup_twin() routine will find the "twin" of this
+ *    instance 'src' in the given other 'book' (if the twin exists).
+ *
+ *    When instances are gemini'ed or cloned, both of the pair are marked
+ *    with the guid of thier copy, thus allowing the sibling-copy of
+ *    an instance to be found.  Since the sibling may end up in a
+ *    different book, we need a way of finding it, given only that we
+ *    know the book, and that we know its twin.
+ *
+ *    That's what this routine does.  Given some book 'book', and an
+ *    instance 'src', it will find the sibling instance of 'src' that is
+ *    in 'book', and return it.  If not found, it returns NULL.  This
+ *    routine uses the 'gemini' kvp values to do its work. 
+ */
+QofInstance * qof_instance_lookup_twin (QofInstance *src, QofBook *book);
+
+/* @} */
+/* @} */
+#endif /* QOF_INSTANCE_H */

Added: gnucash/trunk/lib/libqof/qof/qofla-dir.h.in
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofla-dir.h.in	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofla-dir.h.in	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,26 @@
+/***************************************************************************
+ *            qofla-dir.h.in
+ *
+ *  Mon Dec 20 20:27:48 2004
+ *  Copyright  2004  Neil Williams
+ *  linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define QOF_LIB_DIR "@-libdir-@"
+

Added: gnucash/trunk/lib/libqof/qof/qofmath128.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofmath128.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofmath128.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,408 @@
+/********************************************************************
+ * qofmath128.c -- an 128-bit integer library                       *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org>               *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ *******************************************************************/
+
+#define _GNU_SOURCE
+
+#include "config.h"
+#include "qofmath128.h"
+
+#include <glib.h>
+
+/* =============================================================== */
+/*
+ *  Quick-n-dirty 128-bit integer math lib.   Things seem to mostly
+ *  work, and have been tested, but not comprehensively tested.
+ */
+
+#define HIBIT (0x8000000000000000ULL)
+
+/** Multiply a pair of signed 64-bit numbers, 
+ *  returning a signed 128-bit number.
+ */
+inline qofint128
+mult128 (gint64 a, gint64 b)
+{
+  qofint128 prod;
+  guint64 a0, a1;
+  guint64 b0, b1;
+  guint64 d, d0, d1;
+  guint64 e, e0, e1;
+  guint64 f, f0, f1;
+  guint64 g, g0, g1;
+  guint64 sum, carry, roll, pmax;
+
+  prod.isneg = 0;
+  if (0>a)
+  {
+    prod.isneg = !prod.isneg;
+    a = -a;
+  }
+
+  if (0>b)
+  {
+    prod.isneg = !prod.isneg;
+    b = -b;
+  }
+
+  a1 = a >> 32;
+  a0 = a - (a1<<32);
+
+  b1 = b >> 32;
+  b0 = b - (b1<<32);
+
+  d = a0*b0;
+  d1 = d >> 32;
+  d0 = d - (d1<<32);
+
+  e = a0*b1;
+  e1 = e >> 32;
+  e0 = e - (e1<<32);
+
+  f = a1*b0;
+  f1 = f >> 32;
+  f0 = f - (f1<<32);
+
+  g = a1*b1;
+  g1 = g >> 32;
+  g0 = g - (g1<<32);
+
+  sum = d1+e0+f0;
+  carry = 0;
+  /* Can't say 1<<32 cause cpp will goof it up; 1ULL<<32 might work */
+  roll = 1<<30;
+  roll <<= 2;
+
+  pmax = roll-1;
+  while (pmax < sum)
+  {
+    sum -= roll;
+    carry ++;
+  }
+
+  prod.lo = d0 + (sum<<32);
+  prod.hi = carry + e1 + f1 + g0 + (g1<<32);
+  // prod.isbig = (prod.hi || (sum >> 31));
+  prod.isbig = prod.hi || (prod.lo >> 63);
+
+  return prod;
+}
+
+/** Shift right by one bit (i.e. divide by two) */
+inline qofint128
+shift128 (qofint128 x)
+{
+  guint64 sbit = x.hi & 0x1;
+  x.hi >>= 1;
+  x.lo >>= 1;
+  x.isbig = 0;
+  if (sbit)
+  {
+    x.lo |= HIBIT;
+    x.isbig = 1;
+    return x;
+  }
+  if (x.hi)
+  {
+    x.isbig = 1;
+  }
+  return x;
+}
+
+/** Shift leftt by one bit (i.e. multiply by two) */
+inline qofint128
+shiftleft128 (qofint128 x)
+{
+  guint64 sbit;
+  sbit = x.lo & HIBIT;
+  x.hi <<= 1;
+  x.lo <<= 1;
+  x.isbig = 0;
+  if (sbit)
+  {
+    x.hi |= 1;
+    x.isbig = 1;
+    return x;
+  }
+  if (x.hi)
+  {
+    x.isbig = 1;
+  }
+  return x;
+}
+
+/** increment a 128-bit number by one */
+inline qofint128
+inc128 (qofint128 a)
+{
+  if (0 == a.isneg)
+  {
+    a.lo ++;
+    if (0 == a.lo)
+    {
+      a.hi ++;
+    }
+  }
+  else
+  {
+    if (0 == a.lo)
+    {
+      a.hi --;
+    }
+    a.lo --;
+  }
+
+  a.isbig = (a.hi != 0) || (a.lo >> 63);
+  return a;
+}
+
+/** Divide a signed 128-bit number by a signed 64-bit,
+ *  returning a signed 128-bit number.
+ */
+inline qofint128
+div128 (qofint128 n, gint64 d)
+{
+  qofint128 quotient;
+  int i;
+  guint64 remainder = 0;
+
+  quotient = n;
+  if (0 > d)
+  {
+    d = -d;
+    quotient.isneg = !quotient.isneg;
+  }
+
+  /* Use grade-school long division algorithm */
+  for (i=0; i<128; i++)
+  {
+    guint64 sbit = HIBIT & quotient.hi;
+    remainder <<= 1;
+    if (sbit) remainder |= 1;
+    quotient = shiftleft128 (quotient);
+    if (remainder >= d)
+    {
+       remainder -= d;
+       quotient.lo |= 1;
+    }
+  }
+
+  /* compute the carry situation */
+  quotient.isbig = (quotient.hi || (quotient.lo >> 63));
+
+  return quotient;
+}
+
+/** Return the remainder of a signed 128-bit number modulo 
+ *  a signed 64-bit.  That is, return n%d in 128-bit math.
+ *  I beleive that ths algo is overflow-free, but should be 
+ *  audited some more ... 
+ */
+inline gint64
+rem128 (qofint128 n, gint64 d)
+{
+  qofint128 quotient = div128 (n,d);
+
+  qofint128 mu = mult128 (quotient.lo, d);
+
+  gint64 nn = 0x7fffffffffffffffULL & n.lo;
+  gint64 rr = 0x7fffffffffffffffULL & mu.lo;
+  return nn - rr;
+}
+
+/** Return true of two numbers are equal */
+inline gboolean
+equal128 (qofint128 a, qofint128 b)
+{
+	if (a.lo != b.lo) return 0;
+	if (a.hi != b.hi) return 0;
+	if (a.isneg != b.isneg) return 0;
+	return 1;
+}
+
+/** Return returns 1 if a>b, -1 if b>a, 0 if a == b */
+inline int
+cmp128 (qofint128 a, qofint128 b)
+{
+   if ((0 == a.isneg) && b.isneg) return 1;
+   if (a.isneg && (0 == b.isneg)) return -1;
+   if (0 == a.isneg)
+   {
+     if (a.hi > b.hi) return 1;
+     if (a.hi < b.hi) return -1;
+     if (a.lo > b.lo) return 1;
+     if (a.lo < b.lo) return -1;
+     return 0;
+   }
+
+   if (a.hi > b.hi) return -1;
+   if (a.hi < b.hi) return 1;
+   if (a.lo > b.lo) return -1;
+   if (a.lo < b.lo) return 1;
+   return 0;
+}
+
+/** Return the greatest common factor of two 64-bit numbers */
+inline guint64
+gcf64(guint64 num, guint64 denom)
+{
+  guint64   t;
+
+  t =  num % denom;
+  num = denom;
+  denom = t;
+
+  /* The strategy is to use Euclid's algorithm */
+  while (0 != denom) 
+  {
+    t = num % denom;
+    num = denom;
+    denom = t;
+  }
+  /* num now holds the GCD (Greatest Common Divisor) */
+  return num;
+}
+
+/** Return the least common multiple of two 64-bit numbers. */
+inline qofint128
+lcm128 (guint64 a, guint64 b)
+{
+  guint64 gcf = gcf64 (a,b);
+  b /= gcf;
+  return mult128 (a,b);
+}
+
+/** Add a pair of 128-bit numbers, returning a 128-bit number */
+inline qofint128
+add128 (qofint128 a, qofint128 b)
+{
+  qofint128 sum;
+  if (a.isneg == b.isneg)
+  {
+    sum.isneg = a.isneg;
+    sum.hi = a.hi + b.hi;
+    sum.lo = a.lo + b.lo;
+    if ((sum.lo < a.lo) || (sum.lo < b.lo))
+    {
+     sum.hi ++;
+    }
+    sum.isbig = sum.hi || (sum.lo >> 63);
+    return sum;
+  }
+  if ((b.hi > a.hi) ||
+     ((b.hi == a.hi) && (b.lo > a.lo)))
+  {
+    qofint128 tmp = a;
+    a = b;
+    b = tmp;
+  }
+
+  sum.isneg = a.isneg;
+  sum.hi = a.hi - b.hi;
+  sum.lo = a.lo - b.lo;
+
+  if (sum.lo > a.lo)
+  {
+    sum.hi --;
+  }
+
+  sum.isbig = sum.hi || (sum.lo >> 63);
+  return sum;
+}
+
+
+#ifdef TEST_128_BIT_MULT
+static void pr (gint64 a, gint64 b)
+{
+   qofint128 prod = mult128 (a,b);
+   printf ("%" G_GINT64_FORMAT " * %" G_GINT64_FORMAT " = %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " (0x%llx %llx) %hd\n",
+	   a, b, prod.hi, prod.lo, prod.hi, prod.lo, prod.isbig);
+}
+
+static void prd (gint64 a, gint64 b, gint64 c)
+{
+   qofint128 prod = mult128 (a,b);
+   qofint128 quot = div128 (prod, c);
+   gint64 rem = rem128 (prod, c);
+   printf ("%" G_GINT64_FORMAT " * %" G_GINT64_FORMAT " / %" G_GINT64_FORMAT " = %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " + %" G_GINT64_FORMAT " (0x%llx %llx) %hd\n",
+	   a, b, c, quot.hi, quot.lo, rem, quot.hi, quot.lo, quot.isbig);
+}
+
+int main ()
+{
+  pr (2,2);
+
+  gint64 x = 1<<30;
+  x <<= 2;
+
+  pr (x,x);
+  pr (x+1,x);
+  pr (x+1,x+1);
+
+  pr (x,-x);
+  pr (-x,-x);
+  pr (x-1,x);
+  pr (x-1,x-1);
+  pr (x-2,x-2);
+
+  x <<= 1;
+  pr (x,x);
+  pr (x,-x);
+
+  pr (1000000, 10000000000000);
+
+  prd (x,x,2);
+  prd (x,x,3);
+  prd (x,x,4);
+  prd (x,x,5);
+  prd (x,x,6);
+
+  x <<= 29;
+  prd (3,x,3);
+  prd (6,x,3);
+  prd (99,x,3);
+  prd (100,x,5);
+  prd (540,x,5);
+  prd (777,x,7);
+  prd (1111,x,11);
+
+  /* Really test division */
+  qofint128 n;
+  n.hi = 0xdd91;
+  n.lo = 0x6c5abefbb9e13480ULL;
+
+  gint64 d = 0x2ae79964d3ae1d04ULL;
+  
+  int i;
+  for (i=0; i<20; i++) {
+
+  qofint128 quot = div128 (n, d);
+  printf ("%d result = %llx %llx\n", i, quot.hi, quot.lo);
+    d >>=1;
+    n = shift128 (n);
+  }
+  return 0;
+}
+
+#endif /* TEST_128_BIT_MULT */
+
+/* ======================== END OF FILE =================== */

Added: gnucash/trunk/lib/libqof/qof/qofmath128.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofmath128.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofmath128.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,85 @@
+/********************************************************************
+ * qofmath128.h -- an 128-bit integer library                       *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org>               *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+ *******************************************************************/
+
+#ifndef QOF_MATH_128_H
+#define QOF_MATH_128_H
+
+#include <glib.h>
+
+/** @addtogroup Math128
+ *  Quick-n-dirty 128-bit integer math lib.   Things seem to mostly
+ *  work, and have been tested, but not comprehensively tested.
+ * @{
+ */
+
+typedef struct {
+  guint64 hi;
+  guint64 lo;
+  short isneg;    /**< sign-bit -- T if number is negative */
+  short isbig;    /**< sizeflag -- T if number won't fit in signed 64-bit */
+} qofint128;
+
+/** Return true of two numbers are equal */
+inline gboolean equal128 (qofint128 a, qofint128 b);
+
+/** Return returns 1 if a>b, -1 if b>a, 0 if a == b */
+inline int cmp128 (qofint128 a, qofint128 b);
+
+/** Shift right by one bit (i.e. divide by two) */
+inline qofint128 shift128 (qofint128 x);
+
+/** Shift left by one bit (i.e. multiply by two) */
+inline qofint128 shiftleft128 (qofint128 x);
+
+/** Increment by one */
+inline qofint128 inc128 (qofint128 a);
+
+/** Add a pair of 128-bit numbers, returning a 128-bit number */
+inline qofint128 add128 (qofint128 a, qofint128 b);
+
+/** Multiply a pair of signed 64-bit numbers, 
+ *  returning a signed 128-bit number.
+ */
+inline qofint128 mult128 (gint64 a, gint64 b);
+
+/** Divide a signed 128-bit number by a signed 64-bit,
+ *  returning a signed 128-bit number.
+ */
+inline qofint128 div128 (qofint128 n, gint64 d);
+
+/** Return the remainder of a signed 128-bit number modulo 
+ *  a signed 64-bit.  That is, return n%d in 128-bit math.
+ *  I beleive that ths algo is overflow-free, but should be 
+ *  audited some more ... 
+ */
+inline gint64 rem128 (qofint128 n, gint64 d);
+
+/** Return the greatest common factor of two 64-bit numbers */
+inline guint64 gcf64(guint64 num, guint64 denom);
+
+/** Return the least common multiple of two 64-bit numbers. */
+inline qofint128 lcm128 (guint64 a, guint64 b);
+
+#endif
+
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/qofobject-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofobject-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofobject-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,49 @@
+/********************************************************************\
+ * qofobject-p.h -- the private Object Registration/Lookup Interface   *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup Object
+    @{ */
+/** @addtogroup Object_Private
+    Private interfaces, not meant to be used by applications. 
+    @{ */
+/** @name  Objects_Private
+    @{ */
+/** @file qofobject-p.h
+ * @brief the Core Object Registration/Lookup Private Interface
+ * @author Copyright (c) 2001,2002, Derek Atkins <warlord at MIT.EDU>
+ */
+
+#ifndef QOF_OBJECT_P_H_
+#define QOF_OBJECT_P_H_
+
+#include "qofbook.h"
+#include "qofobject.h"
+
+/** To be called from within the book */
+void qof_object_book_begin (QofBook *book);
+void qof_object_book_end (QofBook *book);
+
+gboolean qof_object_is_dirty (QofBook *book);
+void qof_object_mark_clean (QofBook *book);
+
+#endif /* QOF_OBJECT_P_H_ */
+/** @} */
+/** @} */
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/qofobject.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofobject.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofobject.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,353 @@
+/********************************************************************\
+ * qofobject.c -- the Core Object Registration/Lookup Interface     *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/*
+ * qofobject.c -- the Core Object Object Registry
+ * Copyright (C) 2001 Derek Atkins
+ * Author: Derek Atkins <warlord at MIT.EDU>
+ */
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "qofobject.h"
+#include "qofobject-p.h"
+#include "qofbook.h"
+
+static QofLogModule log_module = QOF_MOD_OBJECT;
+
+static gboolean object_is_initialized = FALSE;
+static GList *object_modules = NULL;
+static GList *book_list = NULL;
+static GHashTable *backend_data = NULL;
+
+gpointer
+qof_object_new_instance (QofIdTypeConst type_name, QofBook *book)
+{
+  const QofObject *obj;
+
+  if (!type_name) return NULL;
+
+  obj = qof_object_lookup (type_name);
+  if (!obj) return NULL;
+
+  if (obj->create) 
+    return (obj->create (book));
+
+  return NULL;
+}
+
+void qof_object_book_begin (QofBook *book)
+{
+  GList *l;
+
+  if (!book) return;
+  ENTER (" ");
+  for (l = object_modules; l; l = l->next) {
+    QofObject *obj = l->data;
+    if (obj->book_begin)
+      obj->book_begin (book);
+  }
+
+  /* Remember this book for later */
+  book_list = g_list_prepend (book_list, book);
+  LEAVE (" ");
+}
+
+void qof_object_book_end (QofBook *book)
+{
+  GList *l;
+
+  if (!book) return;
+  ENTER (" ");
+  for (l = object_modules; l; l = l->next) {
+    QofObject *obj = l->data;
+    if (obj->book_end)
+      obj->book_end (book);
+  }
+
+  /* Remove it from the list */
+  book_list = g_list_remove (book_list, book);
+  LEAVE (" ");
+}
+
+gboolean 
+qof_object_is_dirty (QofBook *book)
+{
+  GList *l;
+
+  if (!book) return FALSE;
+  for (l = object_modules; l; l = l->next) 
+  {
+    QofObject *obj = l->data;
+    if (obj->is_dirty)
+    {
+      QofCollection *col;
+      col = qof_book_get_collection (book, obj->e_type);
+      if (obj->is_dirty (col)) return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+void 
+qof_object_mark_clean (QofBook *book)
+{
+  GList *l;
+
+  if (!book) return;
+  for (l = object_modules; l; l = l->next) 
+  {
+    QofObject *obj = l->data;
+    if (obj->mark_clean)
+    {
+      QofCollection *col;
+      col = qof_book_get_collection (book, obj->e_type);
+      (obj->mark_clean) (col);
+    }
+  }
+}
+
+void qof_object_foreach_type (QofForeachTypeCB cb, gpointer user_data)
+{
+  GList *l;
+
+  if (!cb) return;
+
+  for (l = object_modules; l; l = l->next) {
+    QofObject *obj = l->data;
+    (cb) (obj, user_data);
+  }
+}
+
+void 
+qof_object_foreach (QofIdTypeConst type_name, QofBook *book, 
+                    QofEntityForeachCB cb, gpointer user_data)
+{
+  QofCollection *col;
+  const QofObject *obj;
+
+  if (!book || !type_name) return;
+  ENTER ("type=%s", type_name);
+
+  obj = qof_object_lookup (type_name);
+  if (!obj)
+  {
+    PERR ("No object of type %s", type_name);
+    return;
+  }
+  col = qof_book_get_collection (book, obj->e_type);
+  PINFO ("lookup obj=%p for type=%s", obj, type_name);
+  if (!obj) return;
+
+  PINFO ("type=%s foreach=%p", type_name, obj->foreach);
+  if (obj->foreach) 
+  {
+    obj->foreach (col, cb, user_data);
+  }
+  LEAVE ("type=%s", type_name);
+
+  return;
+}
+
+const char *
+qof_object_printable (QofIdTypeConst type_name, gpointer obj)
+{
+  const QofObject *b_obj;
+
+  if (!type_name || !obj) return NULL;
+
+  b_obj = qof_object_lookup (type_name);
+  if (!b_obj) return NULL;
+
+  if (b_obj->printable)
+    return (b_obj->printable (obj));
+
+  return NULL;
+}
+
+const char * qof_object_get_type_label (QofIdTypeConst type_name)
+{
+  const QofObject *obj;
+
+  if (!type_name) return NULL;
+
+  obj = qof_object_lookup (type_name);
+  if (!obj) return NULL;
+
+  return (obj->type_label);
+}
+
+static gboolean clear_table (gpointer key, gpointer value, gpointer user_data)
+{
+  g_hash_table_destroy (value);
+  return TRUE;
+}
+
+/* INITIALIZATION and PRIVATE FUNCTIONS */
+
+void qof_object_initialize (void)
+{
+  if (object_is_initialized) return;
+  backend_data = g_hash_table_new (g_str_hash, g_str_equal);
+  object_is_initialized = TRUE;
+}
+
+void qof_object_shutdown (void)
+{
+  g_return_if_fail (object_is_initialized == TRUE);
+
+  g_hash_table_foreach_remove (backend_data, clear_table, NULL);
+  g_hash_table_destroy (backend_data);
+  backend_data = NULL;
+
+  g_list_free (object_modules);
+  object_modules = NULL;
+  g_list_free (book_list);
+  book_list = NULL;
+  object_is_initialized = FALSE;
+}
+
+/* Register new types of object objects.
+ * Return TRUE if successful,
+ * return FALSE if it fails, invalid arguments, or if the object
+ * already exists
+ */
+gboolean qof_object_register (const QofObject *object)
+{
+  g_return_val_if_fail (object_is_initialized, FALSE);
+
+  if (!object) return FALSE;
+  g_return_val_if_fail (object->interface_version == QOF_OBJECT_VERSION, FALSE);
+
+  if (g_list_index (object_modules, (gpointer)object) == -1)
+    object_modules = g_list_prepend (object_modules, (gpointer)object);
+  else
+    return FALSE;
+
+  /* Now initialize all the known books */
+  if (object->book_begin && book_list) {
+    GList *node;
+    for (node = book_list; node; node = node->next)
+      object->book_begin (node->data);
+  }
+
+  return TRUE;
+}
+
+const QofObject * qof_object_lookup (QofIdTypeConst name)
+{
+  GList *iter;
+  const QofObject *obj;
+
+  g_return_val_if_fail (object_is_initialized, NULL);
+
+  if (!name) return NULL;
+
+  for (iter = object_modules; iter; iter = iter->next) {
+    obj = iter->data;
+    if (!safe_strcmp (obj->e_type, name))
+      return obj;
+  }
+  return NULL;
+}
+
+gboolean qof_object_register_backend (QofIdTypeConst type_name,
+                                   const char *backend_name,
+                                   gpointer be_data)
+{
+  GHashTable *ht;
+  g_return_val_if_fail (object_is_initialized, FALSE);
+
+  if (!type_name || *type_name == '\0' ||
+      !backend_name || *backend_name == '\0' ||
+      !be_data)
+    return FALSE;
+
+  ht = g_hash_table_lookup (backend_data, backend_name);
+
+  /* If it doesn't already exist, create a new table for this backend */
+  if (!ht) {
+    ht = g_hash_table_new (g_str_hash, g_str_equal);
+    g_hash_table_insert (backend_data, (char *)backend_name, ht);
+  }
+
+  /* Now insert the data */
+  g_hash_table_insert (ht, (char *)type_name, be_data);
+
+  return TRUE;
+}
+
+gpointer qof_object_lookup_backend (QofIdTypeConst type_name,
+                                 const char *backend_name)
+{
+  GHashTable *ht;
+
+  if (!type_name || *type_name == '\0' ||
+      !backend_name || *backend_name == '\0')
+    return NULL;
+
+  ht = g_hash_table_lookup (backend_data, (char *)backend_name);
+  if (!ht)
+    return NULL;
+
+  return g_hash_table_lookup (ht, (char *)type_name);
+}
+
+struct foreach_data {
+  QofForeachBackendTypeCB        cb;
+  gpointer                 user_data;
+};
+
+static void foreach_backend (gpointer key, gpointer be_item, gpointer arg)
+{
+  char *data_type = key;
+  struct foreach_data *cb_data = arg;
+
+  g_return_if_fail (key && be_item && arg);
+
+  /* Call the callback for this data type */
+  (cb_data->cb) (data_type, be_item, cb_data->user_data);
+}
+
+void qof_object_foreach_backend (const char *backend_name,
+                                 QofForeachBackendTypeCB cb,
+                                 gpointer user_data)
+{
+  GHashTable *ht;
+  struct foreach_data cb_data;
+
+  if (!backend_name || *backend_name == '\0' || !cb)
+    return;
+
+  ht = g_hash_table_lookup (backend_data, (char *)backend_name);
+  if (!ht)
+    return;
+
+  cb_data.cb = cb;
+  cb_data.user_data = user_data;
+
+  g_hash_table_foreach (ht, foreach_backend, &cb_data);
+}
+
+/* ========================= END OF FILE =================== */

Added: gnucash/trunk/lib/libqof/qof/qofobject.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofobject.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofobject.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,179 @@
+/********************************************************************\
+ * qofobject.h -- the Core Object Registration/Lookup Interface     *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/** @addtogroup Object
+    @{ */
+/** @addtogroup Objects
+    QOF Objects provide the means for associating
+    a storage backend to a set of QOF Entities.   While an entity
+    can be though of as an identified instance of some thing,  the
+    QOF Object provides for a way to associate instances with
+    a storage backend.  Storage might be file or SQL storage.
+
+    QOF Objects are also used by the query system .... 
+
+    To work with your own QOF Objects, you can use the QOF
+    Generator to create sample objects and a mini-application
+    with the SQL-type query interface.
+    http://qof-gen.sourceforge.net/
+
+    XXX todo, we should split out the storage aspects of this 
+    thing from the 'foreach' that query depends on.  These are
+    kinda unrelated concepts.
+
+    @{ */
+/** @file qofobject.h
+ *  @brief the Core Object Registration/Lookup Interface
+ *  @author Copyright (c) 2001,2002 Derek Atkins <warlord at MIT.EDU>
+ */
+
+#ifndef QOF_OBJECT_H_
+#define QOF_OBJECT_H_
+
+#include "qofbook.h"
+#include "qofid.h"
+#include "qofchoice.h"
+
+/** Defines the version of the core object object registration
+ * interface.  Only object modules compiled against this version
+ * of the interface will load properly
+ */
+#define QOF_OBJECT_VERSION 3
+
+#define QOF_MOD_OBJECT "qof-object"
+
+typedef struct _QofObject QofObject;
+typedef void (*QofForeachCB) (gpointer obj, gpointer user_data);
+typedef void (*QofForeachTypeCB) (QofObject *type, gpointer user_data);
+typedef void (*QofForeachBackendTypeCB) (QofIdTypeConst type,
+                                      gpointer backend_data,
+                                      gpointer user_data);
+
+/** This is the QofObject Class descriptor 
+ */
+struct _QofObject 
+{
+  gint                interface_version; /* of this object interface */
+  QofIdType           e_type;            /* the Object's QOF_ID */
+  const char *        type_label;        /* "Printable" type-label string */
+
+  /** Create a new instance of this object type.  This routine might be
+   *  NULL if the object type doesn't provide a way of creating new 
+   *  instances. 
+   */
+  gpointer            (*create)(QofBook *);
+
+  /** book_begin is called from within the Book routines to create
+   * module-specific hooks in a book whenever a book is created.
+   */
+  void                (*book_begin)(QofBook *);
+
+  /** book_end is called when the book is being closed, to clean
+   * up (and free memory).
+   */
+  void                (*book_end)(QofBook *);
+
+  /** Determine if there are any dirty items in this book */
+  gboolean            (*is_dirty)(QofCollection *);
+
+  /** Mark this object's book clean (for after a load) */
+  void                (*mark_clean)(QofCollection *);
+
+  /** Traverse over all of the items in the collection, calling
+   *  the callback on each item.  The third argument can be any 
+   *  arbitrary caller-supplied data, and is passed to the callback. 
+   *  Although (*foreach) may be NULL, allmost all objects should
+   *  provide this routine, as without it, little of interest can 
+    * be done.
+   */
+  void                (*foreach)(QofCollection *, QofEntityForeachCB, gpointer);
+
+  /** Given a particular item of this type, return a printable string. 
+   */
+  const char *        (*printable)(gpointer instance);
+
+  /** Given a pair of items of this type, this routine returns value 
+   *  indicating which item is 'newer'.  This routine is used by storage
+   *  backends to determine if the local or the remote copy of a 
+   *  particular item is the latest, 'uptodate' version.  Tis routine
+   *  should return an integer less than, equal to, or greater than zero
+   *  if 'instance_left' is found to be, respecitvely, earlier than, equal
+   *  to or later than than 'instance_right'.
+   */
+  int                 (*version_cmp)(gpointer instance_left, gpointer instance_right);
+};
+
+/* -------------------------------------------------------------- */
+
+/** @name Initialize the object registration subsystem */
+/** @{ */
+void qof_object_initialize (void);
+void qof_object_shutdown (void);
+/** @} */
+
+/** Register new types of object objects */
+gboolean qof_object_register (const QofObject *object);
+
+/** Lookup an object definition */
+const QofObject * qof_object_lookup (QofIdTypeConst type_name);
+
+/** Create an instance of the indicated type, returning a pointer to that
+ *  instance.  This routine just calls the (*new) callback on the object
+ *  definition.  
+ */
+gpointer qof_object_new_instance (QofIdTypeConst type_name, QofBook *book);
+
+/** Get the printable label for a type.  This label is *not*
+ * translated; you must use _() on it if you want a translated version.
+ */
+const char * qof_object_get_type_label (QofIdTypeConst type_name);
+
+/** @return a Human-readable string name for an instance */
+const char * qof_object_printable (QofIdTypeConst type_name, gpointer instance);
+
+/** Invoke the callback 'cb' on every object class definition.
+ *  The user_data pointer is passed back to the callback.
+ */
+void qof_object_foreach_type (QofForeachTypeCB cb, gpointer user_data);
+
+/** Invoke the callback 'cb' on every instance ov a particular
+ *  object type.  It is presumed that the 'book' stores or somehow
+ *  identifies a colllection of instances; thus the callback will 
+ *  be invoked only for those instances stored in the book.
+ */
+void qof_object_foreach (QofIdTypeConst type_name, QofBook *book, 
+                         QofEntityForeachCB cb, gpointer user_data);
+
+/** Register and lookup backend-specific data for this particular object */
+gboolean qof_object_register_backend (QofIdTypeConst type_name,
+                                      const char *backend_name,
+                                      gpointer be_data);
+
+gpointer qof_object_lookup_backend (QofIdTypeConst type_name,
+                                    const char *backend_name);
+
+void qof_object_foreach_backend (const char *backend_name,
+                                 QofForeachBackendTypeCB cb,
+                                 gpointer user_data);
+
+#endif /* QOF_OBJECT_H_ */
+/** @} */
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/qofquery-deserial.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery-deserial.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery-deserial.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,760 @@
+/********************************************************************\
+ * qofquery-deserial.c -- Convert Qof-Query XML to QofQuery         *
+ * Copyright (C) 2001,2002,2004 Linas Vepstas <linas at linas.org>     *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+// #include "config.h"
+
+#include <stdlib.h>
+#include <glib.h>
+#include <libxml/parser.h>
+
+#include "qofquery-deserial.h"
+#include "qofquery-serialize.h"
+#include "qofquery-p.h"
+#include "qofquerycore-p.h"
+#include "gnc-engine-util.h"
+
+#define CACHE_INSERT(str)  \
+    g_cache_insert(gnc_engine_get_string_cache(), (gpointer)(str))
+#define CACHE_REMOVE(str)  \
+    g_cache_remove(gnc_engine_get_string_cache(), (gpointer)(str))
+
+/* =========================================================== */
+
+#define GET_TEXT(node)  ({                                   \
+   char * sstr = NULL;                                       \
+   xmlNodePtr text;                                          \
+   text = node->xmlChildrenNode;                             \
+   if (text && 0 == strcmp ("text", text->name)) {           \
+      sstr = text->content;                                  \
+   }                                                         \
+   sstr;                                                     \
+})
+
+#define GET_STR(SELF,FN,TOK)                                 \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      FN (SELF, str);                                        \
+   }                                                         \
+   else
+
+#define GET_DBL(SELF,FN,TOK)                                 \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      double rate = atof (str);                              \
+      FN (SELF, rate);                                       \
+   }                                                         \
+   else
+
+#define GET_INT32(SELF,FN,TOK)                               \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      gint32 ival = atoi (str);                              \
+      FN (SELF, ival);                                       \
+   }                                                         \
+   else
+
+#define GET_INT64(SELF,FN,TOK)                               \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      gint64 ival = atoll (str);                             \
+      FN (SELF, ival);                                       \
+   }                                                         \
+   else
+
+#define GET_DATE(SELF,FN,TOK)                                \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      Timespec tval = gnc_iso8601_to_timespec_gmt (str);     \
+      FN (SELF, tval);                                       \
+   }                                                         \
+   else
+
+#define GET_BOOL(SELF,FN,TOK)                                \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      gboolean bval = qof_util_bool_to_int (str);            \
+      FN (SELF, bval);                                       \
+   }                                                         \
+   else
+
+#define GET_NUMERIC(SELF,FN,TOK)                             \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      gnc_numeric nval;                                      \
+      string_to_gnc_numeric (str, &nval);                    \
+      FN (SELF, nval);                                       \
+   }                                                         \
+   else
+
+#define GET_GUID(SELF,FN,TOK)                                \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      GUID guid;                                             \
+      string_to_guid (str, &guid);                           \
+      FN (SELF, &guid);                                      \
+   }                                                         \
+   else
+
+#define GET_HOW(VAL,TOK,A,B,C,D,E,F)                         \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      int ival = QOF_COMPARE_##A;                            \
+      if (!strcmp (#A, str)) ival = QOF_COMPARE_##A;         \
+      else if (!strcmp (#B, str)) ival = QOF_COMPARE_##B;    \
+      else if (!strcmp (#C, str)) ival = QOF_COMPARE_##C;    \
+      else if (!strcmp (#D, str)) ival = QOF_COMPARE_##D;    \
+      else if (!strcmp (#E, str)) ival = QOF_COMPARE_##E;    \
+      else if (!strcmp (#F, str)) ival = QOF_COMPARE_##F;    \
+      VAL = ival;                                            \
+   }                                                         \
+   else
+
+#define GET_MATCH2(VAL,TOK,PFX,A,B)                          \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      int ival = QOF_##PFX##_##A;                            \
+      if (!strcmp (#A, str)) ival = QOF_##PFX##_##A;         \
+      else if (!strcmp (#B, str)) ival = QOF_##PFX##_##B;    \
+      VAL = ival;                                            \
+   }                                                         \
+   else
+
+#define GET_MATCH3(VAL,TOK,PFX,A,B,C)                        \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      int ival = QOF_##PFX##_##A;                            \
+      if (!strcmp (#A, str)) ival = QOF_##PFX##_##A;         \
+      else if (!strcmp (#B, str)) ival = QOF_##PFX##_##B;    \
+      else if (!strcmp (#C, str)) ival = QOF_##PFX##_##C;    \
+      VAL = ival;                                            \
+   }                                                         \
+   else
+
+#define GET_MATCH5(VAL,TOK,PFX,A,B,C,D,E)                    \
+   if (0 == strcmp (TOK, node->name))                        \
+   {                                                         \
+      const char *str = GET_TEXT (node);                     \
+      int ival = QOF_##PFX##_##A;                            \
+      if (!strcmp (#A, str)) ival = QOF_##PFX##_##A;         \
+      else if (!strcmp (#B, str)) ival = QOF_##PFX##_##B;    \
+      else if (!strcmp (#C, str)) ival = QOF_##PFX##_##C;    \
+      else if (!strcmp (#D, str)) ival = QOF_##PFX##_##D;    \
+      else if (!strcmp (#E, str)) ival = QOF_##PFX##_##E;    \
+      VAL = ival;                                            \
+   }                                                         \
+   else
+
+/* =============================================================== */
+/* Autogen the code for the simple, repetitive predicates */
+
+#define SIMPLE_PRED_HANDLER(SUBRNAME,CTYPE,GETTER,XMLTYPE,PRED) \
+static QofQueryPredData *                                       \
+SUBRNAME (xmlNodePtr root)                                      \
+{                                                               \
+	QofQueryPredData *pred;                                      \
+	xmlNodePtr xp;												\
+	xmlNodePtr node;                                             \
+	QofQueryCompare how;										\
+	CTYPE val;													\
+	xp = root->xmlChildrenNode;									\
+                                                                \
+	how = QOF_COMPARE_EQUAL;									\
+	val = 0;                                               \
+                                                                \
+	for (node=xp; node; node = node->next)                       \
+	{                                                            \
+		if (node->type != XML_ELEMENT_NODE) continue;             \
+                                                                \
+		GET_HOW (how, "qofquery:compare", LT, LTE, EQUAL, GT, GTE, NEQ); \
+		GETTER (0, val=, XMLTYPE);                                \
+		{}                                                        \
+	}                                                            \
+                                                                \
+	pred = PRED (how, val);                                      \
+	return pred;                                                 \
+}
+
+SIMPLE_PRED_HANDLER (qof_query_pred_double_from_xml,
+                     double,
+                     GET_DBL,
+                     "qofquery:double",
+	                  qof_query_double_predicate);
+
+SIMPLE_PRED_HANDLER (qof_query_pred_int64_from_xml,
+                     gint64,
+                     GET_INT64,
+                     "qofquery:int64",
+	                  qof_query_int64_predicate);
+
+SIMPLE_PRED_HANDLER (qof_query_pred_int32_from_xml,
+                     gint32,
+                     GET_INT32,
+                     "qofquery:int32",
+	                  qof_query_int32_predicate);
+
+SIMPLE_PRED_HANDLER (qof_query_pred_boolean_from_xml,
+                     gboolean,
+                     GET_BOOL,
+                     "qofquery:boolean",
+	                  qof_query_boolean_predicate);
+
+/* =============================================================== */
+
+static void wrap_new_gint64(KvpValue **v, gint64 value) {
+	*v = kvp_value_new_gint64 (value); }
+static void wrap_new_double(KvpValue **v, double value) {
+	*v = kvp_value_new_double (value); }
+static void wrap_new_numeric(KvpValue **v, gnc_numeric value) {
+	*v = kvp_value_new_gnc_numeric (value); }
+static void wrap_new_string(KvpValue **v, const char * value) {
+	*v = kvp_value_new_string (value); }
+static void wrap_new_guid(KvpValue **v, const GUID * value) {
+	*v = kvp_value_new_guid (value); }
+static void wrap_new_timespec(KvpValue **v, Timespec value) {
+	*v = kvp_value_new_timespec (value); }
+
+
+static QofQueryPredData *
+qof_query_pred_kvp_from_xml (xmlNodePtr root)
+{
+	QofQueryCompare how;
+	GSList *path;
+	KvpValue *value;
+	QofQueryPredData *pred;
+	xmlNodePtr xp;
+	xmlNodePtr node;
+
+	how = QOF_COMPARE_EQUAL;
+	xp = root->xmlChildrenNode;
+	path = NULL;
+	value = NULL;
+
+	for (node=xp; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+
+		GET_HOW (how, "qofquery:compare", LT, LTE, EQUAL, GT, GTE, NEQ);
+		if (0 == strcmp ("qofquery:kvp-path", node->name))
+		{
+			const char *str = GET_TEXT (node);
+			path = g_slist_append (path, (gpointer) str);
+		}
+		else
+		GET_INT64(&value,   wrap_new_gint64,   "qofquery:int64");
+		GET_DBL(&value,     wrap_new_double,   "qofquery:double");
+		GET_NUMERIC(&value, wrap_new_numeric,  "qofquery:numeric");
+		GET_STR(&value,     wrap_new_string,   "qofquery:string");
+		GET_GUID(&value,    wrap_new_guid,     "qofquery:guid");
+		GET_DATE(&value,    wrap_new_timespec, "qofquery:date");
+	}
+
+	pred = qof_query_kvp_predicate (how, path, value);
+	g_slist_free (path);
+	return pred;
+}
+
+/* =============================================================== */
+
+static QofQueryPredData *
+qof_query_pred_guid_from_xml (xmlNodePtr root)
+{
+	GList *guid_list, *n;
+	const char *str;
+	GUID *guid;
+	gboolean decode;
+	QofQueryPredData *pred;
+	QofGuidMatch sm;
+	xmlNodePtr xp;
+	xmlNodePtr node;
+	guid_list = NULL;
+
+	sm = QOF_GUID_MATCH_ANY;
+	xp = root->xmlChildrenNode;
+	for (node=xp; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+
+		/* char pred doesn't have GET_HOW */
+		GET_MATCH5 (sm, "qofquery:guid-match", 
+		            GUID_MATCH, ANY, NONE, NULL, ALL, LIST_ANY);
+
+		if (0 == strcmp ("qofquery:guid", node->name))
+		{
+			str = GET_TEXT (node);
+			guid = guid_malloc ();
+			decode = string_to_guid (str, guid);
+			if (decode)
+			{
+				guid_list = g_list_append (guid_list, guid);
+			}
+			else
+			{
+				guid_free (guid);
+				// XXX error!  let someone know!
+			}
+		}
+	}
+
+	pred = qof_query_guid_predicate (sm, guid_list);
+
+	/* The predicate made a copy of everything, so free our stuff */
+	for (n=guid_list; n; n=n->next)
+	{
+		guid_free (n->data);
+	}
+   g_list_free (guid_list);
+	return pred;
+}
+
+/* =============================================================== */
+
+static QofQueryPredData *
+qof_query_pred_char_from_xml (xmlNodePtr root)
+{
+	QofQueryPredData *pred;
+	QofCharMatch sm;
+	const char * char_list;
+	xmlNodePtr xp;
+	xmlNodePtr node;
+
+    char_list = NULL;
+	xp = root->xmlChildrenNode;
+	sm = QOF_CHAR_MATCH_ANY;
+
+	for (node=xp; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+
+		/* char pred doesn't have GET_HOW */
+		GET_MATCH2 (sm, "qofquery:char-match", 
+		            CHAR_MATCH, ANY, NONE);
+		GET_STR (0, char_list=, "qofquery:char-list");
+		{}
+	}
+
+	pred = qof_query_char_predicate (sm, char_list);
+	return pred;
+}
+
+/* =============================================================== */
+
+static QofQueryPredData *
+qof_query_pred_numeric_from_xml (xmlNodePtr root)
+{
+	QofQueryPredData *pred;
+	xmlNodePtr node;
+	QofQueryCompare how;
+	QofNumericMatch sm;
+	gnc_numeric num;
+	xmlNodePtr xp;
+	
+	xp = root->xmlChildrenNode;
+	how = QOF_COMPARE_EQUAL;
+	sm = QOF_NUMERIC_MATCH_ANY;
+
+	for (node=xp; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+
+		GET_HOW (how, "qofquery:compare", LT, LTE, EQUAL, GT, GTE, NEQ);
+		GET_MATCH3 (sm, "qofquery:numeric-match", 
+		            NUMERIC_MATCH, DEBIT, CREDIT, ANY);
+		GET_NUMERIC (0, num=, "qofquery:numeric");
+		{}
+	}
+
+	pred = qof_query_numeric_predicate (how, sm, num);
+	return pred;
+}
+
+/* =============================================================== */
+
+static QofQueryPredData *
+qof_query_pred_date_from_xml (xmlNodePtr root)
+{
+	xmlNodePtr xp;
+	xmlNodePtr node;
+	QofQueryCompare how;
+	QofDateMatch sm;
+	Timespec date;
+	QofQueryPredData *pred;
+	
+	xp = root->xmlChildrenNode;
+
+	how = QOF_COMPARE_EQUAL;
+	sm = QOF_DATE_MATCH_DAY;
+	date = (Timespec){0,0};
+
+	for (node=xp; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+
+		GET_HOW (how, "qofquery:compare", LT, LTE, EQUAL, GT, GTE, NEQ);
+		GET_MATCH2 (sm, "qofquery:date-match", 
+		            DATE_MATCH, NORMAL, DAY);
+		GET_DATE (0, date=, "qofquery:date");
+		{}
+	}
+
+	pred = qof_query_date_predicate (how, sm, date);
+	return pred;
+}
+
+/* =============================================================== */
+
+static QofQueryPredData *
+qof_query_pred_string_from_xml (xmlNodePtr root)
+{
+	QofQueryPredData *pred;
+	xmlNodePtr xp;
+	xmlNodePtr node;
+	QofQueryCompare how;
+	QofStringMatch sm;
+	gboolean is_regex;
+	const char *pstr;
+	
+	xp = root->xmlChildrenNode;
+	how = QOF_COMPARE_EQUAL;
+	sm = QOF_STRING_MATCH_CASEINSENSITIVE;
+	is_regex = FALSE;
+	pstr = NULL;
+
+	for (node=xp; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+
+		GET_HOW (how, "qofquery:compare", LT, LTE, EQUAL, GT, GTE, NEQ);
+		GET_BOOL (0, is_regex=, "qofquery:is-regex");
+		GET_STR (0, pstr=, "qofquery:string");
+		GET_MATCH2 (sm, "qofquery:string-match", 
+		            STRING_MATCH, NORMAL, CASEINSENSITIVE);
+		{}
+	}
+	pred = qof_query_string_predicate (how, pstr, sm , is_regex);
+	return pred;
+}
+
+/* =============================================================== */
+
+static GSList * 
+qof_query_param_path_from_xml (xmlNodePtr root)
+{
+	xmlNodePtr pterms;
+	GSList *plist;
+	xmlNodePtr node;
+
+	pterms = root->xmlChildrenNode;
+	plist = NULL;
+	for (node=pterms; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+
+		if (0 == strcmp (node->name, "qofquery:param"))
+		{
+			const char *str = GET_TEXT (node);
+			plist = g_slist_append (plist, CACHE_INSERT(str));
+		}
+	}
+	return plist;
+}
+
+/* =============================================================== */
+
+static void 
+qof_query_term_from_xml (QofQuery *q, xmlNodePtr root)
+{
+	xmlNodePtr node;
+	xmlNodePtr term;
+	QofQueryPredData *pred;
+	GSList *path;
+	QofQuery *qt;
+	QofQuery *qinv;
+	
+	pred = NULL;
+	path = NULL;
+	term = root->xmlChildrenNode;
+	for (node=term; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+		if (0 == strcmp (node->name, "qofquery:invert"))
+		{
+			qt = qof_query_create();
+			qof_query_term_from_xml (qt, node);
+			qinv = qof_query_invert (qt);
+			qof_query_merge_in_place (q, qinv, QOF_QUERY_AND);
+			qof_query_destroy (qinv);
+			qof_query_destroy (qt);
+			return;
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:param-path"))
+		{
+			path = qof_query_param_path_from_xml (node);
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:pred-string"))
+		{
+			pred = qof_query_pred_string_from_xml (node);
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:pred-date"))
+		{
+			pred = qof_query_pred_date_from_xml (node);
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:pred-numeric"))
+		{
+			pred = qof_query_pred_numeric_from_xml (node);
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:pred-int32"))
+		{
+			pred = qof_query_pred_int32_from_xml (node);
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:pred-int64"))
+		{
+			pred = qof_query_pred_int64_from_xml (node);
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:pred-double"))
+		{
+			pred = qof_query_pred_double_from_xml (node);
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:pred-boolean"))
+		{
+			pred = qof_query_pred_boolean_from_xml (node);
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:pred-char"))
+		{
+			pred = qof_query_pred_char_from_xml (node);
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:pred-guid"))
+		{
+			pred = qof_query_pred_guid_from_xml (node);
+		}
+		else
+		if (0 == strcmp (node->name, "qofquery:pred-kvp"))
+		{
+			pred = qof_query_pred_kvp_from_xml (node);
+		}
+		else
+		{
+			// warning unhandled predicate type
+		}
+	}
+
+	/* At this level, the terms should always be anded */
+	qof_query_add_term (q, path, pred, QOF_QUERY_AND);
+}
+
+/* =============================================================== */
+
+static void 
+qof_query_and_terms_from_xml (QofQuery *q, xmlNodePtr root)
+{
+	xmlNodePtr andterms;
+	xmlNodePtr node;
+	
+	andterms = root->xmlChildrenNode;
+	for (node=andterms; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+
+		if (0 == strcmp (node->name, "qofquery:term"))
+		{
+			qof_query_term_from_xml (q, node);
+		}
+	}
+}
+
+/* =============================================================== */
+
+static void 
+qof_query_or_terms_from_xml (QofQuery *q, xmlNodePtr root)
+{
+	xmlNodePtr andterms;
+	xmlNodePtr node;
+	QofQuery *qand;
+
+	andterms = root->xmlChildrenNode;
+	for (node=andterms; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+
+		if (0 == strcmp (node->name, "qofquery:and-terms"))
+		{
+			qand = qof_query_create ();
+			qof_query_and_terms_from_xml (qand, node);
+			qof_query_merge_in_place (q, qand, QOF_QUERY_OR);
+			qof_query_destroy (qand);
+		}
+	}
+}
+
+/* =============================================================== */
+
+QofQuery *
+qof_query_from_xml (xmlNodePtr root)
+{
+	QofQuery *q;
+	xmlChar *version;
+	xmlNodePtr qpart;
+	xmlNodePtr node;
+	
+	if (!root) return NULL;
+	
+	version = xmlGetProp(root, "version");
+    if (!root->name || strcmp ("qof:qofquery", root->name))
+    {
+		// XXX something is wrong. warn ... 
+      return NULL;
+    }
+
+	q = qof_query_create ();
+
+	qpart = root->xmlChildrenNode;
+	for (node=qpart; node; node = node->next)
+	{
+		if (node->type != XML_ELEMENT_NODE) continue;
+
+		GET_STR   (q, qof_query_search_for,      "qofquery:search-for");
+		GET_INT32 (q, qof_query_set_max_results, "qofquery:max-results");
+		if (0 == strcmp (node->name, "qofquery:or-terms"))
+		{
+			qof_query_or_terms_from_xml (q, node);
+		}
+		else 
+		if (0 == strcmp (node->name, "qofquery:sort-list"))
+		{
+// XXX unfinished  I'm bored
+		}
+		else 
+		{
+			// XXX unknown node type tell someone about it
+		}
+	}
+
+	return q;
+}
+
+/* =============================================================== */
+
+#ifdef UNIT_TEST
+
+#include <stdio.h>
+#include <qof/qofsql.h>
+
+int main (int argc, char * argv[])
+{
+	QofQuery *q, *qnew;
+	QofSqlQuery *sq;
+	xmlNodePtr topnode;
+
+	guid_init();
+	qof_query_init();
+	qof_object_initialize ();
+
+	static QofParam params[] = {
+		{ "adate", QOF_TYPE_DATE, NULL, NULL},
+		{ "aint", QOF_TYPE_INT32, NULL, NULL},
+		{ "aint64", QOF_TYPE_INT64, NULL, NULL},
+		{ "aflt", QOF_TYPE_DOUBLE, NULL, NULL},
+		{ "abool", QOF_TYPE_BOOLEAN, NULL, NULL},
+		{ "astr", QOF_TYPE_STRING, NULL, NULL},
+		{ "adate", QOF_TYPE_DATE, NULL, NULL},
+		{ "anum", QOF_TYPE_NUMERIC, NULL, NULL},
+		{ "achar", QOF_TYPE_CHAR, NULL, NULL},
+		{ "aguid", QOF_TYPE_GUID, NULL, NULL},
+		{ "akvp", QOF_TYPE_KVP, NULL, NULL},
+		{ NULL },
+   };
+
+	qof_class_register ("GncABC", NULL, params);
+	sq = qof_sql_query_new();
+
+	qof_sql_query_parse (sq, 
+	    "SELECT * from GncABC WHERE aint = 123 " 
+	    "and not aint64 = 6123123456789 "
+	    "or abool = TRUE "
+	    "and not aflt >= \'3.14159265358979\' "
+	    "and not astr=\'asdf\' "
+	    "and adate<\'01-01-01\' "
+	    "or anum<\'12301/100\' "
+	    "or achar != asdf "
+	    "and aguid != abcdef01234567890fedcba987654321 "
+	    "and akvp != \'/some/path:abcdef01234567890fedcba987654321\' "
+	    "and not akvp != \'/some/path/glop:1234\' "
+	    "and akvp = \'/arf/arf/arf:10.234\' "
+	    "and akvp != \'/some/other/path:qwerty1234uiop\' "
+	    "and not akvp = \'/some/final/path:123401/100\' "
+	    );
+	// qof_sql_query_parse (sq, "SELECT * from GncABC;");
+	q = qof_sql_query_get_query (sq);
+
+	qof_query_print (q);
+
+   xmlNodePtr topnode = qof_query_to_xml (q);
+
+	qnew = qof_query_from_xml (topnode);
+	printf ("  ------------------------------------------------------- \n");
+	qof_query_print (qnew);
+
+   /* If the before and after trees are the same, the test pases. */
+	gboolean eq = qof_query_equal (q, qnew);
+	printf ("Are the two equal? answer=%d\n", eq);
+
+#define DOPRINT 1
+#ifdef DOPRINT
+   xmlDocPtr doc = doc = xmlNewDoc("1.0");
+	xmlDocSetRootElement(doc,topnode);
+
+	xmlChar *xbuf;
+	int bufsz;
+	xmlDocDumpFormatMemory (doc, &xbuf, &bufsz, 1);
+
+	printf ("%s\n", xbuf);
+	xmlFree (xbuf);
+	xmlFreeDoc(doc);
+#endif
+
+	return 0;
+}
+
+#endif /* UNIT_TEST */
+
+/* ======================== END OF FILE =================== */

Added: gnucash/trunk/lib/libqof/qof/qofquery-deserial.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery-deserial.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery-deserial.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,47 @@
+/********************************************************************\
+ * qofquery-deserial.h -- Convert Qof-Query XML to QofQuery         *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org>               *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/* 
+ qofquery-deserial.h
+ Convert Qof-Query XML to QofQuery 
+author Copyright (C) 2004 Linas Vepstas <linas at linas.org>
+*/
+
+#ifndef QOF_QUERY_DESERIAL_H
+#define QOF_QUERY_DESERIAL_H
+
+#include "qofquery.h"
+#include <libxml/tree.h>
+
+/*
+    Qof Queries can be converted to and from XML so that they
+    can be sent from here to there. This file implements the
+    routine needed to convert the XML back into a C struct.
+
+    Unfinished. XXX Why is this easier than reading a text/sql
+    file? 
+
+ */
+/* Given an XML tree, reconstruct and return the equivalent query. */
+QofQuery *qof_query_from_xml (xmlNodePtr);
+
+#endif /* QOF_QUERY_DESERIAL_H */

Added: gnucash/trunk/lib/libqof/qof/qofquery-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,65 @@
+/********************************************************************\
+ * qofquery-p.h -- internal/private API for finding objects         *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#ifndef QOF_QUERY_P_H
+#define QOF_QUERY_P_H
+
+#include "qofquery.h"
+
+typedef struct _QofQueryTerm QofQueryTerm;
+typedef struct _QofQuerySort QofQuerySort;
+
+/* Functions to get Query information */
+int qof_query_get_max_results (QofQuery *q);
+
+
+/* Functions to get and look at QueryTerms */
+
+/* This returns a List of List of Query Terms.  Each list of Query
+ * Terms are ANDed together, and each list of ANDed terms are ORed
+ * together.  So, what is returned is the 'or' list of 'and' lists
+ * of query term objects.
+ *
+ * Note that you should NOT modify this list in any way.  It belongs
+ * to the query.
+ */
+GList * qof_query_get_terms (QofQuery *q);
+
+GSList * qof_query_term_get_param_path (QofQueryTerm *queryterm);
+QofQueryPredData *qof_query_term_get_pred_data (QofQueryTerm *queryterm);
+gboolean qof_query_term_is_inverted (QofQueryTerm *queryterm);
+
+
+/* Functions to get and look at QuerySorts */
+
+/* This function returns the primary, secondary, and tertiary sorts.
+ * These are part of the query and should NOT be changed!
+ */
+void qof_query_get_sorts (QofQuery *q, QofQuerySort **primary,
+                       QofQuerySort **secondary, QofQuerySort **tertiary);
+
+GSList * qof_query_sort_get_param_path (QofQuerySort *querysort);
+gint qof_query_sort_get_sort_options (QofQuerySort *querysort);
+gboolean qof_query_sort_get_increasing (QofQuerySort *querysort);
+
+#endif /* QOF_QUERY_P_H */

Added: gnucash/trunk/lib/libqof/qof/qofquery-serialize.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery-serialize.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery-serialize.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,597 @@
+/********************************************************************\
+ * qofquery-serialize.c -- Convert QofQuery to XML                  *
+ * Copyright (C) 2001,2002,2004 Linas Vepstas <linas at linas.org>     *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#include "config.h"
+
+#include "qofquery-serialize.h"
+#include "qofquery-p.h"
+#include "qofquerycore-p.h"
+#include "kvp_frame.h"
+
+/* ======================================================= */
+
+#define PUT_STR(TOK,VAL) {                           \
+   xmlNodePtr node;                                  \
+   const char * str = (VAL);                         \
+   if (str && 0 != str[0])                           \
+   {                                                 \
+      node = xmlNewNode (NULL, TOK);                 \
+      xmlNodeAddContent(node, str);                  \
+      xmlAddChild (topnode, node);                   \
+   }                                                 \
+}
+
+#define PUT_INT32(TOK,VAL) {                         \
+   xmlNodePtr node;                                  \
+   char buff[80];                                    \
+   g_snprintf (buff, sizeof(buff), "%d", (VAL));     \
+   node = xmlNewNode (NULL, TOK);                    \
+   xmlNodeAddContent(node, buff);                    \
+   xmlAddChild (topnode, node);                      \
+}
+
+#define PUT_INT64(TOK,VAL) {                         \
+   xmlNodePtr node;                                  \
+   char buff[80];                                    \
+   g_snprintf (buff, sizeof(buff), "%" G_GINT64_FORMAT, (VAL));   \
+   node = xmlNewNode (NULL, TOK);                    \
+   xmlNodeAddContent(node, buff);                    \
+   xmlAddChild (topnode, node);                      \
+}
+
+#define PUT_DBL(TOK,VAL) {                           \
+   xmlNodePtr node;                                  \
+   char buff[80];                                    \
+   g_snprintf (buff, sizeof(buff), "%.18g", (VAL));  \
+   node = xmlNewNode (NULL, TOK);                    \
+   xmlNodeAddContent(node, buff);                    \
+   xmlAddChild (topnode, node);                      \
+}
+
+#define PUT_GUID(TOK,VAL) {                          \
+   xmlNodePtr node;                                  \
+   char buff[80];                                    \
+   guid_to_string_buff ((VAL), buff);                \
+   node = xmlNewNode (NULL, TOK);                    \
+   xmlNodeAddContent(node, buff);                    \
+   xmlAddChild (topnode, node);                      \
+}
+
+#define PUT_DATE(TOK,VAL) {                          \
+   xmlNodePtr node;                                  \
+   char buff[80];                                    \
+   gnc_timespec_to_iso8601_buff ((VAL), buff);       \
+   node = xmlNewNode (NULL, TOK);                    \
+   xmlNodeAddContent(node, buff);                    \
+   xmlAddChild (topnode, node);                      \
+}
+
+#define PUT_NUMERIC(TOK,VAL) {                       \
+   xmlNodePtr node;                                  \
+   char *str;                                        \
+   str = gnc_numeric_to_string (VAL);                \
+   node = xmlNewNode (NULL, TOK);                    \
+   xmlNodeAddContent(node, str);                     \
+   g_free (str);                                     \
+   xmlAddChild (topnode, node);                      \
+}
+
+#define PUT_BOOL(TOK,VAL) {                          \
+   xmlNodePtr node;                                  \
+   gboolean boll = (VAL);                            \
+   node = xmlNewNode (NULL, TOK);                    \
+   if (boll) {                                       \
+      xmlNodeAddContent(node, "T");                  \
+   } else {                                          \
+      xmlNodeAddContent(node, "F");                  \
+   }                                                 \
+   xmlAddChild (topnode, node);                      \
+}
+
+#define PUT_HOW(TOK,VAL,A,B,C,D,E,F) {               \
+   xmlNodePtr node;                                  \
+   const char * str = "EQUAL";                       \
+   switch (VAL)                                      \
+   {                                                 \
+      case QOF_COMPARE_##A: str = #A; break;         \
+      case QOF_COMPARE_##B: str = #B; break;         \
+      case QOF_COMPARE_##C: str = #C; break;         \
+      case QOF_COMPARE_##D: str = #D; break;         \
+      case QOF_COMPARE_##E: str = #E; break;         \
+      case QOF_COMPARE_##F: str = #F; break;         \
+   }                                                 \
+   node = xmlNewNode (NULL, TOK);                    \
+   xmlNodeAddContent(node, str);                     \
+   xmlAddChild (topnode, node);                      \
+}
+
+#define PUT_MATCH2(TOK,VAL,PFX,A,B) {                \
+   xmlNodePtr node;                                  \
+   const char * str = #A;                            \
+   switch (VAL)                                      \
+   {                                                 \
+      case QOF_##PFX##_##A: str = #A; break;         \
+      case QOF_##PFX##_##B: str = #B; break;         \
+   }                                                 \
+   node = xmlNewNode (NULL, TOK);                    \
+   xmlNodeAddContent(node, str);                     \
+   xmlAddChild (topnode, node);                      \
+}
+
+#define PUT_MATCH3(TOK,VAL,PFX,A,B,C) {              \
+   xmlNodePtr node;                                  \
+   const char * str = #A;                            \
+   switch (VAL)                                      \
+   {                                                 \
+      case QOF_##PFX##_##A: str = #A; break;         \
+      case QOF_##PFX##_##B: str = #B; break;         \
+      case QOF_##PFX##_##C: str = #C; break;         \
+   }                                                 \
+   node = xmlNewNode (NULL, TOK);                    \
+   xmlNodeAddContent(node, str);                     \
+   xmlAddChild (topnode, node);                      \
+}
+
+#define PUT_MATCH5(TOK,VAL,PFX,A,B,C,D,E) {          \
+   xmlNodePtr node;                                  \
+   const char * str = #A;                            \
+   switch (VAL)                                      \
+   {                                                 \
+      case QOF_##PFX##_##A: str = #A; break;         \
+      case QOF_##PFX##_##B: str = #B; break;         \
+      case QOF_##PFX##_##C: str = #C; break;         \
+      case QOF_##PFX##_##D: str = #D; break;         \
+      case QOF_##PFX##_##E: str = #E; break;         \
+   }                                                 \
+   node = xmlNewNode (NULL, TOK);                    \
+   xmlNodeAddContent(node, str);                     \
+   xmlAddChild (topnode, node);                      \
+}
+
+/* ======================================================= */
+
+static void
+qof_kvp_value_to_xml (KvpValue *kval, xmlNodePtr topnode)
+{
+	KvpValueType kvt = kvp_value_get_type (kval);
+
+	switch (kvt)
+	{
+		case KVP_TYPE_GINT64:
+			PUT_INT64 ("qofquery:int64", kvp_value_get_gint64(kval));
+			break;
+		case KVP_TYPE_DOUBLE:
+			PUT_DBL ("qofquery:double", kvp_value_get_double(kval));
+			break;
+		case KVP_TYPE_NUMERIC:
+			PUT_NUMERIC ("qofquery:numeric", kvp_value_get_numeric(kval));
+			break;
+		case KVP_TYPE_GUID:
+			PUT_GUID ("qofquery:guid", kvp_value_get_guid(kval));
+			break;
+		case KVP_TYPE_STRING:
+			PUT_STR ("qofquery:string", kvp_value_get_string(kval));
+			break;
+		case KVP_TYPE_TIMESPEC:
+			PUT_DATE ("qofquery:date", kvp_value_get_timespec(kval));
+			break;
+		case KVP_TYPE_BINARY:
+		case KVP_TYPE_GLIST:
+		case KVP_TYPE_FRAME:
+			// XXX don't know how to support these.
+			break;
+	}
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_pred_data_to_xml (QofQueryPredData *pd)
+{
+	GList *n;
+	GSList *ns;
+	xmlNodePtr topnode;
+	query_guid_t pdata_g;
+	query_string_t pdata_s;
+	query_numeric_t pdata_n;
+	query_kvp_t pdata_k;
+	query_date_t pdata_d;
+	query_int64_t pdata_i64;
+	query_int32_t pdata_i32;
+	query_double_t pdata_db;
+	query_boolean_t pdata_bool;
+	query_char_t pdata_c;
+	
+	if (!safe_strcmp (pd->type_name, QOF_TYPE_GUID))
+	{
+		topnode = xmlNewNode (NULL, "qofquery:pred-guid");
+		/* GUID Predicate doesn't do a PUT_HOW */
+
+		pdata_g = (query_guid_t) pd;
+		PUT_MATCH5("qofquery:guid-match", pdata_g->options, 
+		                GUID_MATCH, ANY, ALL, NONE, NULL, LIST_ANY);
+
+		for (n = pdata_g->guids; n; n = n->next)
+		{
+			PUT_GUID ("qofquery:guid", n->data);
+		}
+		return topnode;
+	}
+	if (!safe_strcmp (pd->type_name, QOF_TYPE_STRING))
+	{
+		topnode = xmlNewNode (NULL, "qofquery:pred-string");
+		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+		pdata_s = (query_string_t) pd;
+		PUT_MATCH2("qofquery:string-match", pdata_s->options,
+                       STRING_MATCH, NORMAL, CASEINSENSITIVE);
+		PUT_BOOL ("qofquery:is-regex", pdata_s->is_regex);
+		PUT_STR ("qofquery:string", pdata_s->matchstring);
+		return topnode;
+	}
+	if (!safe_strcmp (pd->type_name, QOF_TYPE_NUMERIC))
+	{
+		topnode = xmlNewNode (NULL, "qofquery:pred-numeric");
+		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+		pdata_n = (query_numeric_t) pd;
+		PUT_MATCH3("qofquery:numeric-match", pdata_n->options,
+		                 NUMERIC_MATCH, DEBIT, CREDIT, ANY);
+		
+		PUT_NUMERIC ("qofquery:numeric", pdata_n->amount);
+		return topnode;
+	}
+	if (!safe_strcmp (pd->type_name, QOF_TYPE_KVP))
+	{
+		topnode = xmlNewNode (NULL, "qofquery:pred-kvp");
+		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+		pdata_k = (query_kvp_t) pd;
+		for (ns=pdata_k->path; ns; ns=ns->next)
+		{
+			PUT_STR ("qofquery:kvp-path", ns->data);
+		}
+		qof_kvp_value_to_xml (pdata_k->value, topnode);
+		return topnode;
+	}
+	if (!safe_strcmp (pd->type_name, QOF_TYPE_DATE))
+	{
+		topnode = xmlNewNode (NULL, "qofquery:pred-date");
+		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+		pdata_d = (query_date_t) pd;
+		
+		PUT_MATCH2("qofquery:date-match", pdata_d->options,
+		                 DATE_MATCH, NORMAL, DAY);
+
+		PUT_DATE ("qofquery:date", pdata_d->date);
+		return topnode;
+	}
+	if (!safe_strcmp (pd->type_name, QOF_TYPE_INT64))
+	{
+		topnode = xmlNewNode (NULL, "qofquery:pred-int64");
+		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+		pdata_i64 = (query_int64_t) pd;
+		PUT_INT64 ("qofquery:int64", pdata_i64->val);
+		return topnode;
+	}
+	if (!safe_strcmp (pd->type_name, QOF_TYPE_INT32))
+	{
+		topnode = xmlNewNode (NULL, "qofquery:pred-int32");
+		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+		pdata_i32 = (query_int32_t) pd;
+		
+		PUT_INT32 ("qofquery:int32", pdata_i32->val);
+		return topnode;
+	}
+	if (!safe_strcmp (pd->type_name, QOF_TYPE_DOUBLE))
+	{
+		topnode = xmlNewNode (NULL, "qofquery:pred-double");
+		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+		pdata_db = (query_double_t) pd;
+		
+		PUT_DBL ("qofquery:double", pdata_db->val);
+		return topnode;
+	}
+	if (!safe_strcmp (pd->type_name, QOF_TYPE_BOOLEAN))
+	{
+		topnode = xmlNewNode (NULL, "qofquery:pred-boolean");
+		PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+		pdata_bool = (query_boolean_t) pd;
+		
+		PUT_BOOL ("qofquery:boolean", pdata_bool->val);
+		return topnode;
+	}
+	if (!safe_strcmp (pd->type_name, QOF_TYPE_CHAR))
+	{
+		topnode = xmlNewNode (NULL, "qofquery:pred-char");
+		/* There is no PUT_HOW for char-match */
+		pdata_c = (query_char_t) pd;
+		
+		PUT_MATCH2("qofquery:char-match", pdata_c->options,
+		                 CHAR_MATCH, ANY, NONE);
+		
+		PUT_STR ("qofquery:char-list", pdata_c->char_list);
+		return topnode;
+	}
+	return NULL;
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_param_path_to_xml (GSList *param_path)
+{
+	xmlNodePtr topnode;
+	GSList *n;
+	QofIdTypeConst path;
+	
+	n = param_path;
+	topnode = xmlNewNode (NULL, "qofquery:param-path");
+	for ( ; n; n=n->next)
+	{
+		path = n->data;
+		if (!path) continue;
+		PUT_STR ("qofquery:param", path);
+	}
+	return topnode;
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_one_term_to_xml (QofQueryTerm *qt)
+{
+	xmlNodePtr node;
+	xmlNodePtr term;
+	xmlNodePtr topnode;
+	gboolean invert;
+	GSList *path;
+	QofQueryPredData *pd;
+
+	invert = qof_query_term_is_inverted (qt);
+	term = xmlNewNode (NULL, "qofquery:term");
+	topnode = term;
+	path = qof_query_term_get_param_path (qt);
+	pd = qof_query_term_get_pred_data (qt);
+	if (invert)
+	{
+		/* inverter becomes new top mode */
+		topnode = xmlNewNode (NULL, "qofquery:invert");
+		xmlAddChild (term, topnode);
+	}
+
+	node = qof_query_param_path_to_xml (path);
+	if (node) xmlAddChild (topnode, node);
+
+	node = qof_query_pred_data_to_xml (pd);
+	if (node) xmlAddChild (topnode, node);
+
+	return term;
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_and_terms_to_xml (GList *and_terms)
+{
+	xmlNodePtr terms;
+	GList *n;
+	QofQueryTerm *qt;
+	xmlNodePtr t;
+	
+	terms = xmlNewNode (NULL, "qofquery:and-terms");
+	n = and_terms;
+	for ( ; n; n=n->next)
+	{
+		qt = n->data;
+		if (!qt) continue;
+
+		t = qof_query_one_term_to_xml (n->data);
+		if (t) xmlAddChild (terms, t);
+	}
+	return terms;
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_terms_to_xml (QofQuery *q)
+{
+	xmlNodePtr terms;
+	GList *n;
+	xmlNodePtr andt;
+	
+	terms = NULL;
+	n = qof_query_get_terms (q);
+	if (!n) return NULL;
+	terms = xmlNewNode (NULL, "qofquery:or-terms");
+
+	for ( ; n; n=n->next)
+	{
+		andt = qof_query_and_terms_to_xml (n->data);
+		if (andt) xmlAddChild (terms, andt);
+	}
+	return terms;
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_sorts_to_xml (QofQuery *q)
+{
+	QofQuerySort *s[3];
+	xmlNodePtr sortlist;
+	GSList *plist;
+	xmlNodePtr sort;
+	xmlNodePtr topnode;
+	gboolean increasing;
+	gint opt;
+	xmlNodePtr pl;
+	int i;
+	
+	qof_query_get_sorts (q, &s[0], &s[1], &s[2]);
+
+	if (NULL == s[0]) return NULL;
+
+	sortlist = xmlNewNode (NULL, "qofquery:sort-list");
+	for (i=0; i<3; i++)
+	{
+		if (NULL == s[i]) continue;
+
+		plist = qof_query_sort_get_param_path (s[i]);
+		if (!plist) continue;
+
+		sort = xmlNewNode (NULL, "qofquery:sort");
+		xmlAddChild (sortlist, sort);
+
+		topnode = sort;
+
+		increasing = qof_query_sort_get_increasing (s[i]);
+		PUT_STR ("qofquery:order", increasing ? "DESCENDING" : "ASCENDING");
+
+		opt = qof_query_sort_get_sort_options (s[i]);
+		PUT_INT32 ("qofquery:options", opt);
+
+		pl = qof_query_param_path_to_xml (plist);
+		if (pl) xmlAddChild (sort, pl);
+	}
+
+	return sortlist;
+}
+
+/* ======================================================= */
+
+static void
+do_qof_query_to_xml (QofQuery *q, xmlNodePtr topnode)
+{
+	QofIdType search_for;
+	xmlNodePtr terms;
+	xmlNodePtr sorts;
+	gint max_results;
+	
+	search_for = qof_query_get_search_for (q);
+	PUT_STR ("qofquery:search-for", search_for);
+
+	terms = qof_query_terms_to_xml(q);
+	if (terms) xmlAddChild (topnode, terms);
+
+	sorts = qof_query_sorts_to_xml (q);
+	if (sorts) xmlAddChild (topnode, sorts);
+
+	max_results = qof_query_get_max_results (q);
+	PUT_INT32 ("qofquery:max-results", max_results);
+}
+
+/* ======================================================= */
+
+xmlNodePtr
+qof_query_to_xml (QofQuery *q)
+{
+	xmlNodePtr topnode;
+	xmlNodePtr node;
+	xmlNsPtr   ns;
+
+	topnode = xmlNewNode(NULL, "qof:qofquery");
+	xmlSetProp(topnode, "version", "1.0.1");
+
+	// XXX path to DTD is wrong
+	// ns = xmlNewNs (topnode, "file:" "/usr/share/lib" "/qofquery.dtd", "qof");
+
+	do_qof_query_to_xml (q, topnode);
+
+	return topnode;
+}
+
+/* =============================================================== */
+
+#ifdef UNIT_TEST
+
+#include <stdio.h>
+#include <qof/qofsql.h>
+
+int main (int argc, char * argv[])
+{
+	QofQuery *q;
+	QofSqlQuery *sq;
+	xmlDocPtr doc;
+	xmlNodePtr topnode;
+	xmlChar *xbuf;
+	int bufsz;
+	xmlOutputBufferPtr xbuf;
+	
+	qof_query_init();
+	qof_object_initialize ();
+
+	static QofParam params[] = {
+		{ "adate",  QOF_TYPE_DATE, NULL, NULL},
+		{ "aint",   QOF_TYPE_INT32, NULL, NULL},
+		{ "aint64", QOF_TYPE_INT64, NULL, NULL},
+		{ "astr",   QOF_TYPE_STRING, NULL, NULL},
+		{ NULL },
+	};
+
+	qof_class_register ("GncABC", NULL, params);
+	sq = qof_sql_query_new();
+
+	qof_sql_query_parse (sq, 
+	    "SELECT * from GncABC WHERE aint = 123 "
+	    "or not astr=\'asdf\' "
+	    "and aint64 = 9876123456789;");
+	// qof_sql_query_parse (sq, "SELECT * from GncABC;");
+	q = qof_sql_query_get_query (sq);
+
+	qof_query_print (q);
+
+	doc = doc = xmlNewDoc("1.0");
+	topnode = qof_query_to_xml (q);
+	xmlDocSetRootElement(doc,topnode);
+
+	xmlDocDumpFormatMemory (doc, &xbuf, &bufsz, 1);
+
+	printf ("%s\n", xbuf);
+	xmlFree (xbuf);
+	xmlFreeDoc(doc);
+
+#if 0
+printf ("duude\n");
+	// xmlOutputBufferPtr xbuf = xmlAllocOutputBuffer (enc);
+	xbuf = xmlOutputBufferCreateFile (stdout, NULL);
+printf ("duude\n");
+
+	xbuf = xmlOutputBufferCreateFd (1, NULL);
+printf ("duude\n");
+	xmlNodeDumpOutput (xbuf, NULL, topnode, 99, 99, "iso-8859-1");
+	// xmlElemDump (stdout, NULL, topnode);
+#endif
+
+	return 0;
+}
+
+#endif /* UNIT_TEST */
+
+/* ======================== END OF FILE =================== */

Added: gnucash/trunk/lib/libqof/qof/qofquery-serialize.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery-serialize.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery-serialize.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,42 @@
+/********************************************************************\
+ * qofquery-serialize.h -- Convert QofQuery to XML                  *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org>               *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+/* qofquery-serialize.h
+   Convert QofQuery to XML
+   Copyright (C) 2001,2002,2004 Linas Vepstas <linas at linas.org>
+ */
+
+#ifndef QOF_QUERY_SERIALIZE_H
+#define QOF_QUERY_SERIALIZE_H
+
+#include "qofquery.h"
+#include <libxml/tree.h>
+
+/* XML Serialize Queries to/from XML */
+
+/* Take the query passed as input, and serialize it into XML.
+ *  The DTD used will be a very qofquery specific DTD
+ *  This is NOT the XQuery XML.
+ */
+xmlNodePtr qof_query_to_xml (QofQuery *q);
+
+#endif /* QOF_QUERY_SERIALIZE_H */

Added: gnucash/trunk/lib/libqof/qof/qofquery.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1879 @@
+/********************************************************************\
+ * qof_query.c -- Implement predicate API for searching for objects *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <time.h>
+#include <glib.h>
+#include <regex.h>
+#include <string.h>
+
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+
+#include "qofbackend-p.h"
+#include "qofbook.h"
+#include "qofbook-p.h"
+#include "qofclass.h"
+#include "qofclass-p.h"
+#include "qofobject.h"
+#include "qofquery.h"
+#include "qofquery-p.h"
+#include "qofquerycore.h"
+#include "qofquerycore-p.h"
+
+static QofLogModule log_module = QOF_MOD_QUERY;
+
+struct _QofQueryTerm 
+{
+  GSList *                param_list;
+  QofQueryPredData        *pdata;
+  gboolean                invert;
+
+  /* These values are filled in during "compilation" of the query
+   * term, based upon the obj_name, param_name, and searched-for
+   * object type.  If conv_fcn is NULL, then we don't know how to
+   * convert types.
+   */
+  GSList *                param_fcns;
+  QofQueryPredicateFunc   pred_fcn;
+};
+
+struct _QofQuerySort 
+{
+  GSList *            param_list;
+  gint                options;
+  gboolean            increasing;
+
+  /* These values are filled in during "compilation" of the query
+   * term, based upon the obj_name, param_name, and searched-for
+   * object type.  If conv_fcn is NULL, then we don't know how to
+   * convert types.
+   */
+  gboolean            use_default;
+  GSList *            param_fcns;     /* Chain of paramters to walk */
+  QofSortFunc         obj_cmp;        /* In case you are comparing objects */
+  QofCompareFunc      comp_fcn;       /* When you are comparing core types */
+};
+
+/* The QUERY structure */
+struct _QofQuery 
+{
+  /* The object type that we're searching for */
+  QofIdType         search_for;
+
+  /* terms is a list of the OR-terms in a sum-of-products 
+   * logical expression. */
+  GList *           terms;  
+
+  /* sorting and chopping is independent of the search filter */
+
+  QofQuerySort      primary_sort;
+  QofQuerySort      secondary_sort;
+  QofQuerySort      tertiary_sort;
+  QofSortFunc       defaultSort;        /* <- Computed from search_for */
+
+  /* The maximum number of results to return */
+  int               max_results;
+
+  /* list of books that will be participating in the query */
+  GList *           books;
+
+  /* a map of book to backend-compiled queries */
+  GHashTable*       be_compiled;
+
+  /* cache the results so we don't have to run the whole search 
+   * again until it's really necessary */
+  int               changed;
+
+  GList *           results;
+};
+
+typedef struct _QofQueryCB 
+{
+  QofQuery *        query;
+  GList *           list;
+  int               count;
+} QofQueryCB;
+
+/* initial_term will be owned by the new Query */
+static void query_init (QofQuery *q, QofQueryTerm *initial_term)
+{
+  GList * or = NULL;
+  GList *and = NULL;
+  GHashTable *ht;
+
+  if (initial_term) {
+    or = g_list_alloc ();
+    and = g_list_alloc ();
+    and->data = initial_term;
+    or->data = and;
+  }
+
+  if(q->terms)
+    qof_query_clear (q);
+
+  g_list_free (q->results);
+  g_list_free (q->books);
+
+  g_slist_free (q->primary_sort.param_list);
+  g_slist_free (q->secondary_sort.param_list);
+  g_slist_free (q->tertiary_sort.param_list);
+
+  g_slist_free (q->primary_sort.param_fcns);
+  g_slist_free (q->secondary_sort.param_fcns);
+  g_slist_free (q->tertiary_sort.param_fcns);
+
+  ht = q->be_compiled;
+  memset (q, 0, sizeof (*q));
+  q->be_compiled = ht;
+
+  q->terms = or;
+  q->changed = 1;
+  q->max_results = -1;
+
+  q->primary_sort.param_list = g_slist_prepend (NULL, QUERY_DEFAULT_SORT);
+  q->primary_sort.increasing = TRUE;
+  q->secondary_sort.increasing = TRUE;
+  q->tertiary_sort.increasing = TRUE;
+}
+
+static void swap_terms (QofQuery *q1, QofQuery *q2)
+{
+  GList *g;
+
+  if (!q1 || !q2) return;
+
+  g = q1->terms;
+  q1->terms = q2->terms;
+  q2->terms = g;
+
+  g = q1->books;
+  q1->books = q2->books;
+  q2->books = g;
+
+  q1->changed = 1;
+  q2->changed = 1;
+}
+
+static void free_query_term (QofQueryTerm *qt)
+{
+  if (!qt) return;
+
+  qof_query_core_predicate_free (qt->pdata);
+  g_slist_free (qt->param_list);
+  g_slist_free (qt->param_fcns);
+  g_free (qt);
+}
+
+static QofQueryTerm * copy_query_term (QofQueryTerm *qt)
+{
+  QofQueryTerm *new_qt;
+  if (!qt) return NULL;
+
+  new_qt = g_new0 (QofQueryTerm, 1);
+  memcpy (new_qt, qt, sizeof(QofQueryTerm));
+  new_qt->param_list = g_slist_copy (qt->param_list);
+  new_qt->param_fcns = g_slist_copy (qt->param_fcns);
+  new_qt->pdata = qof_query_core_predicate_copy (qt->pdata);
+  return new_qt;
+}
+
+static GList * copy_and_terms (GList *and_terms)
+{
+  GList *and = NULL;
+  GList *cur_and;
+
+  for(cur_and = and_terms; cur_and; cur_and = cur_and->next)
+  {
+    and = g_list_prepend(and, copy_query_term (cur_and->data));
+  }
+
+  return g_list_reverse(and);
+}
+
+static GList * 
+copy_or_terms(GList * or_terms) 
+{
+  GList * or = NULL;
+  GList * cur_or;
+
+  for(cur_or = or_terms; cur_or; cur_or = cur_or->next)
+  {
+    or = g_list_prepend(or, copy_and_terms(cur_or->data));
+  }
+
+  return g_list_reverse(or);
+}
+
+static void copy_sort (QofQuerySort *dst, const QofQuerySort *src)
+{
+  memcpy (dst, src, sizeof (*dst));
+  dst->param_list = g_slist_copy (src->param_list);
+  dst->param_fcns = g_slist_copy (src->param_fcns);
+}
+
+static void free_sort (QofQuerySort *s)
+{
+  g_slist_free (s->param_list);
+  s->param_list = NULL;
+
+  g_slist_free (s->param_fcns);
+  s->param_fcns = NULL;
+}
+
+static void free_members (QofQuery *q)
+{
+  GList * cur_or;
+
+  if (q == NULL) return;
+
+  for(cur_or = q->terms; cur_or; cur_or = cur_or->next) 
+  {
+    GList * cur_and;
+
+    for(cur_and = cur_or->data; cur_and; cur_and = cur_and->next) 
+    {
+      free_query_term(cur_and->data);
+      cur_and->data = NULL;
+    }
+
+    g_list_free(cur_or->data);
+    cur_or->data = NULL;
+  }
+
+  free_sort (&(q->primary_sort));
+  free_sort (&(q->secondary_sort));
+  free_sort (&(q->tertiary_sort));
+
+  g_list_free(q->terms);
+  q->terms = NULL;
+
+  g_list_free(q->books);
+  q->books = NULL;
+
+  g_list_free(q->results);
+  q->results = NULL;
+}
+
+static int cmp_func (QofQuerySort *sort, QofSortFunc default_sort,
+                     gconstpointer a, gconstpointer b)
+{
+  QofParam *param = NULL;
+  GSList *node;
+  gpointer conva, convb;
+
+  g_return_val_if_fail (sort, 0);
+
+  /* See if this is a default sort */
+  if (sort->use_default) 
+  {
+    if (default_sort) return default_sort ((gpointer)a, (gpointer)b);
+    return 0;
+  }
+
+  /* If no parameters, consider them equal */
+  if (!sort->param_fcns) return 0;
+
+  /* no compare function, consider the two objects equal */
+  if (!sort->comp_fcn && !sort->obj_cmp) return 0;
+  
+  /* Do the list of conversions */
+  conva = (gpointer)a;
+  convb = (gpointer)b;
+  for (node = sort->param_fcns; node; node = node->next) 
+  {
+    param = node->data;
+
+    /* The last term is really the "parameter getter",
+     * unless we're comparing objects ;) */
+    if (!node->next && !sort->obj_cmp)
+      break;
+
+    /* Do the converstions */
+    conva = (param->param_getfcn) (conva, param);
+    convb = (param->param_getfcn) (convb, param);
+  }
+
+  /* And now return the (appropriate) compare */
+  if (sort->comp_fcn)
+  {
+    int rc = sort->comp_fcn (conva, convb, sort->options, param);
+    return rc;
+  }
+
+  return sort->obj_cmp (conva, convb);
+}
+
+static QofQuery * sortQuery = NULL;
+
+static int sort_func (gconstpointer a, gconstpointer b)
+{
+  int retval;
+
+  g_return_val_if_fail (sortQuery, 0);
+
+  retval = cmp_func (&(sortQuery->primary_sort), sortQuery->defaultSort, a, b);
+  if (retval == 0) 
+  {
+    retval = cmp_func (&(sortQuery->secondary_sort), sortQuery->defaultSort,
+                       a, b);
+    if (retval == 0) 
+    {
+      retval = cmp_func (&(sortQuery->tertiary_sort), sortQuery->defaultSort,
+                         a, b);
+      return sortQuery->tertiary_sort.increasing ? retval : -retval;
+    } 
+    else 
+    {
+      return sortQuery->secondary_sort.increasing ? retval : -retval;
+    }
+  } 
+  else 
+  {
+    return sortQuery->primary_sort.increasing ? retval : -retval;
+  }
+}
+
+/* ==================================================================== */ 
+/* This is the main workhorse for performing the query.  For each
+ * object, it walks over all of the query terms to see if the 
+ * object passes the seive.
+ */
+
+static int 
+check_object (QofQuery *q, gpointer object)
+{
+  GList     * and_ptr;
+  GList     * or_ptr;
+  QofQueryTerm * qt;
+  int       and_terms_ok=1;
+  
+  ENTER (" object=%p terms=%p name=%s", 
+          object, q->terms, qof_object_printable (q->search_for, object));
+
+  for(or_ptr = q->terms; or_ptr; or_ptr = or_ptr->next) 
+  {
+    and_terms_ok = 1;
+    for(and_ptr = or_ptr->data; and_ptr; and_ptr = and_ptr->next) 
+    {
+      qt = (QofQueryTerm *)(and_ptr->data);
+      if (qt->param_fcns && qt->pred_fcn) 
+      {
+        GSList *node;
+        QofParam *param = NULL;
+        gpointer conv_obj = object;
+
+        /* iterate through the conversions */
+        for (node = qt->param_fcns; node; node = node->next) 
+        {
+          param = node->data;
+
+          /* The last term is the actual parameter getter */
+          if (!node->next) break;
+
+          conv_obj = param->param_getfcn (conv_obj, param);
+        }
+
+        if (((qt->pred_fcn)(conv_obj, param, qt->pdata)) == qt->invert) 
+        {
+          and_terms_ok = 0;
+          break;
+        }
+      } 
+      else 
+      {
+        /* XXX: Don't know how to do this conversion -- do we care? */
+      }
+    }
+    if (and_terms_ok) 
+    {
+      LEAVE (" (terms are OK)");
+      return 1;
+    }
+  }
+
+  /* If there are no terms, assume a "match any" applies.
+   * A query with no terms is still meaningful, since the user
+   * may want to get all objects, but in a particular sorted 
+   * order.
+   */
+  LEAVE (" ");
+  if (NULL == q->terms) return 1;
+  return 0;
+}
+
+/* walk the list of parameters, starting with the given object, and
+ * compile the list of parameter get-functions.  Save the last valid
+ * parameter definition in "final" and return the list of functions.
+ *
+ * returns NULL if the first parameter is bad (and final is unchanged).
+ */
+static GSList * 
+compile_params (GSList *param_list, QofIdType start_obj, 
+                QofParam const **final)
+{
+  const QofParam *objDef = NULL;
+  GSList *fcns = NULL;
+
+  ENTER ("param_list=%p id=%s", param_list, start_obj);
+  g_return_val_if_fail (param_list, NULL);
+  g_return_val_if_fail (start_obj, NULL);
+  g_return_val_if_fail (final, NULL);
+
+  for (; param_list; param_list = param_list->next) 
+  {
+    QofIdType param_name = param_list->data;
+    objDef = qof_class_get_parameter (start_obj, param_name);
+
+    /* If it doesn't exist, then we've reached the end */
+    if (!objDef) break;
+
+    /* Save off this parameter */
+    fcns = g_slist_prepend (fcns, (gpointer) objDef);
+
+    /* Save this off, just in case */
+    *final = objDef;
+
+    /* And reset for the next parameter */
+    start_obj = (QofIdType) objDef->param_type;
+  }
+
+  LEAVE ("fcns=%p", fcns);
+  return (g_slist_reverse (fcns));
+}
+
+static void 
+compile_sort (QofQuerySort *sort, QofIdType obj)
+{
+  const QofParam *resObj = NULL;
+
+  ENTER ("sort=%p id=%s params=%p", sort, obj, sort->param_list);
+  sort->use_default = FALSE;
+
+  g_slist_free (sort->param_fcns);
+  sort->param_fcns = NULL;
+  sort->comp_fcn = NULL;
+  sort->obj_cmp = NULL;
+
+  /* An empty param_list implies "no sort" */
+  if (!sort->param_list) { LEAVE (" "); return; }
+
+  /* Walk the parameter list of obtain the parameter functions */
+  sort->param_fcns = compile_params (sort->param_list, obj, &resObj);
+
+  /* If we have valid parameters, grab the compare function,
+   * If not, check if this is the default sort.
+   */
+  if (sort->param_fcns) 
+  {
+    sort->comp_fcn = qof_query_core_get_compare (resObj->param_type);
+
+    /* Hrm, perhaps this is an object compare, not a core compare? */
+    if (sort->comp_fcn == NULL)
+    {
+      sort->obj_cmp = qof_class_get_default_sort (resObj->param_type);
+    }
+  } 
+  else if (!safe_strcmp (sort->param_list->data, QUERY_DEFAULT_SORT))
+  {
+    sort->use_default = TRUE;
+  }
+  LEAVE ("sort=%p id=%s", sort, obj);
+}
+
+static void compile_terms (QofQuery *q)
+{
+  GList *or_ptr, *and_ptr, *node;
+
+  ENTER (" query=%p", q);
+  /* Find the specific functions for this Query.  Note that the
+   * Query's search_for should now be set to the new type.
+   */
+  for (or_ptr = q->terms; or_ptr; or_ptr = or_ptr->next) {
+    for (and_ptr = or_ptr->data; and_ptr; and_ptr = and_ptr->next) {
+      QofQueryTerm *qt = and_ptr->data;
+      const QofParam *resObj = NULL;
+      
+      g_slist_free (qt->param_fcns);
+      qt->param_fcns = NULL;
+
+      /* Walk the parameter list of obtain the parameter functions */
+      qt->param_fcns = compile_params (qt->param_list, q->search_for,
+                                       &resObj);
+
+      /* If we have valid parameters, grab the predicate function,
+       * If not, see if this is the default sort.
+       */
+
+      if (qt->param_fcns)
+        qt->pred_fcn = qof_query_core_get_predicate (resObj->param_type);
+      else
+        qt->pred_fcn = NULL;
+    }
+  }
+
+  /* Update the sort functions */
+  compile_sort (&(q->primary_sort), q->search_for);
+  compile_sort (&(q->secondary_sort), q->search_for);
+  compile_sort (&(q->tertiary_sort), q->search_for);
+
+  q->defaultSort = qof_class_get_default_sort (q->search_for);
+
+  /* Now compile the backend instances */
+  for (node = q->books; node; node = node->next) {
+    QofBook *book = node->data;
+    QofBackend *be = book->backend;
+
+    if (be && be->compile_query) {
+      gpointer result = (be->compile_query)(be, q);
+      if (result)
+        g_hash_table_insert (q->be_compiled, book, result);
+    }
+  }
+  LEAVE (" query=%p", q);
+}
+
+static void check_item_cb (gpointer object, gpointer user_data)
+{
+  QofQueryCB *ql = user_data;
+
+  if (!object || !ql) return;
+
+  if (check_object (ql->query, object)) {
+    ql->list = g_list_prepend (ql->list, object);
+    ql->count++;
+  }
+  return;
+}
+
+static int param_list_cmp (GSList *l1, GSList *l2)
+{
+  while (1) {
+    int ret;
+
+    /* Check the easy stuff */
+    if (!l1 && !l2) return 0;
+    if (!l1 && l2) return -1;
+    if (l1 && !l2) return 1;
+
+    ret = safe_strcmp (l1->data, l2->data);
+    if (ret)
+      return ret;
+
+    l1 = l1->next;
+    l2 = l2->next;
+  }
+}
+
+static GList * merge_books (GList *l1, GList *l2)
+{
+  GList *res = NULL;
+  GList *node;
+
+  res = g_list_copy (l1);
+
+  for (node = l2; node; node = node->next) {
+    if (g_list_index (res, node->data) == -1)
+      res = g_list_prepend (res, node->data);
+  }
+
+  return res;
+}
+
+static gboolean
+query_free_compiled (gpointer key, gpointer value, gpointer not_used)
+{
+  QofBook* book = key;
+  QofBackend* be = book->backend;
+
+  if (be && be->free_query)
+    (be->free_query)(be, value);
+
+  return TRUE;
+}
+
+/* clear out any cached query_compilations */
+static void query_clear_compiles (QofQuery *q)
+{
+  g_hash_table_foreach_remove (q->be_compiled, query_free_compiled, NULL);
+}
+
+/********************************************************************/
+/* PUBLISHED API FUNCTIONS */
+
+GSList *
+qof_query_build_param_list (char const *param, ...)
+{
+  GSList *param_list = NULL;
+  char const *this_param;
+  va_list ap;
+
+  if (!param)
+    return NULL;
+
+  va_start (ap, param);
+
+  for (this_param = param; this_param; this_param = va_arg (ap, const char *))
+    param_list = g_slist_prepend (param_list, (gpointer)this_param);
+
+  va_end (ap);
+
+  return g_slist_reverse (param_list);
+}
+
+void qof_query_add_term (QofQuery *q, GSList *param_list,                      
+                      QofQueryPredData *pred_data, QofQueryOp op)
+{
+  QofQueryTerm *qt;
+  QofQuery *qr, *qs;
+
+  if (!q || !param_list || !pred_data) return;
+
+  qt = g_new0 (QofQueryTerm, 1);
+  qt->param_list = param_list;
+  qt->pdata = pred_data;
+  qs = qof_query_create ();
+  query_init (qs, qt);
+
+  if (qof_query_has_terms (q))
+    qr = qof_query_merge (q, qs, op);
+  else
+    qr = qof_query_merge (q, qs, QOF_QUERY_OR);
+
+  swap_terms (q, qr);
+  qof_query_destroy (qs);
+  qof_query_destroy (qr);
+}
+
+void qof_query_purge_terms (QofQuery *q, GSList *param_list)
+{
+  QofQueryTerm *qt;
+  GList *or, *and;
+
+  if (!q || !param_list) return;
+
+  for (or = q->terms; or; or = or->next) {
+    for (and = or->data; and; and = and->next) {
+      qt = and->data;
+      if (!param_list_cmp (qt->param_list, param_list)) {
+        if (g_list_length (or->data) == 1) {
+          q->terms = g_list_remove_link (q->terms, or);
+          g_list_free_1 (or);
+          or = q->terms;
+          break;
+        } else {
+          or->data = g_list_remove_link (or->data, and);
+          g_list_free_1 (and);
+          and = or->data;
+          if (!and) break;
+        }
+        q->changed = 1;
+        free_query_term (qt);
+      }
+    }
+    if (!or) break;
+  }
+}
+
+GList * qof_query_run (QofQuery *q)
+{
+  GList *matching_objects = NULL;
+  GList *node;
+  int        object_count = 0;
+
+  if (!q) return NULL;
+  g_return_val_if_fail (q->search_for, NULL);
+  g_return_val_if_fail (q->books, NULL);
+  ENTER (" q=%p", q);
+
+  /* XXX: Prioritize the query terms? */
+
+  /* prepare the Query for processing */
+  if (q->changed) 
+  {
+    query_clear_compiles (q);
+    compile_terms (q);
+  }
+
+  /* Maybe log this sucker */
+  if (gnc_should_log (log_module, GNC_LOG_DETAIL)) qof_query_print (q);
+
+  /* Now run the query over all the objects and save the results */
+  {
+    QofQueryCB qcb;
+
+    memset (&qcb, 0, sizeof (qcb));
+    qcb.query = q;
+
+    /* For each book */
+    for (node=q->books; node; node=node->next) 
+    {
+      QofBook *book = node->data;
+      QofBackend *be = book->backend;
+
+      /* run the query in the backend */
+      if (be) 
+      {
+        gpointer compiled_query = g_hash_table_lookup (q->be_compiled, book);
+
+        if (compiled_query && be->run_query)
+        {
+          (be->run_query) (be, compiled_query);
+        }
+      }
+
+      /* And then iterate over all the objects */
+      qof_object_foreach (q->search_for, book, (QofEntityForeachCB) check_item_cb, &qcb);
+    }
+
+    matching_objects = qcb.list;
+    object_count = qcb.count;
+  }
+  PINFO ("matching objects=%p count=%d", matching_objects, object_count);
+
+  /* There is no absolute need to reverse this list, since it's being
+   * sorted below. However, in the common case, we will be searching
+   * in a confined location where the objects are already in order,
+   * thus reversing will put us in the correct order we want and make
+   * the sorting go much faster.
+   */
+  matching_objects = g_list_reverse(matching_objects);
+
+  /* Now sort the matching objects based on the search criteria
+   * sortQuery is an unforgivable use of static global data...  
+   * I just can't figure out how else to do this sanely.
+   */
+  if (q->primary_sort.comp_fcn || q->primary_sort.obj_cmp ||
+      (q->primary_sort.use_default && q->defaultSort))
+  {
+    sortQuery = q;
+    matching_objects = g_list_sort(matching_objects, sort_func);
+    sortQuery = NULL;
+  }
+
+  /* Crop the list to limit the number of splits. */
+  if((object_count > q->max_results) && (q->max_results > -1)) 
+  {
+    if(q->max_results > 0) 
+    {
+      GList *mptr;
+
+      /* mptr is set to the first node of what will be the new list */
+      mptr = g_list_nth(matching_objects, object_count - q->max_results);
+      /* mptr should not be NULL, but let's be safe */
+      if (mptr != NULL) 
+      {
+        if (mptr->prev != NULL) mptr->prev->next = NULL;
+        mptr->prev = NULL;
+      }
+      g_list_free(matching_objects);
+      matching_objects = mptr;
+    }
+    else 
+    { 
+      /* q->max_results == 0 */
+      g_list_free(matching_objects);
+      matching_objects = NULL;
+    }
+    object_count = q->max_results;
+  }
+  
+  q->changed = 0;
+  
+  g_list_free(q->results);
+  q->results = matching_objects;
+  
+  LEAVE (" q=%p", q);
+  return matching_objects;
+}
+
+GList *
+qof_query_last_run (QofQuery *query)
+{
+  if (!query)
+    return NULL;
+
+  return query->results;
+}
+
+void qof_query_clear (QofQuery *query)
+{
+  QofQuery *q2 = qof_query_create ();
+  swap_terms (query, q2);
+  qof_query_destroy (q2);
+
+  g_list_free (query->books);
+  query->books = NULL;
+  g_list_free (query->results);
+  query->results = NULL;
+  query->changed = 1;
+}
+
+QofQuery * qof_query_create (void)
+{
+  QofQuery *qp = g_new0 (QofQuery, 1);
+  qp->be_compiled = g_hash_table_new (g_direct_hash, g_direct_equal);
+  query_init (qp, NULL);
+  return qp;
+}
+
+void qof_query_search_for (QofQuery *q, QofIdTypeConst obj_type)
+{
+  if (!q || !obj_type)
+    return;
+
+  if (safe_strcmp (q->search_for, obj_type)) {
+    q->search_for = (QofIdType) obj_type;
+    q->changed = 1;
+  }
+}
+
+QofQuery * qof_query_create_for (QofIdTypeConst obj_type)
+{
+  QofQuery *q;
+  if (!obj_type)
+    return NULL;
+  q = qof_query_create ();
+  qof_query_search_for (q, obj_type);
+  return q;
+}
+
+int qof_query_has_terms (QofQuery *q)
+{
+  if (!q) return 0;
+  return g_list_length (q->terms);
+}
+
+int qof_query_num_terms (QofQuery *q)
+{
+  GList *o;
+  int n = 0;
+  if (!q) return 0;
+  for (o = q->terms; o; o=o->next)
+    n += g_list_length(o->data);
+  return n;
+}
+
+gboolean qof_query_has_term_type (QofQuery *q, GSList *term_param)
+{
+  GList *or;
+  GList *and;
+
+  if (!q || !term_param)
+    return FALSE;
+
+  for(or = q->terms; or; or = or->next) {
+    for(and = or->data; and; and = and->next) {
+      QofQueryTerm *qt = and->data;
+      if (!param_list_cmp (term_param, qt->param_list))
+        return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
+GSList * qof_query_get_term_type (QofQuery *q, GSList *term_param)
+{
+  GList *or;
+  GList *and;
+  GSList *results = NULL;
+
+  if (!q || !term_param)
+    return FALSE;
+
+  for(or = q->terms; or; or = or->next) {
+    for(and = or->data; and; and = and->next) {
+      QofQueryTerm *qt = and->data;
+      if (!param_list_cmp (term_param, qt->param_list))
+        results = g_slist_append(results, qt->pdata);
+    }
+  }
+
+  return results;
+}
+
+void qof_query_destroy (QofQuery *q)
+{
+  if (!q) return;
+  free_members (q);
+  query_clear_compiles (q);
+  g_hash_table_destroy (q->be_compiled);
+  g_free (q);
+}
+
+QofQuery * qof_query_copy (QofQuery *q)
+{
+  QofQuery *copy;
+  GHashTable *ht;
+
+  if (!q) return NULL;
+  copy = qof_query_create ();
+  ht = copy->be_compiled;
+  free_members (copy);
+
+  memcpy (copy, q, sizeof (QofQuery));
+
+  copy->be_compiled = ht;
+  copy->terms = copy_or_terms (q->terms);
+  copy->books = g_list_copy (q->books);
+  copy->results = g_list_copy (q->results);
+
+  copy_sort (&(copy->primary_sort), &(q->primary_sort));
+  copy_sort (&(copy->secondary_sort), &(q->secondary_sort));
+  copy_sort (&(copy->tertiary_sort), &(q->tertiary_sort));
+
+  copy->changed = 1;
+
+  return copy;
+}
+
+/********************************************************************
+ * qof_query_invert 
+ * return a newly-allocated Query object which is the 
+ * logical inverse of the original.
+ ********************************************************************/
+
+QofQuery * qof_query_invert (QofQuery *q)
+{
+  QofQuery  * retval;
+  QofQuery  * right, * left, * iright, * ileft;
+  QofQueryTerm * qt;
+  GList  * aterms;
+  GList  * cur;
+  GList  * new_oterm;
+  int    num_or_terms;
+
+  if (!q)
+    return NULL;
+
+  num_or_terms = g_list_length(q->terms);
+
+  switch(num_or_terms) 
+  {
+  case 0:
+    retval = qof_query_create();
+    retval->max_results = q->max_results;
+    break;
+
+    /* This is the DeMorgan expansion for a single AND expression. */
+    /* !(abc) = !a + !b + !c */
+  case 1:
+    retval = qof_query_create();
+    retval->max_results = q->max_results;
+    retval->books = g_list_copy (q->books);
+    retval->search_for = q->search_for;
+    retval->changed = 1;
+
+    aterms = g_list_nth_data(q->terms, 0);
+    new_oterm = NULL;
+    for(cur=aterms; cur; cur=cur->next) {
+      qt = copy_query_term(cur->data);
+      qt->invert = !(qt->invert);
+      new_oterm = g_list_append(NULL, qt);
+
+      /* g_list_append() can take forever, so let's do this for speed
+       * in "large" queries.
+       */
+      retval->terms = g_list_reverse(retval->terms);
+      retval->terms = g_list_prepend(retval->terms, new_oterm);
+      retval->terms = g_list_reverse(retval->terms);
+    }
+    break;
+
+    /* If there are multiple OR-terms, we just recurse by 
+     * breaking it down to !(a + b + c) = 
+     * !a * !(b + c) = !a * !b * !c.  */
+  default:
+    right        = qof_query_create();
+    right->terms = copy_or_terms(g_list_nth(q->terms, 1));
+
+    left         = qof_query_create();
+    left->terms  = g_list_append(NULL, 
+                                 copy_and_terms(g_list_nth_data(q->terms, 0)));
+
+    iright       = qof_query_invert(right);
+    ileft        = qof_query_invert(left);
+
+    retval = qof_query_merge(iright, ileft, QOF_QUERY_AND);
+    retval->books          = g_list_copy (q->books);
+    retval->max_results    = q->max_results;
+    retval->search_for     = q->search_for;
+    retval->changed        = 1;
+
+    qof_query_destroy(iright);
+    qof_query_destroy(ileft);
+    qof_query_destroy(right);
+    qof_query_destroy(left);
+    break;
+  }
+
+  return retval;
+}
+
+/********************************************************************
+ * qof_query_merge
+ * combine 2 Query objects by the logical operation in "op".
+ ********************************************************************/
+
+QofQuery * 
+qof_query_merge(QofQuery *q1, QofQuery *q2, QofQueryOp op)
+{
+  
+  QofQuery * retval = NULL;
+  QofQuery * i1, * i2;
+  QofQuery * t1, * t2;
+  GList * i, * j;
+  QofIdType search_for;
+
+  if(!q1) return q2;
+  if(!q2) return q1;
+
+  if (q1->search_for && q2->search_for)
+    g_return_val_if_fail (safe_strcmp (q1->search_for, q2->search_for) == 0,
+                          NULL);
+
+  search_for = (q1->search_for ? q1->search_for : q2->search_for);
+
+  /* Avoid merge surprises if op==QOF_QUERY_AND but q1 is empty.
+   * The goal of this tweak is to all the user to start with
+   * an empty q1 and then append to it recursively
+   * (and q1 (and q2 (and q3 (and q4 ....))))
+   * without bombing out because the append started with an 
+   * empty list.
+   * We do essentially the same check in qof_query_add_term()
+   * so that the first term added to an empty query doesn't screw up.
+   */
+  if ((QOF_QUERY_AND == op) && (0 == qof_query_has_terms (q1)))
+  {
+    op = QOF_QUERY_OR;
+  }
+
+  switch(op) 
+  {
+  case QOF_QUERY_OR:
+    retval = qof_query_create();
+    retval->terms = 
+      g_list_concat(copy_or_terms(q1->terms), copy_or_terms(q2->terms));
+    retval->books           = merge_books (q1->books, q2->books);
+    retval->max_results    = q1->max_results;
+    retval->changed        = 1;
+    break;
+
+  case QOF_QUERY_AND:
+    retval = qof_query_create();
+    retval->books          = merge_books (q1->books, q2->books);
+    retval->max_results    = q1->max_results;
+    retval->changed        = 1;
+
+    /* g_list_append() can take forever, so let's build the list in
+     * reverse and then reverse it at the end, to deal better with
+     * "large" queries.
+     */
+    for(i=q1->terms; i; i=i->next) 
+    {
+      for(j=q2->terms; j; j=j->next) 
+      {
+        retval->terms = 
+          g_list_prepend(retval->terms, 
+                        g_list_concat
+                        (copy_and_terms(i->data),
+                         copy_and_terms(j->data)));
+      }
+    }
+    retval->terms = g_list_reverse(retval->terms);
+    break;
+
+  case QOF_QUERY_NAND:
+    /* !(a*b) = (!a + !b) */
+    i1     = qof_query_invert(q1);
+    i2     = qof_query_invert(q2);
+    retval = qof_query_merge(i1, i2, QOF_QUERY_OR);
+    qof_query_destroy(i1);
+    qof_query_destroy(i2);
+    break;
+
+  case QOF_QUERY_NOR:
+    /* !(a+b) = (!a*!b) */
+    i1     = qof_query_invert(q1);
+    i2     = qof_query_invert(q2);
+    retval = qof_query_merge(i1, i2, QOF_QUERY_AND);
+    qof_query_destroy(i1);
+    qof_query_destroy(i2);
+    break;
+
+  case QOF_QUERY_XOR:
+    /* a xor b = (a * !b) + (!a * b) */
+    i1     = qof_query_invert(q1);
+    i2     = qof_query_invert(q2);
+    t1     = qof_query_merge(q1, i2, QOF_QUERY_AND);
+    t2     = qof_query_merge(i1, q2, QOF_QUERY_AND);
+    retval = qof_query_merge(t1, t2, QOF_QUERY_OR);
+
+    qof_query_destroy(i1);
+    qof_query_destroy(i2);
+    qof_query_destroy(t1);
+    qof_query_destroy(t2);     
+    break;
+  }
+
+  retval->search_for = search_for;
+  return retval;
+}
+
+void
+qof_query_merge_in_place(QofQuery *q1, QofQuery *q2, QofQueryOp op)
+{
+  QofQuery *tmp_q;
+
+  if (!q1 || !q2)
+    return;
+
+  tmp_q = qof_query_merge (q1, q2, op);
+  swap_terms (q1, tmp_q);
+  qof_query_destroy (tmp_q);
+}
+
+void
+qof_query_set_sort_order (QofQuery *q,
+                      GSList *params1, GSList *params2, GSList *params3)
+{
+  if (!q) return;
+  if (q->primary_sort.param_list)
+    g_slist_free (q->primary_sort.param_list);
+  q->primary_sort.param_list = params1;
+  q->primary_sort.options = 0;
+
+  if (q->secondary_sort.param_list)
+    g_slist_free (q->secondary_sort.param_list);
+  q->secondary_sort.param_list = params2;
+  q->secondary_sort.options = 0;
+
+  if (q->tertiary_sort.param_list)
+    g_slist_free (q->tertiary_sort.param_list);
+  q->tertiary_sort.param_list = params3;
+  q->tertiary_sort.options = 0;
+
+  q->changed = 1;
+}
+
+void qof_query_set_sort_options (QofQuery *q, gint prim_op, gint sec_op,
+                             gint tert_op)
+{
+  if (!q) return;
+  q->primary_sort.options = prim_op;
+  q->secondary_sort.options = sec_op;
+  q->tertiary_sort.options = tert_op;
+}
+
+void qof_query_set_sort_increasing (QofQuery *q, gboolean prim_inc,
+                                gboolean sec_inc, gboolean tert_inc)
+{
+  if (!q) return;
+  q->primary_sort.increasing = prim_inc;
+  q->secondary_sort.increasing = sec_inc;
+  q->tertiary_sort.increasing = tert_inc;
+}
+
+void qof_query_set_max_results (QofQuery *q, int n)
+{
+  if (!q) return;
+  q->max_results = n;
+}
+
+void qof_query_add_guid_list_match (QofQuery *q, GSList *param_list,
+                               GList *guid_list, QofGuidMatch options,
+                               QofQueryOp op)
+{
+  QofQueryPredData *pdata;
+
+  if (!q || !param_list) return;
+
+  if (!guid_list)
+    g_return_if_fail (options == QOF_GUID_MATCH_NULL);
+
+  pdata = qof_query_guid_predicate (options, guid_list);
+  qof_query_add_term (q, param_list, pdata, op);
+}
+
+void qof_query_add_guid_match (QofQuery *q, GSList *param_list,
+                           const GUID *guid, QofQueryOp op)
+{
+  GList *g = NULL;
+
+  if (!q || !param_list) return;
+
+  if (guid)
+    g = g_list_prepend (g, (gpointer)guid);
+
+  qof_query_add_guid_list_match (q, param_list, g,
+                            g ? QOF_GUID_MATCH_ANY : QOF_GUID_MATCH_NULL, op);
+
+  g_list_free (g);
+}
+
+void qof_query_set_book (QofQuery *q, QofBook *book)
+{
+  GSList *slist = NULL;
+  if (!q || !book) return;
+
+  /* Make sure this book is only in the list once */
+  if (g_list_index (q->books, book) == -1)
+    q->books = g_list_prepend (q->books, book);
+
+  g_slist_prepend (slist, QOF_PARAM_GUID);
+  g_slist_prepend (slist, QOF_PARAM_BOOK);
+  qof_query_add_guid_match (q, slist,
+                        qof_book_get_guid(book), QOF_QUERY_AND);
+}
+
+GList * qof_query_get_books (QofQuery *q)
+{
+  if (!q) return NULL;
+  return q->books;
+}
+
+void qof_query_add_boolean_match (QofQuery *q, GSList *param_list, gboolean value,
+                              QofQueryOp op)
+{
+  QofQueryPredData *pdata;
+  if (!q || !param_list) return;
+
+  pdata = qof_query_boolean_predicate (QOF_COMPARE_EQUAL, value);
+  qof_query_add_term (q, param_list, pdata, op);
+}
+
+/**********************************************************************/
+/* PRIVATE PUBLISHED API FUNCTIONS                                    */
+
+void qof_query_init (void)
+{
+  ENTER (" ");
+  qof_query_core_init ();
+  qof_class_init ();
+}
+
+void qof_query_shutdown (void)
+{
+  qof_class_shutdown ();
+  qof_query_core_shutdown ();
+}
+
+int qof_query_get_max_results (QofQuery *q)
+{
+  if (!q) return 0;
+  return q->max_results;
+}
+
+QofIdType qof_query_get_search_for (QofQuery *q)
+{
+  if (!q) return NULL;
+  return q->search_for;
+}
+
+GList * qof_query_get_terms (QofQuery *q)
+{
+  if (!q) return NULL;
+  return q->terms;
+}
+
+GSList * qof_query_term_get_param_path (QofQueryTerm *qt)
+{
+  if (!qt)
+    return NULL;
+  return qt->param_list;
+}
+
+QofQueryPredData *qof_query_term_get_pred_data (QofQueryTerm *qt)
+{
+  if (!qt)
+    return NULL;
+  return qt->pdata;
+}
+
+gboolean qof_query_term_is_inverted (QofQueryTerm *qt)
+{
+  if (!qt)
+    return FALSE;
+  return qt->invert;
+}
+
+void qof_query_get_sorts (QofQuery *q, QofQuerySort **primary,
+                       QofQuerySort **secondary, QofQuerySort **tertiary)
+{
+  if (!q)
+    return;
+  if (primary)
+    *primary = &(q->primary_sort);
+  if (secondary)
+    *secondary = &(q->secondary_sort);
+  if (tertiary)
+    *tertiary = &(q->tertiary_sort);
+}
+
+GSList * qof_query_sort_get_param_path (QofQuerySort *qs)
+{
+  if (!qs)
+    return NULL;
+  return qs->param_list;
+}
+
+gint qof_query_sort_get_sort_options (QofQuerySort *qs)
+{
+  if (!qs)
+    return 0;
+  return qs->options;
+}
+
+gboolean qof_query_sort_get_increasing (QofQuerySort *qs)
+{
+  if (!qs)
+    return FALSE;
+  return qs->increasing;
+}
+
+static gboolean 
+qof_query_term_equal (QofQueryTerm *qt1, QofQueryTerm *qt2)
+{
+  if (qt1 == qt2) return TRUE;
+  if (!qt1 || !qt2) return FALSE;
+
+  if (qt1->invert != qt2->invert) return FALSE;
+  if (param_list_cmp (qt1->param_list, qt2->param_list)) return FALSE;
+  return qof_query_core_predicate_equal (qt1->pdata, qt2->pdata);
+}
+
+static gboolean 
+qof_query_sort_equal (QofQuerySort* qs1, QofQuerySort* qs2)
+{
+  if (qs1 == qs2) return TRUE;
+  if (!qs1 || !qs2) return FALSE;
+
+  /* "Empty" sorts are equivalent, regardless of the flags */
+  if (!qs1->param_list && !qs2->param_list) return TRUE;
+
+  if (qs1->options != qs2->options) return FALSE;
+  if (qs1->increasing != qs2->increasing) return FALSE;
+  return (param_list_cmp (qs1->param_list, qs2->param_list) == 0);
+}
+
+gboolean qof_query_equal (QofQuery *q1, QofQuery *q2)
+{
+  GList *or1, *or2;
+
+  if (q1 == q2) return TRUE;
+  if (!q1 || !q2) return FALSE;
+
+  if (g_list_length (q1->terms) != g_list_length (q2->terms)) return FALSE;
+  if (q1->max_results != q2->max_results) return FALSE;
+
+  for (or1 = q1->terms, or2 = q2->terms; or1;
+       or1 = or1->next, or2 = or2->next)
+  {
+    GList *and1, *and2;
+
+    and1 = or1->data;
+    and2 = or2->data;
+
+    if (g_list_length (and1) != g_list_length (and2)) return FALSE;
+
+    for ( ; and1; and1 = and1->next, and2 = and2->next)
+      if (!qof_query_term_equal (and1->data, and2->data))
+        return FALSE;
+  }
+
+  if (!qof_query_sort_equal (&(q1->primary_sort), &(q2->primary_sort)))
+    return FALSE;
+  if (!qof_query_sort_equal (&(q1->secondary_sort), &(q2->secondary_sort)))
+    return FALSE;
+  if (!qof_query_sort_equal (&(q1->tertiary_sort), &(q2->tertiary_sort)))
+    return FALSE;
+
+  return TRUE;
+}
+
+/***************************************************************************/
+/***************************************************************************/
+/* Query Print functions.  qof_query_print is public; everthing else supports
+ * that.
+ * Just call qof_query_print(QofQuery *q), and it will print out the query 
+ * contents to stderr.
+*/
+
+/* Static prototypes */
+static GList *qof_query_printSearchFor (QofQuery * query, GList * output);
+static GList *qof_query_printTerms (QofQuery * query, GList * output);
+static GList *qof_query_printSorts (QofQuerySort *s[], const gint numSorts,
+                                  GList * output);
+static GList *qof_query_printAndTerms (GList * terms, GList * output);
+static gchar *qof_query_printStringForHow (QofQueryCompare how);
+static gchar *qof_query_printStringMatch (QofStringMatch s);
+static gchar *qof_query_printDateMatch (QofDateMatch d);
+static gchar *qof_query_printNumericMatch (QofNumericMatch n);
+static gchar *qof_query_printGuidMatch (QofGuidMatch g);
+static gchar *qof_query_printCharMatch (QofCharMatch c);
+static GString *qof_query_printPredData (QofQueryPredData *pd);
+static GString *qof_query_printParamPath (GSList * parmList);
+static void qof_query_printValueForParam (QofQueryPredData *pd, GString * gs);
+static void qof_query_printOutput (GList * output);
+
+/*
+        This function cycles through a QofQuery object, and
+        prints out the values of the various members of the query
+*/
+void
+qof_query_print (QofQuery * query)
+{
+  GList *output;
+  GString *str;
+  QofQuerySort *s[3];
+  gint maxResults = 0, numSorts = 3;
+
+  ENTER (" ");
+
+  if (!query) {
+      LEAVE("query is (null)");
+      return;
+  }
+
+  output = NULL;
+  str = NULL;
+  maxResults = qof_query_get_max_results (query);
+
+  output = qof_query_printSearchFor (query, output);
+  output = qof_query_printTerms (query, output);
+
+  qof_query_get_sorts (query, &s[0], &s[1], &s[2]);
+
+  if (s[0])
+  {
+    output = qof_query_printSorts (s, numSorts, output);
+  }
+
+  str = g_string_new (" ");
+  g_string_sprintf (str, "Maximum number of results: %d", maxResults);
+  output = g_list_append (output, str);
+
+  qof_query_printOutput (output);
+  LEAVE (" ");
+}
+
+static void
+qof_query_printOutput (GList * output)
+{
+  GList *lst;
+
+  for (lst = output; lst; lst = lst->next)
+  {
+    GString *line = (GString *) lst->data;
+
+    fprintf (stderr, "%s\n", line->str);
+    g_string_free (line, TRUE);
+    line = NULL;
+  }
+}
+
+/*
+        Get the search_for type--This is the type of Object
+        we are searching for (SPLIT, TRANS, etc)
+*/
+static GList *
+qof_query_printSearchFor (QofQuery * query, GList * output)
+{
+  QofIdType searchFor;
+  GString *gs;
+
+  searchFor = qof_query_get_search_for (query);
+  gs = g_string_new ("Query Object Type: ");
+  g_string_append (gs, (NULL == searchFor)? "(null)" : searchFor);
+  output = g_list_append (output, gs);
+
+  return output;
+}                               /* qof_query_printSearchFor */
+
+/*
+        Run through the terms of the query.  This is a outer-inner
+        loop.  The elements of the outer loop are ORed, and the
+        elements of the inner loop are ANDed.
+*/
+static GList *
+qof_query_printTerms (QofQuery * query, GList * output)
+{
+
+  GList *terms, *lst;
+
+  terms = qof_query_get_terms (query);
+
+  for (lst = terms; lst; lst = lst->next)
+  {
+    output = g_list_append (output, g_string_new ("OR and AND Terms:"));
+
+    if (lst->data)
+    {
+      output = qof_query_printAndTerms (lst->data, output);
+    }
+    else
+    {
+      output =
+        g_list_append (output, g_string_new ("  No data for AND terms"));
+    }
+  }
+
+  return output;
+}                               /* qof_query_printTerms */
+
+/*
+        Process the sort parameters
+        If this function is called, the assumption is that the first sort
+        not null.
+*/
+static GList *
+qof_query_printSorts (QofQuerySort *s[], const gint numSorts, GList * output)
+{
+  GSList *gsl, *n = NULL;
+  gint curSort;
+  GString *gs = g_string_new ("  Sort Parameters:\n");
+
+  for (curSort = 0; curSort < numSorts; curSort++)
+  {
+    gboolean increasing;
+    if (!s[curSort])
+    {
+      break;
+    }
+    increasing = qof_query_sort_get_increasing (s[curSort]);
+
+    gsl = qof_query_sort_get_param_path (s[curSort]); 
+    if (gsl) g_string_sprintfa (gs, "    Param: ");
+    for (n=gsl; n; n = n->next)
+    {
+      QofIdType param_name = n->data;
+      if (gsl != n)g_string_sprintfa (gs, "\n           ");
+      g_string_sprintfa (gs, "%s", param_name);
+    }
+    if (gsl) 
+    {
+      g_string_sprintfa (gs, " %s\n", increasing ? "DESC" : "ASC");
+      g_string_sprintfa (gs, "    Options: 0x%x\n", s[curSort]->options);
+    }
+  }
+
+  output = g_list_append (output, gs);
+  return output;
+
+}                               /* qof_query_printSorts */
+
+/*
+        Process the AND terms of the query.  This is a GList
+        of WHERE terms that will be ANDed
+*/
+static GList *
+qof_query_printAndTerms (GList * terms, GList * output)
+{
+  const char *prefix = "  AND Terms:";
+  QofQueryTerm *qt;
+  QofQueryPredData *pd;
+  GSList *path;
+  GList *lst;
+  gboolean invert;
+
+  output = g_list_append (output, g_string_new (prefix));
+  for (lst = terms; lst; lst = lst->next)
+  {
+    qt = (QofQueryTerm *) lst->data;
+    pd = qof_query_term_get_pred_data (qt);
+    path = qof_query_term_get_param_path (qt);
+    invert = qof_query_term_is_inverted (qt);
+
+    if (invert) output = g_list_append (output, 
+                                     g_string_new("    INVERT SENSE "));
+    output = g_list_append (output, qof_query_printParamPath (path));
+    output = g_list_append (output, qof_query_printPredData (pd));
+    output = g_list_append (output, g_string_new("\n"));
+  }
+
+  return output;
+}                               /* qof_query_printAndTerms */
+
+/*
+        Process the parameter types of the predicate data
+*/
+static GString *
+qof_query_printParamPath (GSList * parmList)
+{
+  GSList *list = NULL;
+  GString *gs = g_string_new ("    Param List:\n");
+  g_string_append (gs, "      ");
+  for (list = parmList; list; list = list->next)
+  {
+    g_string_append (gs, (gchar *) list->data);
+    if (list->next)
+      g_string_append (gs, "->");
+  }
+
+  return gs;
+}                               /* qof_query_printParamPath */
+
+/*
+        Process the PredData of the AND terms
+*/
+static GString *
+qof_query_printPredData (QofQueryPredData *pd)
+{
+  GString *gs;
+
+  gs = g_string_new ("    Pred Data:\n      ");
+  g_string_append (gs, (gchar *) pd->type_name);
+
+  /* Char Predicate and GUID predicate don't use the 'how' field. */
+  if (safe_strcmp (pd->type_name, QOF_TYPE_CHAR) &&
+      safe_strcmp (pd->type_name, QOF_TYPE_GUID))
+  {
+    g_string_sprintfa (gs, "\n      how: %s",
+                       qof_query_printStringForHow (pd->how));
+  }
+
+  qof_query_printValueForParam (pd, gs);
+
+  return gs;
+}                               /* qof_query_printPredData */
+
+/*
+        Get a string representation for the
+        QofCompareFunc enum type.
+*/
+static gchar *
+qof_query_printStringForHow (QofQueryCompare how)
+{
+
+  switch (how)
+  {
+    case QOF_COMPARE_LT:
+      return "QOF_COMPARE_LT";
+    case QOF_COMPARE_LTE:
+      return "QOF_COMPARE_LTE";
+    case QOF_COMPARE_EQUAL:
+      return "QOF_COMPARE_EQUAL";
+    case QOF_COMPARE_GT:
+      return "QOF_COMPARE_GT";
+    case QOF_COMPARE_GTE:
+      return "QOF_COMPARE_GTE";
+    case QOF_COMPARE_NEQ:
+      return "QOF_COMPARE_NEQ";
+  }
+
+  return "INVALID HOW";
+}                               /* qncQueryPrintStringForHow */
+
+
+static void
+qof_query_printValueForParam (QofQueryPredData *pd, GString * gs)
+{
+
+  if (!safe_strcmp (pd->type_name, QOF_TYPE_GUID))
+  {
+    GList *node;
+    query_guid_t pdata = (query_guid_t) pd;
+    g_string_sprintfa (gs, "\n      Match type %s",
+                       qof_query_printGuidMatch (pdata->options));
+    for (node = pdata->guids; node; node = node->next)
+    {
+      g_string_sprintfa (gs, ", guids: %s",
+			 guid_to_string ((GUID *) node->data));
+    }
+    return;
+  }
+  if (!safe_strcmp (pd->type_name, QOF_TYPE_STRING))
+  {
+    query_string_t pdata = (query_string_t) pd;
+    g_string_sprintfa (gs, "\n      Match type %s",
+                       qof_query_printStringMatch (pdata->options));
+    g_string_sprintfa (gs, " %s string: %s",
+                       pdata->is_regex ? "Regex" : "Not regex",
+                       pdata->matchstring);
+    return;
+  }
+  if (!safe_strcmp (pd->type_name, QOF_TYPE_NUMERIC))
+  {
+    query_numeric_t pdata = (query_numeric_t) pd;
+    g_string_sprintfa (gs, "\n      Match type %s",
+                       qof_query_printNumericMatch (pdata->options));
+    g_string_sprintfa (gs, " gnc_numeric: %s",
+                       gnc_num_dbg_to_string (pdata->amount));
+    return;
+  }
+  if (!safe_strcmp (pd->type_name, QOF_TYPE_KVP))
+  {
+    GSList *node;
+    query_kvp_t pdata = (query_kvp_t) pd;
+    g_string_sprintfa (gs, "\n      kvp path: ");
+    for (node = pdata->path; node; node = node->next)
+    {
+      g_string_sprintfa (gs, "/%s", (gchar *) node->data);
+    }
+    g_string_sprintfa (gs, "\n");
+    g_string_sprintfa (gs, "      kvp value: %s\n", 
+                         kvp_value_to_string (pdata->value));
+    return;
+  }
+  if (!safe_strcmp (pd->type_name, QOF_TYPE_INT64))
+  {
+    query_int64_t pdata = (query_int64_t) pd;
+    g_string_sprintfa (gs, " int64: %" G_GINT64_FORMAT, pdata->val);
+    return;
+  }
+  if (!safe_strcmp (pd->type_name, QOF_TYPE_INT32))
+  {
+    query_int32_t pdata = (query_int32_t) pd;
+    g_string_sprintfa (gs, " int32: %d", pdata->val);
+    return;
+  }
+  if (!safe_strcmp (pd->type_name, QOF_TYPE_DOUBLE))
+  {
+    query_double_t pdata = (query_double_t) pd;
+    g_string_sprintfa (gs, " double: %.18g", pdata->val);
+    return;
+  }
+  if (!safe_strcmp (pd->type_name, QOF_TYPE_DATE))
+  {
+    query_date_t pdata = (query_date_t) pd;
+    g_string_sprintfa (gs, "\n      Match type %s",
+                       qof_query_printDateMatch (pdata->options));
+    g_string_sprintfa (gs, " query_date: %s", gnc_print_date (pdata->date));
+    return;
+  }
+  if (!safe_strcmp (pd->type_name, QOF_TYPE_CHAR))
+  {
+    query_char_t pdata = (query_char_t) pd;
+    g_string_sprintfa (gs, "\n      Match type %s",
+                       qof_query_printCharMatch (pdata->options));
+    g_string_sprintfa (gs, " char list: %s", pdata->char_list);
+    return;
+  }
+  if (!safe_strcmp (pd->type_name, QOF_TYPE_BOOLEAN))
+  {
+    query_boolean_t pdata = (query_boolean_t) pd;
+    g_string_sprintfa (gs, " boolean: %s", pdata->val?"TRUE":"FALSE");
+    return;
+  }
+  /** \todo QOF_TYPE_COLLECT */
+  return;
+}                               /* qof_query_printValueForParam */
+
+/*
+ * Print out a string representation of the
+ * QofStringMatch enum
+ */
+static gchar *
+qof_query_printStringMatch (QofStringMatch s)
+{
+  switch (s)
+  {
+    case QOF_STRING_MATCH_NORMAL:
+      return "QOF_STRING_MATCH_NORMAL";
+    case QOF_STRING_MATCH_CASEINSENSITIVE:
+      return "QOF_STRING_MATCH_CASEINSENSITIVE";
+  }
+  return "UNKNOWN MATCH TYPE";
+}                               /* qof_query_printStringMatch */
+
+/*
+ * Print out a string representation of the
+ * QofDateMatch enum
+ */
+static gchar *
+qof_query_printDateMatch (QofDateMatch d)
+{
+  switch (d)
+  {
+    case QOF_DATE_MATCH_NORMAL:
+      return "QOF_DATE_MATCH_NORMAL";
+    case QOF_DATE_MATCH_DAY:
+      return "QOF_DATE_MATCH_DAY";
+  }
+  return "UNKNOWN MATCH TYPE";
+}                               /* qof_query_printDateMatch */
+
+/*
+ * Print out a string representation of the
+ * QofNumericMatch enum
+ */
+static gchar *
+qof_query_printNumericMatch (QofNumericMatch n)
+{
+  switch (n)
+  {
+    case QOF_NUMERIC_MATCH_DEBIT:
+      return "QOF_NUMERIC_MATCH_DEBIT";
+    case QOF_NUMERIC_MATCH_CREDIT:
+      return "QOF_NUMERIC_MATCH_CREDIT";
+    case QOF_NUMERIC_MATCH_ANY:
+      return "QOF_NUMERIC_MATCH_ANY";
+  }
+  return "UNKNOWN MATCH TYPE";
+}                               /* qof_query_printNumericMatch */
+
+/*
+ * Print out a string representation of the
+ * QofGuidMatch enum
+ */
+static gchar *
+qof_query_printGuidMatch (QofGuidMatch g)
+{
+  switch (g)
+  {
+    case QOF_GUID_MATCH_ANY:
+      return "QOF_GUID_MATCH_ANY";
+    case QOF_GUID_MATCH_ALL:
+      return "QOF_GUID_MATCH_ALL";
+    case QOF_GUID_MATCH_NONE:
+      return "QOF_GUID_MATCH_NONE";
+    case QOF_GUID_MATCH_NULL:
+      return "QOF_GUID_MATCH_NULL";
+    case QOF_GUID_MATCH_LIST_ANY:
+      return "QOF_GUID_MATCH_LIST_ANY";
+  }
+
+  return "UNKNOWN MATCH TYPE";
+}                               /* qof_query_printGuidMatch */
+
+/*
+ * Print out a string representation of the
+ * QofCharMatch enum
+ */
+static gchar *
+qof_query_printCharMatch (QofCharMatch c)
+{
+  switch (c)
+  {
+    case QOF_CHAR_MATCH_ANY:
+      return "QOF_CHAR_MATCH_ANY";
+    case QOF_CHAR_MATCH_NONE:
+      return "QOF_CHAR_MATCH_NONE";
+  }
+  return "UNKNOWN MATCH TYPE";
+}                               /* qof_query_printGuidMatch */
+
+/* ======================== END OF FILE =================== */

Added: gnucash/trunk/lib/libqof/qof/qofquery.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,373 @@
+/********************************************************************\
+ * qofquery.h -- find objects that match a certain expression.      *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+/** @addtogroup Query
+
+BASIC QUERY API: 
+With this API you can create arbitrary logical
+queries to find sets of arbitrary object.  To make simple
+queries (1 term, such as a search for a parameter with one value), 
+create the appropriate
+QueryTerm structure and stick it in a Query object using
+xaccInitQuery. The QueryTerm should be malloced but the Query object
+will handle freeing it.  To make compound queries, make multiple
+simple queries and combine them using qof_query_merge() and the logical
+operations of your choice.
+
+SQL QUERY API: 
+As an alternative to building queries one predicate at a time,
+you can use the SQL query interface.  This interface will accept 
+a string containing an SQL query, parse it, convert it into the
+core representation, and execute it.
+
+STRUCTURE OF A QUERY: A Query is a logical function of any number of
+QueryTerms.  A QueryTerm consists of a C function pointer (the
+Predicate) and a PredicateData structure containing data passed to the
+predicate funtion.  The PredicateData structure is a constant
+associated with the Term and is identical for every object that is
+tested.
+
+The terms of the Query may represent any logical function and are
+stored in canonical form, i.e. the function is expressed as a logical
+sum of logical products.  So if you have QueryTerms a, b, c, d, e and
+you have the logical function a(b+c) + !(c(d+e)), it gets stored as 
+ab + ac + !c + !c!e +!d!c + !d!e.  This may not be optimal for evaluation
+of some functions but it's easy to store, easy to manipulate, and it
+doesn't require a complete algebra system to deal with.
+
+The representation is of a GList of GLists of QueryTerms.  The
+"backbone" GList q->terms represents the OR-chain, and every item on
+the backbone is a GList of QueryTerms representing an AND-chain
+corresponding to a single product-term in the canonical
+representation.  QueryTerms are duplicated when necessary to fill out
+the canonical form, and the same predicate may be evaluated multiple
+times per split for complex queries.  This is a place where we could
+probably optimize.
+
+Evaluation of a Query (see qof_query_run()) is optimized as much as
+possible by short-circuited evaluation.  The predicates in each
+AND-chain are sorted by predicate type, with Account queries sorted
+first to allow the evaluator to completely eliminate accounts from the
+search if there's no chance of them having splits that match.
+(XXX above no longer applies)
+
+ @{ */
+/** @file qofquery.h
+    @brief find objects that match a certain expression.
+    @author Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>
+    @author Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+
+*/
+
+
+#ifndef QOF_QUERYNEW_H
+#define QOF_QUERYNEW_H
+
+#include "guid.h"
+#include "qofbook.h"
+#include "qofquerycore.h"
+#include "qofchoice.h"
+
+#define QOF_MOD_QUERY "qof-query"
+
+/** A Query */
+typedef struct _QofQuery QofQuery;
+
+/** Query Term Operators, for combining Query Terms */
+typedef enum {
+  QOF_QUERY_AND=1,
+  QOF_QUERY_OR,
+  QOF_QUERY_NAND,
+  QOF_QUERY_NOR,
+  QOF_QUERY_XOR
+} QofQueryOp;
+
+/* First/only term is same as 'and' */
+#define QOF_QUERY_FIRST_TERM QOF_QUERY_AND
+
+/** Default sort object type */
+#define QUERY_DEFAULT_SORT      "QofQueryDefaultSort"
+
+/** "Known" Object Parameters -- all objects must support these */
+#define QOF_PARAM_BOOK    "book"
+#define QOF_PARAM_GUID    "guid"
+
+/** "Known" Object Parameters -- some objects might support these */
+#define QOF_PARAM_KVP     "kvp" 
+#define QOF_PARAM_ACTIVE  "active" 
+#define QOF_PARAM_VERSION "version" 
+
+/* --------------------------------------------------------- */
+/** \name Query Subsystem Initialization and Shudown  */
+// @{
+/** Subsystem initialization and shutdown. Call init() once 
+ *  to initalize the query subsytem; call shutdown() to free
+ *  up any resources associated with the query subsystem. 
+ *  Typically called during application startup, shutdown.
+ */
+
+void qof_query_init (void);
+void qof_query_shutdown (void);
+// @}
+
+/* --------------------------------------------------------- */
+/** \name Low-Level API Functions */
+// @{
+
+GSList * qof_query_build_param_list (char const *param, ...);
+
+/** Create a new query.  
+ *  Before running the query, a 'search-for' type must be set
+ *  otherwise nothing will be returned.  The results of the query 
+ *  is a list of the indicated search-for type.
+ *
+ *  Allocates and initializes a Query structure which must be 
+ *  freed by the user with qof_query_destroy().  A newly-allocated 
+ *  QofQuery object matches nothing (qof_query_run() will return NULL).
+ */
+QofQuery * qof_query_create (void);
+QofQuery * qof_query_create_for (QofIdTypeConst obj_type);
+
+/** Frees the resources associate with a Query object.  */
+void qof_query_destroy (QofQuery *q);
+
+/** Set the object type to be searched for.  The results of 
+ *  performuing the query will be a list of this obj_type.
+ */
+void qof_query_search_for (QofQuery *query, QofIdTypeConst obj_type);
+
+/** Set the book to be searched.  Books contain/identify collections 
+ *  of objects; the search will be performed over those books
+ *  specified with this function.  If no books are set, no results 
+ *  will be returned (since there is nothing to search over).
+ *
+ *  You can search multiple books.  To specify multiple books, call 
+ *  this function multiple times with different arguments.  
+ * XXX needed qof_query_clear_books() to reset the list ... 
+ */
+void qof_query_set_book (QofQuery *q, QofBook *book);
+
+
+/** This is the general function that adds a new Query Term to a query.
+ * It will find the 'obj_type' object of the search item and compare
+ * the 'param_list' parameter to the predicate data via the comparitor.
+ *
+ * The param_list is a recursive list of parameters.  For example, you
+ * can say 'split->memo' by creating a list of one element, "SPLIT_MEMO".
+ * You can say 'split->account->name' by creating a list of two elements,
+ * "SPLIT_ACCOUNT" and "ACCOUNT_NAME".  The list becomes the property of
+ * the Query.
+ *
+ * For example:
+ *
+ * acct_name_pred_data = make_string_pred_data(QOF_STRING_MATCH_CASEINSENSITIVE,
+ *                                          account_name);
+ * param_list = make_list (SPLIT_ACCOUNT, ACCOUNT_NAME, NULL);
+ * qof_query_add_term (query, param_list, QOF_COMPARE_EQUAL,
+ *                    acct_name_pred_data, QOF_QUERY_AND);
+ *
+ * Please note that QofQuery does not, at this time, support joins.
+ * That is, one cannot specify a predicate that is a parameter list.
+ * Put another way, one cannot search for objects where 
+ *   obja->thingy == objb->stuff
+ */
+
+void qof_query_add_term (QofQuery *query, GSList *param_list,
+                      QofQueryPredData *pred_data, QofQueryOp op);
+
+/** DOCUMENT ME !! */
+void qof_query_add_guid_match (QofQuery *q, GSList *param_list,
+                           const GUID *guid, QofQueryOp op);
+/** DOCUMENT ME !! */
+void qof_query_add_guid_list_match (QofQuery *q, GSList *param_list,
+                               GList *guid_list, QofGuidMatch options,
+                               QofQueryOp op);
+
+/** Handy-dandy convenience routines, avoids having to create 
+ * a separate predicate for boolean matches.  We might want to 
+ * create handy-dandy sugar routines for the other predicate types 
+ * as well. */
+void qof_query_add_boolean_match (QofQuery *q, 
+                                  GSList *param_list, 
+                                  gboolean value,
+                                  QofQueryOp op);
+
+/** Perform the query, return the results.
+ *  The returned list is a list of the 'search-for' type that was
+ *  previously set with the qof_query_search_for() or the
+ *  qof_query_create_for() routines.  The returned list will have
+ *  been sorted using the indicated sort order, and trimed to the
+ *  max_results length.
+ *
+ *  Do NOT free the resulting list.  This list is managed internally
+ *  by QofQuery.
+ */
+GList * qof_query_run (QofQuery *query);
+
+/** Return the results of the last query, without causing the query to
+ *  be re-run.  Do NOT free the resulting list.  This list is managed
+ *  internally by QofQuery.
+ */
+GList * qof_query_last_run (QofQuery *query);
+
+/** Remove all query terms from query.  query matches nothing 
+ *  after qof_query_clear().
+ */
+void qof_query_clear (QofQuery *query);
+
+/** Remove query terms of a particular type from q.  The "type" of a term
+ *  is determined by the type of data that gets passed to the predicate
+ *  function.  
+ * XXX ??? Huh? remove anything of that predicate type, or just 
+ * the particular predicate ?
+ */
+void qof_query_purge_terms (QofQuery *q, GSList *param_list);
+
+/** Return boolean FALSE if there are no terms in the query 
+ *  Can be used as a predicate to see if the query has been 
+ *  initialized (return value > 0) or is "blank" (return value == 0).
+ */
+int qof_query_has_terms (QofQuery *q);
+
+/** Return the number of terms in the canonical form of the query.  
+ */
+int qof_query_num_terms (QofQuery *q);
+
+/** DOCUMENT ME !! */
+gboolean qof_query_has_term_type (QofQuery *q, GSList *term_param);
+GSList * qof_query_get_term_type (QofQuery *q, GSList *term_param);
+
+/** Make a copy of the indicated query */
+QofQuery * qof_query_copy (QofQuery *q);
+
+/** Make a copy of the indicated query, inverting the sense
+ *  of the search.  In other words, if the original query search 
+ *  for all objects with a certain condition, the inverted query
+ *  will search for all object with NOT that condition.  The union
+ *  of the results returned by the original and inverted queries
+ *  equals the set of all searched objects. These to sets are
+ *  disjoint (share no members in common).
+ *
+ *  This will return a newly allocated QofQuery object, or NULL
+ *  on error. Free it with qof_query_destroy() when no longer needed.
+ */
+QofQuery * qof_query_invert(QofQuery *q);
+
+/** Combine two queries together using the Boolean set (logical) 
+ *  operator 'op'.  For example, if the operator 'op' is set to
+ *  QUERY_AND, then the set of results returned by the query will
+ *  will be the Boolean set intersection of the results returned
+ *  by q1 and q2.  Similarly,  QUERY_OR maps to set union, etc.
+ *
+ *  Both queries must have compatible
+ *  search-types.  If both queries are set, they must search for the
+ *  same object type.  If only one is set, the resulting query will
+ *  search for the set type.  If neither query has the search-type set,
+ *  the result will be unset as well.
+ *
+ *  This will return a newly allocated QofQuery object, or NULL
+ *  on error. Free it with qof_query_destroy() when no longer needed.
+ */
+QofQuery * qof_query_merge(QofQuery *q1, QofQuery *q2, QofQueryOp op);
+
+/** Like qof_query_merge, but this will merge a copy of q2 into q1.  
+ *   q2 remains unchanged.
+ */
+void qof_query_merge_in_place(QofQuery *q1, QofQuery *q2, QofQueryOp op);
+
+/** 
+ * When a query is run, the results are sorted before being returned.
+ * This routine can be used to set the paramters on which the sort will
+ * be performed.  Two objects in the result list will be compared using
+ * the 'primary_sort_params', and sorted based on that order.  If the
+ * comparison shows that they are equal, then the
+ * 'secondary_sort_params' will be used.  If still equal, then the 
+ * tertiary params will be compared.  Any or all of these parameter
+ * lists may be NULL.  Any of these parameter lists may be set to
+ * QUERY_DEFAULT_SORT.
+ *
+ * Note that if there are more results than the 'max-results' value,
+ * then only the *last* max-results will be returned.  For example,
+ * if the sort is set to be increasing date order, then only the
+ * objects with the most recent dates will be returned.
+ *
+ * The input lists become the property of QofQuery and are managed
+ * by it.   They will be freed when the query is destroyed (or when
+ * new lists are set).
+ */
+void qof_query_set_sort_order (QofQuery *q,
+                           GSList *primary_sort_params,
+                           GSList *secondary_sort_params,
+                           GSList *tertiary_sort_params);
+
+void qof_query_set_sort_options (QofQuery *q, gint prim_op, gint sec_op,
+                             gint tert_op);
+
+/**
+ * When a query is run, the results are sorted before being returned.
+ * This routine can be used to control the direction of the ordering.
+ * A value of TRUE indicates the sort will be in increasing order,
+ * a value of FALSE will order results in decreasing order.
+ *
+ * Note that if there are more results than the 'max-results' value,
+ * then only the *last* max-results will be returned.  For example,
+ * if the sort is set to be increasing date order, then only the
+ * objects with the most recent dates will be returned.
+ */
+void qof_query_set_sort_increasing (QofQuery *q, gboolean prim_inc,
+                                gboolean sec_inc, gboolean tert_inc);
+
+
+/**
+ * Set the maximum number of results that should be returned. 
+ * If 'max-results' is set to -1, then all of the results are
+ * returned.  If there are more results than 'max-results',
+ * then the result list is trimmed.  Note that there is an
+ * important interplay between 'max-results' and the sort order:
+ * only the last bit of results are returned.  For example,
+ * if the sort order is set to be increasing date order, then 
+ * only the objects with the most recent dates will be returned.
+ */ 
+void qof_query_set_max_results (QofQuery *q, int n);
+
+/** Compare two queries for equality. 
+ * Query terms are compared each to each.
+ * This is a simplistic
+ * implementation -- logical equivalences between different
+ * and/or trees are ignored. 
+ */
+gboolean qof_query_equal (QofQuery *q1, QofQuery *q2);
+
+/** Print the Query in human-readable format.
+ * Useful for debugging and development.
+ */
+void qof_query_print (QofQuery *query);
+
+/** Return the type of data we're querying for */
+QofIdType qof_query_get_search_for (QofQuery *q);
+
+/** Return the list of books we're using */
+GList * qof_query_get_books (QofQuery *q);
+
+// @}
+/* @} */
+#endif /* QOF_QUERYNEW_H */

Added: gnucash/trunk/lib/libqof/qof/qofquerycore-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquerycore-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquerycore-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,146 @@
+/********************************************************************\
+ * qofquerycore-p.h -- Private API for providing core Query data types *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#ifndef QOF_QUERYCOREP_H
+#define QOF_QUERYCOREP_H
+
+#include <sys/types.h>
+#include <time.h>
+#include <glib.h>
+#include <regex.h>
+#include <string.h>
+
+#include "qofquerycore.h"
+
+/* Initalize the Query Core registry and install the default type handlers */
+void qof_query_core_init(void);
+void qof_query_core_shutdown (void);
+
+/* 
+ * An arbitrary Query Predicate.  Given the object and the
+ * particular parameter get-function (obtained from the registry by
+ * the Query internals), compare the object's parameter to the
+ * predicate data.
+ */
+typedef int (*QofQueryPredicateFunc) (gpointer object,
+			       QofParam *getter,
+			       QofQueryPredData *pdata);
+
+/* A callback for how to compare two (same-type) objects based on a
+ * common getter (parameter member), using the provided comparison
+ * options (which are the type-specific options).
+ */
+typedef int (*QofCompareFunc) (gpointer a, gpointer b,
+                              gint compare_options,
+                              QofParam *getter);
+
+/* Lookup functions */
+QofQueryPredicateFunc qof_query_core_get_predicate (char const *type);
+QofCompareFunc qof_query_core_get_compare (char const *type);
+
+/* Compare two predicates */
+gboolean qof_query_core_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2);
+
+/* Predicate Data Structures:
+ *
+ * These are defined such that you can cast between these types and
+ * a QofQueryPredData.
+ *
+ * Note that these are provided for READ ONLY PURPOSES.  You should NEVER
+ * write into these structures, change them, or use them to create a
+ * Query.
+ */
+
+typedef struct {
+  QofQueryPredData	pd;
+  QofStringMatch	options;
+  gboolean		is_regex;
+  char *		matchstring;
+  regex_t		compiled;
+} query_string_def, *query_string_t;
+
+typedef struct {
+  QofQueryPredData	pd;
+  QofDateMatch	options;
+  Timespec	date;
+} query_date_def, *query_date_t;
+
+typedef struct {
+  QofQueryPredData	pd;
+  QofNumericMatch	options;
+  gnc_numeric		amount;
+} query_numeric_def, *query_numeric_t;
+
+typedef struct {
+  QofQueryPredData	pd;
+  QofGuidMatch	options;
+  GList *	guids;
+} query_guid_def, *query_guid_t;
+
+typedef struct {
+  QofQueryPredData	pd;
+  gint32	val;
+} query_int32_def, *query_int32_t;
+
+typedef struct {
+  QofQueryPredData	pd;
+  gint64	val;
+} query_int64_def, *query_int64_t;
+
+typedef struct {
+  QofQueryPredData	pd;
+  double	val;
+} query_double_def, *query_double_t;
+
+typedef struct {
+  QofQueryPredData	pd;
+  gboolean	val;
+} query_boolean_def, *query_boolean_t;
+
+typedef struct {
+  QofQueryPredData	pd;
+  QofCharMatch	options;
+  char *	char_list;
+} query_char_def, *query_char_t;
+
+typedef struct {
+  QofQueryPredData	pd;
+  GSList *	path;
+  KvpValue *	value;
+} query_kvp_def, *query_kvp_t;
+
+typedef struct {
+	QofQueryPredData pd;
+	QofGuidMatch  options;
+	QofCollection *coll;
+	GList *guids;
+} query_coll_def, *query_coll_t;
+
+typedef struct {
+	QofQueryPredData pd;
+	QofGuidMatch options;
+	const GUID *guid;
+	GList * guids;
+} query_choice_def, *query_choice_t;
+
+#endif /* QOF_QUERYCOREP_H */

Added: gnucash/trunk/lib/libqof/qof/qofquerycore.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquerycore.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquerycore.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1834 @@
+/********************************************************************\
+ * QueryCore.c -- API for providing core Query data types           *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "qofquery.h"
+#include "qofquerycore.h"
+#include "qofquerycore-p.h"
+
+static QofLogModule log_module = QOF_MOD_QUERY;
+
+/* A function to destroy a query predicate's pdata */
+typedef void (*QueryPredDataFree) (QofQueryPredData *pdata);
+
+/* A function to copy a query's predicate data */
+typedef QofQueryPredData *(*QueryPredicateCopyFunc) (QofQueryPredData *pdata);
+
+/* A function to take the object, apply the getter->param_getfcn,
+ * and return a printable string.  Note that this QofParam->getfnc
+ * function should be returning a type equal to this core object type.
+ *
+ * Note that this string MUST be freed by the caller.
+ */
+typedef char * (*QueryToString) (gpointer object, QofParam *getter);
+
+/* A function to test for equality of predicate data */
+typedef gboolean (*QueryPredicateEqual) (QofQueryPredData *p1,
+                                         QofQueryPredData *p2);
+
+static QueryPredicateCopyFunc qof_query_copy_predicate (QofType type);
+static QueryPredDataFree qof_query_predicate_free (QofType type);
+
+/* Core Type Predicate helpers */
+typedef const char * (*query_string_getter) (gpointer, QofParam *);
+static const char * query_string_type = QOF_TYPE_STRING;
+
+typedef Timespec (*query_date_getter) (gpointer, QofParam *);
+static const char * query_date_type = QOF_TYPE_DATE;
+
+typedef gnc_numeric (*query_numeric_getter) (gpointer, QofParam *);
+static const char * query_numeric_type = QOF_TYPE_NUMERIC;
+
+typedef GList * (*query_glist_getter) (gpointer, QofParam *);
+typedef const GUID * (*query_guid_getter) (gpointer, QofParam *);
+static const char * query_guid_type = QOF_TYPE_GUID;
+
+typedef gint32 (*query_int32_getter) (gpointer, QofParam *);
+static const char * query_int32_type = QOF_TYPE_INT32;
+
+typedef gint64 (*query_int64_getter) (gpointer, QofParam *);
+static const char * query_int64_type = QOF_TYPE_INT64;
+
+typedef double (*query_double_getter) (gpointer, QofParam *);
+static const char * query_double_type = QOF_TYPE_DOUBLE;
+
+typedef gboolean (*query_boolean_getter) (gpointer, QofParam *);
+static const char * query_boolean_type = QOF_TYPE_BOOLEAN;
+
+typedef char (*query_char_getter) (gpointer, QofParam *);
+static const char * query_char_type = QOF_TYPE_CHAR;
+
+typedef KvpFrame * (*query_kvp_getter) (gpointer, QofParam *);
+static const char * query_kvp_type = QOF_TYPE_KVP;
+
+typedef QofCollection * (*query_collect_getter) (gpointer, QofParam*);
+static const char * query_collect_type = QOF_TYPE_COLLECT;
+
+typedef const GUID * (*query_choice_getter) (gpointer, QofParam *);
+static const char * query_choice_type = QOF_TYPE_CHOICE;
+
+/* Tables for predicate storage and lookup */
+static gboolean initialized = FALSE;
+static GHashTable *predTable = NULL;
+static GHashTable *cmpTable = NULL;
+static GHashTable *copyTable = NULL;
+static GHashTable *freeTable = NULL;
+static GHashTable *toStringTable = NULL;
+static GHashTable *predEqualTable = NULL;
+
+#define COMPARE_ERROR -3
+#define PREDICATE_ERROR -2
+
+#define VERIFY_PDATA(str) { \
+        g_return_if_fail (pd != NULL); \
+        g_return_if_fail (pd->type_name == str || \
+                        !safe_strcmp (str, pd->type_name)); \
+}
+#define VERIFY_PDATA_R(str) { \
+        g_return_val_if_fail (pd != NULL, NULL); \
+        g_return_val_if_fail (pd->type_name == str || \
+                                !safe_strcmp (str, pd->type_name), \
+                                NULL); \
+}
+#define VERIFY_PREDICATE(str) { \
+        g_return_val_if_fail (getter != NULL, PREDICATE_ERROR); \
+        g_return_val_if_fail (getter->param_getfcn != NULL, PREDICATE_ERROR); \
+        g_return_val_if_fail (pd != NULL, PREDICATE_ERROR); \
+        g_return_val_if_fail (pd->type_name == str || \
+                                !safe_strcmp (str, pd->type_name), \
+                                PREDICATE_ERROR); \
+}
+
+/********************************************************************/
+/* TYPE-HANDLING FUNCTIONS */
+
+/* QOF_TYPE_STRING */
+
+static int
+string_match_predicate (gpointer object,
+                        QofParam *getter,
+                        QofQueryPredData *pd)
+{
+  query_string_t pdata = (query_string_t) pd;
+  const char *s;
+  int ret = 0;
+
+  VERIFY_PREDICATE (query_string_type);
+
+  s = ((query_string_getter)getter->param_getfcn) (object, getter);
+
+  if (!s) s = "";
+
+  if (pdata->is_regex) {
+    regmatch_t match;
+    if (!regexec (&pdata->compiled, s, 1, &match, 0))
+      ret = 1;
+
+  } else if (pdata->options == QOF_STRING_MATCH_CASEINSENSITIVE) {
+    if (strcasestr (s, pdata->matchstring))
+      ret = 1;
+
+  } else {
+    if (strstr (s, pdata->matchstring))
+      ret = 1;
+  }
+
+  switch (pd->how) {
+  case QOF_COMPARE_EQUAL:
+    return ret;
+  case QOF_COMPARE_NEQ:
+    return !ret;
+  default:
+    PWARN ("bad match type: %d", pd->how);
+    return 0;
+  }
+}
+
+static int
+string_compare_func (gpointer a, gpointer b, gint options,
+                     QofParam *getter)
+{
+  const char *s1, *s2;
+  g_return_val_if_fail (a && b && getter &&getter->param_getfcn, COMPARE_ERROR);
+
+  s1 = ((query_string_getter)getter->param_getfcn) (a, getter);
+  s2 = ((query_string_getter)getter->param_getfcn) (b, getter);
+
+  if (options == QOF_STRING_MATCH_CASEINSENSITIVE)
+    return safe_strcasecmp (s1, s2);
+
+  return safe_strcmp (s1, s2);
+}
+
+static void
+string_free_pdata (QofQueryPredData *pd)
+{
+  query_string_t pdata = (query_string_t) pd;
+
+  VERIFY_PDATA (query_string_type);
+
+  if (pdata->is_regex)
+    regfree (&pdata->compiled);
+  else
+    g_free (pdata->matchstring);
+
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+string_copy_predicate (QofQueryPredData *pd)
+{
+  query_string_t pdata = (query_string_t) pd;
+
+  VERIFY_PDATA_R (query_string_type);
+
+  return qof_query_string_predicate (pd->how, pdata->matchstring,
+                                     pdata->options,
+                                     pdata->is_regex);
+}
+
+static gboolean
+string_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_string_t pd1 = (query_string_t) p1;
+  query_string_t pd2 = (query_string_t) p2;
+
+  if (pd1->options != pd2->options) return FALSE;
+  if (pd1->is_regex != pd2->is_regex) return FALSE;
+  return (safe_strcmp (pd1->matchstring, pd2->matchstring) == 0);
+}
+
+QofQueryPredData *
+qof_query_string_predicate (QofQueryCompare how,
+                            const char *str, QofStringMatch options,
+                            gboolean is_regex)
+{
+  query_string_t pdata;
+
+  g_return_val_if_fail (str, NULL);
+  g_return_val_if_fail (*str != '\0', NULL);
+  g_return_val_if_fail (how == QOF_COMPARE_EQUAL || how == QOF_COMPARE_NEQ, NULL);
+
+  pdata = g_new0 (query_string_def, 1);
+  pdata->pd.type_name = query_string_type;
+  pdata->pd.how = how;
+  pdata->options = options;
+  pdata->matchstring = g_strdup (str);
+
+  if (is_regex) {
+    int flags = REG_EXTENDED;
+    if (options == QOF_STRING_MATCH_CASEINSENSITIVE)
+      flags |= REG_ICASE;
+
+    regcomp(&pdata->compiled, str, flags);
+    pdata->is_regex = TRUE;
+  }
+
+  return ((QofQueryPredData*)pdata);
+}
+
+static char *
+string_to_string (gpointer object, QofParam *getter)
+{
+  const char *res;
+  res = ((query_string_getter)getter->param_getfcn)(object, getter);
+  if (res)
+    return g_strdup (res);
+  return NULL;
+}
+
+/* QOF_TYPE_DATE =================================================== */
+
+static int
+date_compare (Timespec ta, Timespec tb, QofDateMatch options)
+{
+
+  if (options == QOF_DATE_MATCH_DAY) {
+    ta = timespecCanonicalDayTime (ta);
+    tb = timespecCanonicalDayTime (tb);
+  }
+
+  if (ta.tv_sec < tb.tv_sec)
+    return -1;
+  if (ta.tv_sec > tb.tv_sec)
+    return 1;
+
+  if (ta.tv_nsec < tb.tv_nsec)
+    return -1;
+  if (ta.tv_nsec > tb.tv_nsec)
+    return 1;
+
+  return 0;
+}
+
+static int
+date_match_predicate (gpointer object, QofParam *getter,
+                                 QofQueryPredData *pd)
+{
+  query_date_t pdata = (query_date_t)pd;
+  Timespec objtime;
+  int compare;
+
+  VERIFY_PREDICATE (query_date_type);
+
+  objtime = ((query_date_getter)getter->param_getfcn) (object, getter);
+  compare = date_compare (objtime, pdata->date, pdata->options);
+
+  switch (pd->how) {
+  case QOF_COMPARE_LT:
+    return (compare < 0);
+  case QOF_COMPARE_LTE:
+    return (compare <= 0);
+  case QOF_COMPARE_EQUAL:
+    return (compare == 0);
+  case QOF_COMPARE_GT:
+    return (compare > 0);
+  case QOF_COMPARE_GTE:
+    return (compare >= 0);
+  case QOF_COMPARE_NEQ:
+    return (compare != 0);
+  default:
+    PWARN ("bad match type: %d", pd->how);
+    return 0;
+  }
+}
+
+static int
+date_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
+{
+  Timespec ta, tb;
+
+  g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+  ta = ((query_date_getter)getter->param_getfcn) (a, getter);
+  tb = ((query_date_getter)getter->param_getfcn) (b, getter);
+
+  return date_compare (ta, tb, options);
+}
+
+static void
+date_free_pdata (QofQueryPredData *pd)
+{
+  query_date_t pdata = (query_date_t)pd;
+
+  VERIFY_PDATA (query_date_type);
+
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+date_copy_predicate (QofQueryPredData *pd)
+{
+  query_date_t pdata = (query_date_t)pd;
+
+  VERIFY_PDATA_R (query_date_type);
+
+  return qof_query_date_predicate (pd->how, pdata->options, pdata->date);
+}
+
+static gboolean
+date_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_date_t pd1 = (query_date_t) p1;
+  query_date_t pd2 = (query_date_t) p2;
+
+  if (pd1->options != pd2->options) return FALSE;
+  return timespec_equal (&(pd1->date), &(pd2->date));
+}
+
+QofQueryPredData *
+qof_query_date_predicate (QofQueryCompare how,
+                          QofDateMatch options, Timespec date)
+{
+  query_date_t pdata;
+
+  pdata = g_new0 (query_date_def, 1);
+  pdata->pd.type_name = query_date_type;
+  pdata->pd.how = how;
+  pdata->options = options;
+  pdata->date = date;
+  return ((QofQueryPredData*)pdata);
+}
+
+gboolean
+qof_query_date_predicate_get_date (QofQueryPredData *pd, Timespec *date)
+{
+  query_date_t pdata = (query_date_t)pd;
+
+  if (pdata->pd.type_name != query_date_type)
+    return FALSE;
+  *date = pdata->date;
+  return TRUE;
+}
+
+static char *
+date_to_string (gpointer object, QofParam *getter)
+{
+  Timespec ts = ((query_date_getter)getter->param_getfcn)(object, getter);
+
+  if (ts.tv_sec || ts.tv_nsec)
+    return g_strdup (gnc_print_date (ts));
+
+  return NULL;
+}
+
+/* QOF_TYPE_NUMERIC ================================================= */
+
+static int
+numeric_match_predicate (gpointer object, QofParam *getter,
+                         QofQueryPredData* pd)
+{
+  query_numeric_t pdata = (query_numeric_t)pd;
+  gnc_numeric obj_val;
+  int compare;
+
+  VERIFY_PREDICATE (query_numeric_type);
+
+  obj_val = ((query_numeric_getter)getter->param_getfcn) (object, getter);
+
+  switch (pdata->options) {
+  case QOF_NUMERIC_MATCH_CREDIT:
+    if (gnc_numeric_positive_p (obj_val)) return 0;
+    break;
+  case QOF_NUMERIC_MATCH_DEBIT:
+    if (gnc_numeric_negative_p (obj_val)) return 0;
+    break;
+  default:
+    break;
+  }
+
+  /* Amounts are considered to be 'equal' if they match to
+   * four decimal places. (epsilon=1/10000) */
+  if (pd->how == QOF_COMPARE_EQUAL || pd->how == QOF_COMPARE_NEQ) {
+    gnc_numeric cmp_val = gnc_numeric_create (1, 10000);
+    compare =
+      (gnc_numeric_compare (gnc_numeric_abs
+                            (gnc_numeric_sub (gnc_numeric_abs (obj_val),
+                                              gnc_numeric_abs (pdata->amount),
+                                              100000, GNC_HOW_RND_ROUND)),
+                            cmp_val) < 0);
+  } else
+    compare = gnc_numeric_compare (gnc_numeric_abs (obj_val), pdata->amount);
+
+  switch (pd->how) {
+  case QOF_COMPARE_LT:
+    return (compare < 0);
+  case QOF_COMPARE_LTE:
+    return (compare <= 0);
+  case QOF_COMPARE_EQUAL:
+    return compare;
+  case QOF_COMPARE_GT:
+    return (compare > 0);
+  case QOF_COMPARE_GTE:
+    return (compare >= 0);
+  case QOF_COMPARE_NEQ:
+    return !compare;
+  default:
+    PWARN ("bad match type: %d", pd->how);
+    return 0;
+  }
+}
+
+static int
+numeric_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
+{
+  gnc_numeric va, vb;
+
+  g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+  va = ((query_numeric_getter)getter->param_getfcn) (a, getter);
+  vb = ((query_numeric_getter)getter->param_getfcn) (b, getter);
+
+  return gnc_numeric_compare (va, vb);
+}
+
+static void
+numeric_free_pdata (QofQueryPredData* pd)
+{
+  query_numeric_t pdata = (query_numeric_t)pd;
+  VERIFY_PDATA (query_numeric_type);
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+numeric_copy_predicate (QofQueryPredData *pd)
+{
+  query_numeric_t pdata = (query_numeric_t)pd;
+  VERIFY_PDATA_R (query_numeric_type);
+  return qof_query_numeric_predicate (pd->how, pdata->options, pdata->amount);
+}
+
+static gboolean
+numeric_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_numeric_t pd1 = (query_numeric_t) p1;
+  query_numeric_t pd2 = (query_numeric_t) p2;
+
+  if (pd1->options != pd2->options) return FALSE;
+  return gnc_numeric_equal (pd1->amount, pd2->amount);
+}
+
+QofQueryPredData *
+qof_query_numeric_predicate (QofQueryCompare how,
+                             QofNumericMatch options,
+                             gnc_numeric value)
+{
+  query_numeric_t pdata;
+  pdata = g_new0 (query_numeric_def, 1);
+  pdata->pd.type_name = query_numeric_type;
+  pdata->pd.how = how;
+  pdata->options = options;
+  pdata->amount = value;
+  return ((QofQueryPredData*)pdata);
+}
+
+static char *
+numeric_to_string (gpointer object, QofParam *getter)
+{
+  gnc_numeric num;
+  num = ((query_numeric_getter)getter->param_getfcn)(object, getter);
+
+  return gnc_numeric_to_string (num);
+}
+
+static char *
+debcred_to_string (gpointer object, QofParam *getter)
+{
+  gnc_numeric num;
+  num = ((query_numeric_getter)getter->param_getfcn)(object, getter);
+
+  return gnc_numeric_to_string (num);
+}
+
+/* QOF_TYPE_GUID =================================================== */
+
+static int
+guid_match_predicate (gpointer object, QofParam *getter,
+                      QofQueryPredData *pd)
+{
+  query_guid_t pdata = (query_guid_t)pd;
+  GList *node, *o_list;
+  const GUID *guid = NULL;
+
+  VERIFY_PREDICATE (query_guid_type);
+
+  switch (pdata->options) {
+
+  case QOF_GUID_MATCH_ALL:
+    /* object is a GList of objects; param_getfcn must be called on each one.
+     * See if every guid in the predicate is accounted-for in the
+     * object list
+     */
+
+    for (node = pdata->guids; node; node = node->next)
+    {
+      /* See if this GUID matches the object's guid */
+      for (o_list = object; o_list; o_list = o_list->next)
+      {
+        guid = ((query_guid_getter)getter->param_getfcn) (o_list->data, getter);
+        if (guid_equal (node->data, guid))
+          break;
+      }
+
+      /*
+       * If o_list is NULL, we've walked the whole list without finding
+       * a match.  Therefore break out now, the match has failed.
+       */
+      if (o_list == NULL)
+        break;
+    }
+
+    /*
+     * The match is complete.  If node == NULL then we've succesfully
+     * found a match for all the guids in the predicate.  Return
+     * appropriately below.
+     */
+
+    break;
+
+  case QOF_GUID_MATCH_LIST_ANY:
+    /* object is a single object, getter returns a GList* of GUID*
+     *
+     * See if any GUID* in the returned list matches any guid in the
+     * predicate match list.
+     */
+
+    o_list = ((query_glist_getter)getter->param_getfcn) (object, getter);
+
+    for (node = o_list; node; node = node->next)
+    {
+      GList *node2;
+
+      /* Search the predicate data for a match */
+      for (node2 = pdata->guids; node2; node2 = node2->next)
+      {
+        if (guid_equal (node->data, node2->data))
+          break;
+      }
+
+      /* Check to see if we found a match.  If so, break now */
+      if (node2 != NULL)
+        break;
+    }
+
+    g_list_free(o_list);
+
+    /* yea, node may point to an invalid location, but that's ok.
+     * we're not _USING_ the value, just checking that it's non-NULL
+     */
+
+    break;
+
+  default:
+    /* object is a single object, getter returns a GUID*
+     *
+     * See if the guid is in the list
+     */
+
+    guid = ((query_guid_getter)getter->param_getfcn) (object, getter);
+    for (node = pdata->guids; node; node = node->next)
+    {
+      if (guid_equal (node->data, guid))
+        break;
+    }
+  }
+
+  switch (pdata->options) {
+  case QOF_GUID_MATCH_ANY:
+  case QOF_GUID_MATCH_LIST_ANY:
+    return (node != NULL);
+    break;
+  case QOF_GUID_MATCH_NONE:
+  case QOF_GUID_MATCH_ALL:
+    return (node == NULL);
+    break;
+  case QOF_GUID_MATCH_NULL:
+    return (guid == NULL);
+    break;
+  default:
+    PWARN ("bad match type");
+    return 0;
+  }
+}
+
+static void
+guid_free_pdata (QofQueryPredData *pd)
+{
+  query_guid_t pdata = (query_guid_t)pd;
+  GList *node;
+  VERIFY_PDATA (query_guid_type);
+  for (node = pdata->guids; node; node = node->next)
+  {
+    guid_free (node->data);
+  }
+  g_list_free (pdata->guids);
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+guid_copy_predicate (QofQueryPredData *pd)
+{
+  query_guid_t pdata = (query_guid_t)pd;
+  VERIFY_PDATA_R (query_guid_type);
+  return qof_query_guid_predicate (pdata->options, pdata->guids);
+}
+
+static gboolean
+guid_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_guid_t pd1 = (query_guid_t) p1;
+  query_guid_t pd2 = (query_guid_t) p2;
+  GList *l1 = pd1->guids, *l2 = pd2->guids;
+
+  if (pd1->options != pd2->options) return FALSE;
+  if (g_list_length (l1) != g_list_length (l2)) return FALSE;
+  for ( ; l1 ; l1 = l1->next, l2 = l2->next)
+  {
+    if (!guid_equal (l1->data, l2->data))
+      return FALSE;
+  }
+  return TRUE;
+}
+
+QofQueryPredData *
+qof_query_guid_predicate (QofGuidMatch options, GList *guid_list)
+{
+  query_guid_t pdata;
+  GList *node;
+
+  if (NULL == guid_list) return NULL;
+
+  pdata = g_new0 (query_guid_def, 1);
+  pdata->pd.how = QOF_COMPARE_EQUAL;
+  pdata->pd.type_name = query_guid_type;
+  pdata->options = options;
+
+  pdata->guids = g_list_copy (guid_list);
+  for (node = pdata->guids; node; node = node->next)
+  {
+    GUID *guid = guid_malloc ();
+    *guid = *((GUID *)node->data);
+    node->data = guid;
+  }
+  return ((QofQueryPredData*)pdata);
+}
+
+/* ================================================================ */
+/* QOF_TYPE_INT32 */
+
+static int
+int32_match_predicate (gpointer object, QofParam *getter,
+                       QofQueryPredData *pd)
+{
+  gint32 val;
+  query_int32_t pdata = (query_int32_t)pd;
+
+  VERIFY_PREDICATE (query_int32_type);
+
+  val = ((query_int32_getter)getter->param_getfcn) (object, getter);
+
+  switch (pd->how) {
+  case QOF_COMPARE_LT:
+    return (val < pdata->val);
+  case QOF_COMPARE_LTE:
+    return (val <= pdata->val);
+  case QOF_COMPARE_EQUAL:
+    return (val == pdata->val);
+  case QOF_COMPARE_GT:
+    return (val > pdata->val);
+  case QOF_COMPARE_GTE:
+    return (val >= pdata->val);
+  case QOF_COMPARE_NEQ:
+    return (val != pdata->val);
+  default:
+    PWARN ("bad match type: %d", pd->how);
+    return 0;
+  }
+}
+
+static int
+int32_compare_func (gpointer a, gpointer b, gint options,
+                    QofParam *getter)
+{
+  gint32 v1, v2;
+  g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+  v1 = ((query_int32_getter)getter->param_getfcn)(a, getter);
+  v2 = ((query_int32_getter)getter->param_getfcn)(b, getter);
+
+  if (v1 < v2) return -1;
+  if (v1 > v2) return 1;
+  return 0;
+}
+
+static void
+int32_free_pdata (QofQueryPredData *pd)
+{
+  query_int32_t pdata = (query_int32_t)pd;
+  VERIFY_PDATA (query_int32_type);
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+int32_copy_predicate (QofQueryPredData *pd)
+{
+  query_int32_t pdata = (query_int32_t)pd;
+  VERIFY_PDATA_R (query_int32_type);
+  return qof_query_int32_predicate (pd->how, pdata->val);
+}
+
+static gboolean
+int32_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_int32_t pd1 = (query_int32_t) p1;
+  query_int32_t pd2 = (query_int32_t) p2;
+
+  return (pd1->val == pd2->val);
+}
+
+QofQueryPredData *
+qof_query_int32_predicate (QofQueryCompare how, gint32 val)
+{
+  query_int32_t pdata = g_new0 (query_int32_def, 1);
+  pdata->pd.type_name = query_int32_type;
+  pdata->pd.how = how;
+  pdata->val = val;
+  return ((QofQueryPredData*)pdata);
+}
+
+static char *
+int32_to_string (gpointer object, QofParam *getter)
+{
+  gint32 num = ((query_int32_getter)getter->param_getfcn)(object, getter);
+
+  return g_strdup_printf ("%d", num);
+}
+
+/* ================================================================ */
+/* QOF_TYPE_INT64 */
+
+static int
+int64_match_predicate (gpointer object, QofParam *getter,
+                       QofQueryPredData *pd)
+{
+  gint64 val;
+  query_int64_t pdata = (query_int64_t)pd;
+
+  VERIFY_PREDICATE (query_int64_type);
+
+  val = ((query_int64_getter)getter->param_getfcn) (object, getter);
+
+  switch (pd->how) {
+  case QOF_COMPARE_LT:
+    return (val < pdata->val);
+  case QOF_COMPARE_LTE:
+    return (val <= pdata->val);
+  case QOF_COMPARE_EQUAL:
+    return (val == pdata->val);
+  case QOF_COMPARE_GT:
+    return (val > pdata->val);
+  case QOF_COMPARE_GTE:
+    return (val >= pdata->val);
+  case QOF_COMPARE_NEQ:
+    return (val != pdata->val);
+  default:
+    PWARN ("bad match type: %d", pd->how);
+    return 0;
+  }
+}
+
+static int
+int64_compare_func (gpointer a, gpointer b, gint options,
+                              QofParam *getter)
+{
+  gint64 v1, v2;
+  g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+  v1 = ((query_int64_getter)getter->param_getfcn)(a, getter);
+  v2 = ((query_int64_getter)getter->param_getfcn)(b, getter);
+
+  if (v1 < v2) return -1;
+  if (v1 > v2) return 1;
+  return 0;
+}
+
+static void
+int64_free_pdata (QofQueryPredData *pd)
+{
+  query_int64_t pdata = (query_int64_t)pd;
+  VERIFY_PDATA (query_int64_type);
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+int64_copy_predicate (QofQueryPredData *pd)
+{
+  query_int64_t pdata = (query_int64_t)pd;
+  VERIFY_PDATA_R (query_int64_type);
+  return qof_query_int64_predicate (pd->how, pdata->val);
+}
+
+static gboolean
+int64_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_int64_t pd1 = (query_int64_t) p1;
+  query_int64_t pd2 = (query_int64_t) p2;
+
+  return (pd1->val == pd2->val);
+}
+
+QofQueryPredData *
+qof_query_int64_predicate (QofQueryCompare how, gint64 val)
+{
+  query_int64_t pdata = g_new0 (query_int64_def, 1);
+  pdata->pd.type_name = query_int64_type;
+  pdata->pd.how = how;
+  pdata->val = val;
+  return ((QofQueryPredData*)pdata);
+}
+
+static char *
+int64_to_string (gpointer object, QofParam *getter)
+{
+  gint64 num = ((query_int64_getter)getter->param_getfcn)(object, getter);
+
+  return g_strdup_printf ("%" G_GINT64_FORMAT, num);
+}
+
+/* ================================================================ */
+/* QOF_TYPE_DOUBLE */
+
+static int
+double_match_predicate (gpointer object, QofParam *getter,
+                        QofQueryPredData *pd)
+{
+  double val;
+  query_double_t pdata = (query_double_t)pd;
+
+  VERIFY_PREDICATE (query_double_type);
+
+  val = ((query_double_getter)getter->param_getfcn) (object, getter);
+
+  switch (pd->how) {
+  case QOF_COMPARE_LT:
+    return (val < pdata->val);
+  case QOF_COMPARE_LTE:
+    return (val <= pdata->val);
+  case QOF_COMPARE_EQUAL:
+    return (val == pdata->val);
+  case QOF_COMPARE_GT:
+    return (val > pdata->val);
+  case QOF_COMPARE_GTE:
+    return (val >= pdata->val);
+  case QOF_COMPARE_NEQ:
+    return (val != pdata->val);
+  default:
+    PWARN ("bad match type: %d", pd->how);
+    return 0;
+  }
+}
+
+static int
+double_compare_func (gpointer a, gpointer b, gint options,
+                     QofParam *getter)
+{
+  double v1, v2;
+  g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+  v1 = ((query_double_getter)getter->param_getfcn) (a, getter);
+  v2 = ((query_double_getter)getter->param_getfcn) (b, getter);
+
+  if (v1 < v2) return -1;
+  if (v1 > v2) return 1;
+  return 0;
+}
+
+static void
+double_free_pdata (QofQueryPredData *pd)
+{
+  query_double_t pdata = (query_double_t)pd;
+  VERIFY_PDATA (query_double_type);
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+double_copy_predicate (QofQueryPredData *pd)
+{
+  query_double_t pdata = (query_double_t)pd;
+  VERIFY_PDATA_R (query_double_type);
+  return qof_query_double_predicate (pd->how, pdata->val);
+}
+
+static gboolean
+double_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_double_t pd1 = (query_double_t) p1;
+  query_double_t pd2 = (query_double_t) p2;
+
+  return (pd1->val == pd2->val);
+}
+
+QofQueryPredData *
+qof_query_double_predicate (QofQueryCompare how, double val)
+{
+  query_double_t pdata = g_new0 (query_double_def, 1);
+  pdata->pd.type_name = query_double_type;
+  pdata->pd.how = how;
+  pdata->val = val;
+  return ((QofQueryPredData*)pdata);
+}
+
+static char *
+double_to_string (gpointer object, QofParam *getter)
+{
+  double num = ((query_double_getter)getter->param_getfcn)(object, getter);
+
+  return g_strdup_printf ("%f", num);
+}
+
+/* QOF_TYPE_BOOLEAN =================================================== */
+
+static int
+boolean_match_predicate (gpointer object, QofParam *getter,
+                         QofQueryPredData *pd)
+{
+  gboolean val;
+  query_boolean_t pdata = (query_boolean_t)pd;
+
+  VERIFY_PREDICATE (query_boolean_type);
+
+  val = ((query_boolean_getter)getter->param_getfcn) (object, getter);
+
+  switch (pd->how) {
+  case QOF_COMPARE_EQUAL:
+    return (val == pdata->val);
+  case  QOF_COMPARE_NEQ:
+    return (val != pdata->val);
+  default:
+    PWARN ("bad match type: %d", pd->how);
+    return 0;
+  }
+}
+
+static int
+boolean_compare_func (gpointer a, gpointer b, gint options,
+                      QofParam *getter)
+{
+  gboolean va, vb;
+  g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+  va = ((query_boolean_getter)getter->param_getfcn) (a, getter);
+  vb = ((query_boolean_getter)getter->param_getfcn) (b, getter);
+  if (!va && vb) return -1;
+  if (va && !vb) return 1;
+  return 0;
+}
+
+static void
+boolean_free_pdata (QofQueryPredData *pd)
+{
+  query_boolean_t pdata = (query_boolean_t)pd;
+  VERIFY_PDATA (query_boolean_type);
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+boolean_copy_predicate (QofQueryPredData *pd)
+{
+  query_boolean_t pdata = (query_boolean_t)pd;
+  VERIFY_PDATA_R (query_boolean_type);
+  return qof_query_boolean_predicate (pd->how, pdata->val);
+}
+
+static gboolean
+boolean_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_boolean_t pd1 = (query_boolean_t) p1;
+  query_boolean_t pd2 = (query_boolean_t) p2;
+
+  return (pd1->val == pd2->val);
+}
+
+QofQueryPredData *
+qof_query_boolean_predicate (QofQueryCompare how, gboolean val)
+{
+  query_boolean_t pdata;
+  g_return_val_if_fail (how == QOF_COMPARE_EQUAL || how == QOF_COMPARE_NEQ, NULL);
+
+  pdata = g_new0 (query_boolean_def, 1);
+  pdata->pd.type_name = query_boolean_type;
+  pdata->pd.how = how;
+  pdata->val = val;
+  return ((QofQueryPredData*)pdata);
+}
+
+static char *
+boolean_to_string (gpointer object, QofParam *getter)
+{
+  gboolean num = ((query_boolean_getter)getter->param_getfcn)(object, getter);
+
+  return g_strdup_printf ("%s", (num ? "X" : ""));
+}
+
+/* QOF_TYPE_CHAR =================================================== */
+
+static int
+char_match_predicate (gpointer object, QofParam *getter,
+                                 QofQueryPredData *pd)
+{
+  char c;
+  query_char_t pdata = (query_char_t)pd;
+
+  VERIFY_PREDICATE (query_char_type);
+
+  c = ((query_char_getter)getter->param_getfcn) (object, getter);
+
+  switch (pdata->options) {
+  case QOF_CHAR_MATCH_ANY:
+    if (strchr (pdata->char_list, c)) return 1;
+    return 0;
+  case QOF_CHAR_MATCH_NONE:
+    if (!strchr (pdata->char_list, c)) return 1;
+    return 0;
+  default:
+    PWARN ("bad match type");
+    return 0;
+  }
+}
+
+static int
+char_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
+{
+  char va, vb;
+  g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+  va = ((query_char_getter)getter->param_getfcn)(a, getter);
+  vb = ((query_char_getter)getter->param_getfcn)(b, getter);
+  return (va-vb);
+}
+
+static void
+char_free_pdata (QofQueryPredData *pd)
+{
+  query_char_t pdata = (query_char_t)pd;
+  VERIFY_PDATA (query_char_type);
+  g_free (pdata->char_list);
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+char_copy_predicate (QofQueryPredData *pd)
+{
+  query_char_t pdata = (query_char_t)pd;
+  VERIFY_PDATA_R (query_char_type);
+  return qof_query_char_predicate (pdata->options, pdata->char_list);
+}
+
+static gboolean
+char_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_char_t pd1 = (query_char_t) p1;
+  query_char_t pd2 = (query_char_t) p2;
+
+  if (pd1->options != pd2->options) return FALSE;
+  return (safe_strcmp (pd1->char_list, pd2->char_list) == 0);
+}
+
+QofQueryPredData *
+qof_query_char_predicate (QofCharMatch options, const char *chars)
+{
+  query_char_t pdata;
+  g_return_val_if_fail (chars, NULL);
+  pdata = g_new0 (query_char_def, 1);
+  pdata->pd.type_name = query_char_type;
+  pdata->pd.how = QOF_COMPARE_EQUAL;
+  pdata->options = options;
+  pdata->char_list = g_strdup (chars);
+  return ((QofQueryPredData*)pdata);
+}
+
+static char *
+char_to_string (gpointer object, QofParam *getter)
+{
+  char num = ((query_char_getter)getter->param_getfcn)(object, getter);
+
+  return g_strdup_printf ("%c", num);
+}
+
+/* QOF_TYPE_KVP ================================================ */
+
+static int
+kvp_match_predicate (gpointer object, QofParam *getter,
+                     QofQueryPredData *pd)
+{
+  int compare;
+  KvpFrame *kvp;
+  KvpValue *value;
+  query_kvp_t pdata = (query_kvp_t)pd;
+
+  VERIFY_PREDICATE (query_kvp_type);
+
+  kvp = ((query_kvp_getter)getter->param_getfcn) (object, getter);
+  if (!kvp)
+    return 0;
+
+  value = kvp_frame_get_slot_path_gslist (kvp, pdata->path);
+  if (!value)
+    return 0;
+
+  if (kvp_value_get_type (value) != kvp_value_get_type (pdata->value))
+    return 0;
+
+  compare = kvp_value_compare (value, pdata->value);
+
+  switch (pd->how)
+  {
+  case QOF_COMPARE_LT:
+    return (compare < 0);
+  case QOF_COMPARE_LTE:
+    return (compare <= 0);
+  case QOF_COMPARE_EQUAL:
+    return (compare == 0);
+  case QOF_COMPARE_GTE:
+    return (compare >= 0);
+  case QOF_COMPARE_GT:
+    return (compare > 0);
+  case QOF_COMPARE_NEQ:
+    return (compare != 0);
+  default:
+    PWARN ("bad match type: %d", pd->how);
+    return 0;
+  }
+}
+
+static void
+kvp_free_pdata (QofQueryPredData *pd)
+{
+  query_kvp_t pdata = (query_kvp_t)pd;
+  GSList *node;
+
+  VERIFY_PDATA (query_kvp_type);
+  kvp_value_delete (pdata->value);
+  for (node = pdata->path; node; node = node->next)
+  {
+    g_free (node->data);
+    node->data = NULL;
+  }
+  g_slist_free (pdata->path);
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+kvp_copy_predicate (QofQueryPredData *pd)
+{
+  query_kvp_t pdata = (query_kvp_t)pd;
+  VERIFY_PDATA_R (query_kvp_type);
+  return qof_query_kvp_predicate (pd->how, pdata->path, pdata->value);
+}
+
+static gboolean
+kvp_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_kvp_t pd1 = (query_kvp_t) p1;
+  query_kvp_t pd2 = (query_kvp_t) p2;
+  GSList *n1, *n2;
+
+  n1 = pd1->path;
+  n2 = pd2->path;
+
+  for ( ; n1 && n2; n1 = n1->next, n2 = n2->next)
+  {
+    if (safe_strcmp (n1->data, n2->data) != 0)
+      return FALSE;
+  }
+
+  if (n1 || n2)
+    return FALSE;
+
+  return (kvp_value_compare (pd1->value, pd2->value) == 0);
+}
+
+QofQueryPredData *
+qof_query_kvp_predicate (QofQueryCompare how,
+                         GSList *path, const KvpValue *value)
+{
+  query_kvp_t pdata;
+  GSList *node;
+
+  g_return_val_if_fail (path && value, NULL);
+
+  pdata = g_new0 (query_kvp_def, 1);
+  pdata->pd.type_name = query_kvp_type;
+  pdata->pd.how = how;
+  pdata->value = kvp_value_copy (value);
+  pdata->path = g_slist_copy (path);
+  for (node = pdata->path; node; node = node->next)
+    node->data = g_strdup (node->data);
+
+  return ((QofQueryPredData*)pdata);
+}
+
+QofQueryPredData *
+qof_query_kvp_predicate_path (QofQueryCompare how,
+                              const char *path, const KvpValue *value)
+{
+  QofQueryPredData *pd;
+  GSList *spath = NULL;
+  char *str, *p;
+
+  if (!path) return NULL;
+
+  str = g_strdup (path);
+  p = str;
+  if (0 == *p) return NULL;
+  if ('/' == *p) p++;
+
+  while (p)
+  {
+    spath = g_slist_append (spath, p);
+    p = strchr (p, '/');
+    if (p) { *p = 0; p++; }
+  }
+
+  pd = qof_query_kvp_predicate (how, spath, value);
+  g_free (str);
+  return pd;
+}
+
+
+/* QOF_TYPE_COLLECT =============================================== */
+
+static int
+collect_match_predicate (gpointer object, QofParam *getter,
+                     QofQueryPredData *pd)
+{
+	query_coll_t pdata;
+	QofCollection *coll;
+	GList *node, *node2, *o_list;
+	const GUID *guid;
+
+	pdata = (query_coll_t)pd;
+	VERIFY_PREDICATE (query_collect_type);
+	coll = ((query_collect_getter)getter->param_getfcn) (object, getter);
+	guid = NULL;
+	switch(pdata->options) {
+		case QOF_GUID_MATCH_ALL : {
+			for (node = pdata->guids; node; node = node->next)
+			{
+				for (o_list = object; o_list; o_list = o_list->next)
+				{
+				guid = ((query_guid_getter)getter->param_getfcn)
+					(o_list->data, getter);
+				if (guid_equal (node->data, guid)) {
+					break;
+					}
+				}
+				if (o_list == NULL) {
+					break;
+				}
+			}
+			break;
+		}
+		case QOF_GUID_MATCH_LIST_ANY : {
+			o_list = ((query_glist_getter)getter->param_getfcn) (object, getter);
+			for (node = o_list; node; node = node->next)
+			{
+				for (node2 = pdata->guids; node2; node2 = node2->next)
+				{
+					if (guid_equal (node->data, node2->data)) {
+						break;
+					}
+				}
+				if (node2 != NULL) {
+					break;
+				}
+			}
+			g_list_free(o_list);
+			break;
+		}
+		default : {
+			guid = ((query_guid_getter)getter->param_getfcn) (object, getter);
+			for (node = pdata->guids; node; node = node->next)
+			{
+				if (guid_equal (node->data, guid)) {
+					break;
+				}
+			}
+		}
+		switch (pdata->options) {
+			case QOF_GUID_MATCH_ANY :
+			case QOF_GUID_MATCH_LIST_ANY : {
+				return (node != NULL);
+				break;
+			}
+			case QOF_GUID_MATCH_NONE :
+			case QOF_GUID_MATCH_ALL : {
+				return (node == NULL);
+				break;
+			}
+			case QOF_GUID_MATCH_NULL : {
+				return (guid == NULL);
+				break;
+			}
+			default : {
+				PWARN ("bad match type");
+				return 0;
+			}
+		}
+	}
+	return 0;
+}
+
+static int
+collect_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
+{
+	gint result;
+	QofCollection *c1, *c2;
+
+	c1 = ((query_collect_getter)getter->param_getfcn) (a, getter);
+	c2 = ((query_collect_getter)getter->param_getfcn) (b, getter);
+	result = qof_collection_compare(c1, c2);
+	return result;
+}
+
+static void
+collect_free_pdata (QofQueryPredData *pd)
+{
+	query_coll_t pdata;
+	GList *node;
+
+	node = NULL;
+	pdata = (query_coll_t) pd;
+	VERIFY_PDATA (query_collect_type);
+	for (node = pdata->guids; node; node = node->next)
+	{
+		guid_free (node->data);
+	}
+	qof_collection_destroy(pdata->coll);
+	g_list_free (pdata->guids);
+	g_free (pdata);
+}
+
+static QofQueryPredData *
+collect_copy_predicate (QofQueryPredData *pd)
+{
+	query_coll_t pdata = (query_coll_t) pd;
+
+	VERIFY_PDATA_R (query_collect_type);
+	return qof_query_collect_predicate (pdata->options, pdata->coll);
+}
+
+static gboolean
+collect_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+	query_coll_t pd1;
+	query_coll_t pd2;
+	gint result;
+
+	pd1 = (query_coll_t) p1;
+	pd2 = (query_coll_t) p2;
+	result = qof_collection_compare(pd1->coll, pd2->coll);
+	if(result == 0) { return TRUE; }
+	return FALSE;
+}
+
+static void
+query_collect_cb(QofEntity* ent, gpointer user_data)
+{
+	query_coll_t pdata;
+	GUID *guid;
+
+	guid = guid_malloc();
+	guid = (GUID*)qof_entity_get_guid(ent);
+	pdata = (query_coll_t)user_data;
+	pdata->guids = g_list_append(pdata->guids, guid);
+}
+
+QofQueryPredData *
+qof_query_collect_predicate (QofGuidMatch options, QofCollection *coll)
+{
+	query_coll_t pdata;
+
+	g_return_val_if_fail (coll, NULL);
+	pdata = g_new0 (query_coll_def, 1);
+	pdata->pd.type_name = query_collect_type;
+	pdata->options = options;
+	qof_collection_foreach(coll, query_collect_cb, pdata);
+	if (NULL == pdata->guids) { return NULL; }
+	return ((QofQueryPredData*)pdata);
+}
+
+/* QOF_TYPE_CHOICE */
+
+static int
+choice_match_predicate (gpointer object, QofParam *getter,
+                      QofQueryPredData *pd)
+{
+  query_choice_t pdata = (query_choice_t)pd;
+  GList *node, *o_list;
+  const GUID *guid = NULL;
+
+  VERIFY_PREDICATE (query_choice_type);
+
+  switch (pdata->options) {
+
+  case QOF_GUID_MATCH_ALL:
+    /* object is a GList of objects; param_getfcn must be called on each one.
+     * See if every guid in the predicate is accounted-for in the
+     * object list
+     */
+
+    for (node = pdata->guids; node; node = node->next)
+    {
+      /* See if this GUID matches the object's guid */
+      for (o_list = object; o_list; o_list = o_list->next)
+      {
+        guid = ((query_choice_getter)getter->param_getfcn) (o_list->data, getter);
+        if (guid_equal (node->data, guid))
+          break;
+      }
+
+      /*
+       * If o_list is NULL, we've walked the whole list without finding
+       * a match.  Therefore break out now, the match has failed.
+       */
+      if (o_list == NULL)
+        break;
+    }
+
+    /*
+     * The match is complete.  If node == NULL then we've succesfully
+     * found a match for all the guids in the predicate.  Return
+     * appropriately below.
+     */
+
+    break;
+
+  case QOF_GUID_MATCH_LIST_ANY:
+
+    o_list = ((query_glist_getter)getter->param_getfcn) (object, getter);
+
+    for (node = o_list; node; node = node->next)
+    {
+      GList *node2;
+
+      for (node2 = pdata->guids; node2; node2 = node2->next)
+      {
+        if (guid_equal (node->data, node2->data))
+          break;
+      }
+
+      if (node2 != NULL)
+        break;
+    }
+
+    g_list_free(o_list);
+
+    break;
+
+  default:
+    /* object is a single object, getter returns a GUID*
+     *
+     * See if the guid is in the list
+     */
+
+    guid = ((query_choice_getter)getter->param_getfcn) (object, getter);
+    for (node = pdata->guids; node; node = node->next)
+    {
+      if (guid_equal (node->data, guid))
+        break;
+    }
+  }
+
+  switch (pdata->options) {
+  case QOF_GUID_MATCH_ANY:
+  case QOF_GUID_MATCH_LIST_ANY:
+    return (node != NULL);
+    break;
+  case QOF_GUID_MATCH_NONE:
+  case QOF_GUID_MATCH_ALL:
+    return (node == NULL);
+    break;
+  case QOF_GUID_MATCH_NULL:
+    return (guid == NULL);
+    break;
+  default:
+    PWARN ("bad match type");
+    return 0;
+  }
+}
+
+static void
+choice_free_pdata (QofQueryPredData *pd)
+{
+  query_choice_t pdata = (query_choice_t)pd;
+  GList *node;
+  VERIFY_PDATA (query_choice_type);
+  for (node = pdata->guids; node; node = node->next)
+  {
+    guid_free (node->data);
+  }
+  g_list_free (pdata->guids);
+  g_free (pdata);
+}
+
+static QofQueryPredData *
+choice_copy_predicate (QofQueryPredData *pd)
+{
+  query_choice_t pdata = (query_choice_t)pd;
+  VERIFY_PDATA_R (query_choice_type);
+  return qof_query_choice_predicate (pdata->options, pdata->guids);
+}
+
+static gboolean
+choice_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  query_choice_t pd1 = (query_choice_t) p1;
+  query_choice_t pd2 = (query_choice_t) p2;
+  GList *l1 = pd1->guids, *l2 = pd2->guids;
+
+  if (pd1->options != pd2->options) return FALSE;
+  if (g_list_length (l1) != g_list_length (l2)) return FALSE;
+  for ( ; l1 ; l1 = l1->next, l2 = l2->next)
+  {
+    if (!guid_equal (l1->data, l2->data))
+      return FALSE;
+  }
+  return TRUE;
+}
+
+QofQueryPredData *
+qof_query_choice_predicate (QofGuidMatch options, GList *guid_list)
+{
+  query_choice_t pdata;
+  GList *node;
+
+  if (NULL == guid_list) return NULL;
+
+  pdata = g_new0 (query_choice_def, 1);
+  pdata->pd.how = QOF_COMPARE_EQUAL;
+  pdata->pd.type_name = query_choice_type;
+  pdata->options = options;
+
+  pdata->guids = g_list_copy (guid_list);
+  for (node = pdata->guids; node; node = node->next)
+  {
+    GUID *guid = guid_malloc ();
+    *guid = *((GUID *)node->data);
+    node->data = guid;
+  }
+  return ((QofQueryPredData*)pdata);
+}
+
+
+/* initialization ================================================== */
+/** This function registers a new Core Object with the QofQuery
+ * subsystem.  It maps the "core_name" object to the given
+ * query_predicate, predicate_copy, and predicate_data_free functions.
+ *
+ * An example:
+ * qof_query_register_core_object (QOF_TYPE_STRING, string_match_predicate,
+ *                               string_compare_fcn, string_free_pdata,
+ *                               string_print_fcn, pred_equal_fcn);
+ */
+
+
+static void
+qof_query_register_core_object (QofType core_name,
+                                QofQueryPredicateFunc pred,
+                                QofCompareFunc comp,
+                                QueryPredicateCopyFunc copy,
+                                QueryPredDataFree pd_free,
+                                QueryToString toString,
+                                QueryPredicateEqual pred_equal)
+{
+  g_return_if_fail (core_name);
+  g_return_if_fail (*core_name != '\0');
+
+  if (pred)
+    g_hash_table_insert (predTable, (char *)core_name, pred);
+
+  if (comp)
+    g_hash_table_insert (cmpTable, (char *)core_name, comp);
+
+  if (copy)
+    g_hash_table_insert (copyTable, (char *)core_name, copy);
+
+  if (pd_free)
+    g_hash_table_insert (freeTable, (char *)core_name, pd_free);
+
+  if (toString)
+    g_hash_table_insert (toStringTable, (char *)core_name, toString);
+
+  if (pred_equal)
+    g_hash_table_insert (predEqualTable, (char *)core_name, pred_equal);
+}
+
+static void init_tables (void)
+{
+  unsigned int i;
+  struct
+  {
+    QofType                name;
+    QofQueryPredicateFunc  pred;
+    QofCompareFunc         comp;
+    QueryPredicateCopyFunc copy;
+    QueryPredDataFree      pd_free;
+    QueryToString          toString;
+    QueryPredicateEqual    pred_equal;
+  } knownTypes[] =
+  {
+    { QOF_TYPE_STRING, string_match_predicate, string_compare_func,
+      string_copy_predicate, string_free_pdata, string_to_string,
+      string_predicate_equal },
+    { QOF_TYPE_DATE, date_match_predicate, date_compare_func,
+      date_copy_predicate, date_free_pdata, date_to_string,
+      date_predicate_equal },
+    { QOF_TYPE_DEBCRED, numeric_match_predicate, numeric_compare_func,
+      numeric_copy_predicate, numeric_free_pdata, debcred_to_string,
+      numeric_predicate_equal },
+    { QOF_TYPE_NUMERIC, numeric_match_predicate, numeric_compare_func,
+      numeric_copy_predicate, numeric_free_pdata, numeric_to_string,
+      numeric_predicate_equal },
+    { QOF_TYPE_GUID, guid_match_predicate, NULL,
+      guid_copy_predicate, guid_free_pdata, NULL,
+      guid_predicate_equal },
+    { QOF_TYPE_INT32, int32_match_predicate, int32_compare_func,
+      int32_copy_predicate, int32_free_pdata, int32_to_string,
+      int32_predicate_equal },
+    { QOF_TYPE_INT64, int64_match_predicate, int64_compare_func,
+      int64_copy_predicate, int64_free_pdata, int64_to_string,
+      int64_predicate_equal },
+    { QOF_TYPE_DOUBLE, double_match_predicate, double_compare_func,
+      double_copy_predicate, double_free_pdata, double_to_string,
+      double_predicate_equal },
+    { QOF_TYPE_BOOLEAN, boolean_match_predicate, boolean_compare_func,
+      boolean_copy_predicate, boolean_free_pdata, boolean_to_string,
+      boolean_predicate_equal },
+    { QOF_TYPE_CHAR, char_match_predicate, char_compare_func,
+      char_copy_predicate, char_free_pdata, char_to_string,
+      char_predicate_equal },
+    { QOF_TYPE_KVP, kvp_match_predicate, NULL, kvp_copy_predicate,
+      kvp_free_pdata, NULL, kvp_predicate_equal },
+    { QOF_TYPE_COLLECT, collect_match_predicate, collect_compare_func,
+      collect_copy_predicate, collect_free_pdata, NULL,
+      collect_predicate_equal },
+    { QOF_TYPE_CHOICE, choice_match_predicate, NULL,
+      choice_copy_predicate, choice_free_pdata, NULL, choice_predicate_equal },
+  };
+
+  /* Register the known data types */
+  for (i = 0; i < (sizeof(knownTypes)/sizeof(*knownTypes)); i++)
+  {
+    qof_query_register_core_object (knownTypes[i].name,
+                                knownTypes[i].pred,
+                                knownTypes[i].comp,
+                                knownTypes[i].copy,
+                                knownTypes[i].pd_free,
+                                knownTypes[i].toString,
+                                knownTypes[i].pred_equal);
+  }
+}
+
+static QueryPredicateCopyFunc
+qof_query_copy_predicate (QofType type)
+{
+  QueryPredicateCopyFunc rc;
+  g_return_val_if_fail (type, NULL);
+  rc = g_hash_table_lookup (copyTable, type);
+  return rc;
+}
+
+static QueryPredDataFree
+qof_query_predicate_free (QofType type)
+{
+  g_return_val_if_fail (type, NULL);
+  return g_hash_table_lookup (freeTable, type);
+}
+
+/********************************************************************/
+/* PUBLISHED API FUNCTIONS */
+
+void qof_query_core_init (void)
+{
+  /* Only let us initialize once */
+  if (initialized) return;
+  initialized = TRUE;
+
+  /* Create the tables */
+  predTable = g_hash_table_new (g_str_hash, g_str_equal);
+  cmpTable = g_hash_table_new (g_str_hash, g_str_equal);
+  copyTable = g_hash_table_new (g_str_hash, g_str_equal);
+  freeTable = g_hash_table_new (g_str_hash, g_str_equal);
+  toStringTable = g_hash_table_new (g_str_hash, g_str_equal);
+  predEqualTable = g_hash_table_new (g_str_hash, g_str_equal);
+
+  init_tables ();
+}
+
+void qof_query_core_shutdown (void)
+{
+  if (!initialized) return;
+  initialized = FALSE;
+
+  g_hash_table_destroy (predTable);
+  g_hash_table_destroy (cmpTable);
+  g_hash_table_destroy (copyTable);
+  g_hash_table_destroy (freeTable);
+  g_hash_table_destroy (toStringTable);
+  g_hash_table_destroy (predEqualTable);
+}
+
+QofQueryPredicateFunc
+qof_query_core_get_predicate (QofType type)
+{
+  g_return_val_if_fail (type, NULL);
+  return g_hash_table_lookup (predTable, type);
+}
+
+QofCompareFunc
+qof_query_core_get_compare (QofType type)
+{
+  g_return_val_if_fail (type, NULL);
+  return g_hash_table_lookup (cmpTable, type);
+}
+
+void
+qof_query_core_predicate_free (QofQueryPredData *pdata)
+{
+  QueryPredDataFree free_fcn;
+
+  g_return_if_fail (pdata);
+  g_return_if_fail (pdata->type_name);
+
+  free_fcn = qof_query_predicate_free (pdata->type_name);
+  free_fcn (pdata);
+}
+
+QofQueryPredData *
+qof_query_core_predicate_copy (QofQueryPredData *pdata)
+{
+  QueryPredicateCopyFunc copy;
+
+  g_return_val_if_fail (pdata, NULL);
+  g_return_val_if_fail (pdata->type_name, NULL);
+
+  copy = qof_query_copy_predicate (pdata->type_name);
+  return (copy (pdata));
+}
+
+char *
+qof_query_core_to_string (QofType type, gpointer object,
+                          QofParam *getter)
+{
+  QueryToString toString;
+
+  g_return_val_if_fail (type, NULL);
+  g_return_val_if_fail (object, NULL);
+  g_return_val_if_fail (getter, NULL);
+
+  toString = g_hash_table_lookup (toStringTable, type);
+  g_return_val_if_fail (toString, NULL);
+
+  return toString (object, getter);
+}
+
+gboolean
+qof_query_core_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+  QueryPredicateEqual pred_equal;
+
+  if (p1 == p2) return TRUE;
+  if (!p1 || !p2) return FALSE;
+
+  if (p1->how != p2->how) return FALSE;
+  if (safe_strcmp (p1->type_name, p2->type_name)) return FALSE;
+
+  pred_equal = g_hash_table_lookup (predEqualTable, p1->type_name);
+  g_return_val_if_fail (pred_equal, FALSE);
+
+  return pred_equal (p1, p2);
+}

Added: gnucash/trunk/lib/libqof/qof/qofquerycore.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquerycore.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquerycore.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,196 @@
+/********************************************************************\
+ * qofquerycore.h -- API for providing core Query data types        *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+/** @addtogroup Query
+    @{ */
+
+/** @file qofquerycore.h
+    @brief API for providing core Query data types
+    @author Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>
+*/
+
+#ifndef QOF_QUERYCORE_H
+#define QOF_QUERYCORE_H
+
+#include <glib.h>
+
+#include "gnc-numeric.h"
+#include "gnc-date.h"
+#include "kvp_frame.h"
+#include "qofclass.h"
+
+/**
+ * PREDICATE DATA TYPES: All the predicate data types are rolled up into
+ * the union type PredicateData.  The "type" field specifies which type
+ * the union is.
+ */
+typedef struct _QofQueryPredData QofQueryPredData;
+
+/** Standard Query comparitors, for how to compare objects in a predicate.
+ *  Note that not all core types implement all comparitors
+ */
+typedef enum {
+  QOF_COMPARE_LT = 1,
+  QOF_COMPARE_LTE,
+  QOF_COMPARE_EQUAL,
+  QOF_COMPARE_GT,
+  QOF_COMPARE_GTE,
+  QOF_COMPARE_NEQ
+} QofQueryCompare;
+
+/** List of known core query data-types...
+ *  Each core query type defines it's set of optional "comparitor qualifiers".
+ */
+/* Comparisons for QOF_TYPE_STRING */
+typedef enum {
+  QOF_STRING_MATCH_NORMAL = 1,
+  QOF_STRING_MATCH_CASEINSENSITIVE
+} QofStringMatch;
+
+/** Comparisons for QOF_TYPE_DATE
+ * The QOF_DATE_MATCH_DAY comparison rounds the two time
+ *     values to mid-day and then compares these rounded values.
+ * The QOF_DATE_MATCH_NORMAL comparison matches the time values,
+ *     down to the second.
+ */
+/* XXX remove these deprecated old names .. */
+//#define QOF_DATE_MATCH_ROUNDED QOF_DATE_MATCH_DAY
+//#define QOF_DATE_MATCH_NORMAL  QOF_DATE_MATCH_TIME
+typedef enum {
+  QOF_DATE_MATCH_NORMAL = 1,
+  QOF_DATE_MATCH_DAY
+} QofDateMatch;
+
+/** Comparisons for QOF_TYPE_NUMERIC, QOF_TYPE_DEBCRED
+ *
+ * XXX Should be deprecated, or at least wrapped up as a convnience
+ * function,  this is based on the old bill gribble code, which assumed
+ * the amount was always positive, and then specified a funds-flow
+ * direction (credit, debit, or either).
+ *
+ * The point being that 'match credit' is equivalent to the compound
+ * predicate (amount >= 0) && (amount 'op' value) while the  'match
+ * debit' predicate is equivalent to (amount <= 0) && (abs(amount) 'op' value)
+*/
+
+typedef enum {
+  QOF_NUMERIC_MATCH_DEBIT = 1,
+  QOF_NUMERIC_MATCH_CREDIT,
+  QOF_NUMERIC_MATCH_ANY
+} QofNumericMatch;
+
+/* Comparisons for QOF_TYPE_GUID */
+typedef enum {
+  /** These expect a single object and expect the
+   * QofAccessFunc returns GUID* */
+  QOF_GUID_MATCH_ANY = 1,
+  QOF_GUID_MATCH_NONE,
+  QOF_GUID_MATCH_NULL,
+  /** These expect a GList* of objects and calls the QofAccessFunc routine
+   * on each item in the list to obtain a GUID* for each object */
+  QOF_GUID_MATCH_ALL,
+  /** These expect a single object and expect the QofAccessFunc function
+   * to return a GList* of GUID* (the list is the property of the caller) */
+  QOF_GUID_MATCH_LIST_ANY,
+} QofGuidMatch;
+
+/** A CHAR type is for a RECNCell, Comparisons for QOF_TYPE_CHAR
+ *  'ANY' will match any charagter in the string.
+ *
+ * Match 'ANY' is a convenience/performance-enhanced predicate
+ * for the compound statement (value==char1) || (value==char2) || etc.
+ * Match 'NONE' is equivalent to
+ * (value != char1) && (value != char2) && etc.
+ */
+typedef enum {
+  QOF_CHAR_MATCH_ANY = 1,
+  QOF_CHAR_MATCH_NONE
+} QofCharMatch;
+
+/** No extended comparisons for QOF_TYPE_INT32, QOF_TYPE_INT64,
+ *  QOF_TYPE_DOUBLE, QOF_TYPE_BOOLEAN, QOF_TYPE_KVP
+ */
+
+/** Head of Predicate Data structures.  All PData must start like this. */
+struct _QofQueryPredData {
+  QofType               type_name;  /* QOF_TYPE_* */
+  QofQueryCompare       how;
+};
+
+
+/** @name Core Data Type Predicates
+    @{ */
+QofQueryPredData *qof_query_string_predicate (QofQueryCompare how,
+                                              const char *str,
+                                              QofStringMatch options,
+                                              gboolean is_regex);
+
+QofQueryPredData *qof_query_date_predicate (QofQueryCompare how,
+                                            QofDateMatch options,
+                                            Timespec date);
+
+QofQueryPredData *qof_query_numeric_predicate (QofQueryCompare how,
+                                               QofNumericMatch options,
+                                               gnc_numeric value);
+
+QofQueryPredData *qof_query_guid_predicate (QofGuidMatch options, GList *guids);
+QofQueryPredData *qof_query_int32_predicate (QofQueryCompare how, gint32 val);
+QofQueryPredData *qof_query_int64_predicate (QofQueryCompare how, gint64 val);
+QofQueryPredData *qof_query_double_predicate (QofQueryCompare how, double val);
+QofQueryPredData *qof_query_boolean_predicate (QofQueryCompare how, gboolean val);
+QofQueryPredData *qof_query_char_predicate (QofCharMatch options,
+                                            const char *chars);
+QofQueryPredData *qof_query_collect_predicate (QofGuidMatch options,
+                                            QofCollection *coll);
+QofQueryPredData *qof_query_choice_predicate  (QofGuidMatch options, GList *guids);
+
+/** The qof_query_kvp_predicate() matches the object that has
+ *  the value 'value' located at the path 'path'.  In a certain
+ *  sense, the 'path' is handled as if it were a paramter.
+ */
+QofQueryPredData *qof_query_kvp_predicate (QofQueryCompare how,
+                                           GSList *path,
+                                           const KvpValue *value);
+
+/** Same predicate as above, except that 'path' is assumed to be
+ * a string containing slash-separated pathname. */
+QofQueryPredData *qof_query_kvp_predicate_path (QofQueryCompare how,
+                                                const char *path,
+                                                const KvpValue *value);
+
+/** Copy a predicate. */
+QofQueryPredData *qof_query_core_predicate_copy (QofQueryPredData *pdata);
+
+/** Destroy a predicate. */
+void qof_query_core_predicate_free (QofQueryPredData *pdata);
+
+/** Retrieve a predicate. */
+gboolean qof_query_date_predicate_get_date (QofQueryPredData *pd, Timespec *date);
+/** Return a printable string for a core data object.  Caller needs
+ *  to g_free() the returned string.
+ */
+char * qof_query_core_to_string (QofType, gpointer object, QofParam *getter);
+
+#endif /* QOF_QUERYCORE_H */
+/* @} */
+/* @} */

Added: gnucash/trunk/lib/libqof/qof/qofsession-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofsession-p.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofsession-p.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,76 @@
+/********************************************************************\
+ * qofsession-p.h -- private functions for QOF sessions.            *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+/*
+ * HISTORY:
+ * Copyright (c) 2001 Linux Developers Group
+ * Copyright (c) 1998-2003 Linas Vepstas <linas at linas.org>
+ */
+
+#ifndef QOF_SESSION_P_H
+#define QOF_SESSION_P_H
+
+#include "qofbook.h"
+#include "qofsession.h"
+
+struct _QofSession
+{
+  /* This is just a "fake" entry point to allow me to pass a Session as
+   * an Entity.  NOTE:  THIS IS NOT AN ENTITY!  THE ONLY PART OF ENTITY
+   * THAT IS VALID IS E_TYPE!
+   */
+  QofEntity entity;
+
+  /* A book holds pointers to the various types of datasets.
+   * A session may have multiple books. */
+  GList *books;
+
+  /* The requested book id, in the form or a URI, such as
+   * file:/some/where, or sql:server.host.com:555
+   */
+  char *book_id;
+
+  /* If any book subroutine failed, this records the failure reason 
+   * (file not found, etc).
+   * This is a 'stack' that is one deep.  (Should be deeper ??)
+   * FIXME: Each backend has its own error stack. The session
+   * and the backends should all be using (or making it look like)
+   * there is only one stack.
+   */
+  QofBackendError last_err;
+  char *error_message;
+
+  /* ---------------------------------------------------- */
+  /* Pointer to the backend that is actually used to move data
+   * between the persistant store and the local engine.  */
+  QofBackend *backend;
+};
+
+
+QofBackend * qof_session_get_backend (QofSession *session);
+
+void qof_session_push_error (QofSession *session, QofBackendError err,
+                             const char *message);
+
+QofBackend* gncBackendInit_file(const char *book_id, void *data);
+
+#endif

Added: gnucash/trunk/lib/libqof/qof/qofsession.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofsession.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofsession.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1458 @@
+/********************************************************************\
+ * qofsesssion.c -- session access (connection to backend)          *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+/**
+ * @file qofsession.c
+ * @brief Encapsulate a connection to a storage backend.
+ *
+ * HISTORY:
+ * Created by Linas Vepstas December 1998
+
+ @author Copyright (c) 1998-2004 Linas Vepstas <linas at linas.org>
+ @author Copyright (c) 2000 Dave Peticolas
+ @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+ */
+
+#include "config.h"
+
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include "qofla-dir.h"
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "gnc-event.h"
+#include "qofsession.h"
+#include "qofbackend-p.h"
+#include "qof-be-utils.h"
+#include "qofbook.h"
+#include "qofbook-p.h"
+#include "qofobject.h"
+#include "qofsession-p.h"
+
+/** \deprecated should not be static */
+static QofSession * current_session = NULL;
+static GHookList * session_closed_hooks = NULL;
+static QofLogModule log_module = QOF_MOD_SESSION;
+static GSList *provider_list = NULL;
+
+/* ====================================================================== */
+
+void
+qof_backend_register_provider (QofBackendProvider *prov)
+{
+	provider_list = g_slist_prepend (provider_list, prov);
+}
+
+/* ====================================================================== */
+
+/* hook routines */
+
+void
+qof_session_add_close_hook (GFunc fn, gpointer data)
+{
+  GHook *hook;
+
+  if (session_closed_hooks == NULL) {
+    session_closed_hooks = malloc(sizeof(GHookList));
+    g_hook_list_init (session_closed_hooks, sizeof(GHook));
+  }
+
+  hook = g_hook_alloc(session_closed_hooks);
+  if (!hook)
+    return;
+
+  hook->func = (GHookFunc)fn;
+  hook->data = data;
+  g_hook_append(session_closed_hooks, hook);
+}
+
+void
+qof_session_call_close_hooks (QofSession *session)
+{
+  GHook *hook;
+  GFunc fn;
+
+  if (session_closed_hooks == NULL)
+    return;
+
+  hook = g_hook_first_valid (session_closed_hooks, FALSE);
+  while (hook) {
+    fn = (GFunc)hook->func;
+    fn(session, hook->data);
+    hook = g_hook_next_valid (session_closed_hooks, hook, FALSE);
+  }
+}
+
+/* ====================================================================== */
+/* error handling routines */
+
+static void
+qof_session_clear_error (QofSession *session)
+{
+  QofBackendError err;
+
+  session->last_err = ERR_BACKEND_NO_ERR;
+  g_free(session->error_message);
+  session->error_message = NULL;
+
+  /* pop the stack on the backend as well. */
+  if (session->backend)
+  {
+    do
+    {
+       err = qof_backend_get_error (session->backend);
+    } while (ERR_BACKEND_NO_ERR != err);
+  }
+}
+
+void
+qof_session_push_error (QofSession *session, QofBackendError err,
+                        const char *message)
+{
+  if (!session) return;
+
+  g_free (session->error_message);
+
+  session->last_err = err;
+  session->error_message = g_strdup (message);
+}
+
+QofBackendError
+qof_session_get_error (QofSession * session)
+{
+  QofBackendError err;
+
+  if (!session) return ERR_BACKEND_NO_BACKEND;
+
+  /* if we have a local error, return that. */
+  if (ERR_BACKEND_NO_ERR != session->last_err)
+  {
+    return session->last_err;
+  }
+
+  /* maybe we should return a no-backend error ??? */
+  if (! session->backend) return ERR_BACKEND_NO_ERR;
+
+  err = qof_backend_get_error (session->backend);
+  session->last_err = err;
+  return err;
+}
+
+static const char *
+get_default_error_message(QofBackendError err)
+{
+    return "";
+}
+
+const char *
+qof_session_get_error_message(QofSession *session)
+{
+    if(!session) return "";
+    if(!session->error_message)
+      return get_default_error_message(session->last_err);
+    return session->error_message;
+}
+
+QofBackendError
+qof_session_pop_error (QofSession * session)
+{
+  QofBackendError err;
+
+  if (!session) return ERR_BACKEND_NO_BACKEND;
+
+  err = qof_session_get_error(session);
+  qof_session_clear_error(session);
+
+  return err;
+}
+
+/* ====================================================================== */
+
+static void
+qof_session_init (QofSession *session)
+{
+  if (!session) return;
+
+  session->entity.e_type = QOF_ID_SESSION;
+  session->books = g_list_append (NULL, qof_book_new ());
+  session->book_id = NULL;
+  session->backend = NULL;
+
+  qof_session_clear_error (session);
+}
+
+QofSession *
+qof_session_new (void)
+{
+  QofSession *session = g_new0(QofSession, 1);
+  qof_session_init(session);
+  return session;
+}
+
+/** \deprecated Each application should keep
+their \b own session context. */
+QofSession *
+qof_session_get_current_session (void)
+{
+  if (!current_session)
+  {
+    gnc_engine_suspend_events ();
+    current_session = qof_session_new ();
+    gnc_engine_resume_events ();
+  }
+
+  return current_session;
+}
+
+/** \deprecated Each application should keep
+their \b own session context. */
+void
+qof_session_set_current_session (QofSession *session)
+{
+  current_session = session;
+}
+
+QofBook *
+qof_session_get_book (QofSession *session)
+{
+   GList *node;
+   if (!session) return NULL;
+
+   for (node=session->books; node; node=node->next)
+   {
+      QofBook *book = node->data;
+      if ('y' == book->book_open) return book;
+   }
+   return NULL;
+}
+
+void
+qof_session_add_book (QofSession *session, QofBook *addbook)
+{
+  GList *node;
+  if (!session) return;
+
+  ENTER (" sess=%p book=%p", session, addbook);
+
+  /* See if this book is already there ... */
+  for (node=session->books; node; node=node->next)
+  {
+     QofBook *book = node->data;
+     if (addbook == book) return;
+  }
+
+  if ('y' == addbook->book_open)
+  {
+    /* hack alert -- someone should free all the books in the list,
+     * but it should probably not be us ... since the books backends
+     * should be shutdown first, etc */
+/* XXX this should probably be an error XXX */
+    g_list_free (session->books);
+    session->books = g_list_append (NULL, addbook);
+  }
+  else 
+  {
+/* XXX Need to tell the backend to add a book as well */
+    session->books = g_list_append (session->books, addbook);
+  }
+
+  qof_book_set_backend (addbook, session->backend);
+  LEAVE (" ");
+}
+
+QofBackend * 
+qof_session_get_backend (QofSession *session)
+{
+   if (!session) return NULL;
+   return session->backend;
+}
+
+const char *
+qof_session_get_file_path (QofSession *session)
+{
+   if (!session) return NULL;
+   if (!session->backend) return NULL;
+   return session->backend->fullpath;
+}
+
+const char *
+qof_session_get_url (QofSession *session)
+{
+   if (!session) return NULL;
+   return session->book_id;
+}
+
+/* =============================================================== */
+
+typedef struct qof_entity_copy_data {
+	QofEntity *from;
+	QofEntity *to;
+	GList  *referenceList;
+	GSList *param_list;
+	QofSession *new_session;
+	gboolean error;
+}QofEntityCopyData;
+
+static void
+qof_book_set_partial(QofBook *book)
+{
+	gboolean partial;
+
+	partial =
+         (gboolean)GPOINTER_TO_INT(qof_book_get_data(book, PARTIAL_QOFBOOK));
+	if(!partial) {
+		qof_book_set_data(book, PARTIAL_QOFBOOK, (gboolean*)TRUE);
+	}
+}
+
+void
+qof_session_update_reference_list(QofSession *session, QofEntityReference *reference)
+{
+	QofBook  *book;
+	GList    *book_ref_list;
+
+	book = qof_session_get_book(session);
+	book_ref_list = (GList*)qof_book_get_data(book, ENTITYREFERENCE);
+	book_ref_list = g_list_append(book_ref_list, reference);
+	qof_book_set_data(book, ENTITYREFERENCE, book_ref_list);
+	qof_book_set_partial(book);
+}
+
+static void
+qof_entity_param_cb(QofParam *param, gpointer data)
+{
+	QofEntityCopyData *qecd;
+
+	g_return_if_fail(data != NULL);
+	qecd = (QofEntityCopyData*)data;
+	g_return_if_fail(param != NULL);
+	if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
+			qecd->param_list = g_slist_prepend(qecd->param_list, param);
+	}
+}
+
+QofEntityReference*
+qof_entity_get_reference_from(QofEntity *ent, const QofParam *param)
+{
+	QofEntityReference *reference;
+	QofEntity    *ref_ent;
+	const GUID   *cm_guid;
+	char         cm_sa[GUID_ENCODING_LENGTH + 1];
+	gchar        *cm_string;
+
+	g_return_val_if_fail(param, NULL);
+	ref_ent = (QofEntity*)param->param_getfcn(ent, param);
+	if(ref_ent != NULL) {
+		reference = g_new0(QofEntityReference, 1);
+		reference->type = ent->e_type;
+		reference->ref_guid = g_new(GUID, 1);
+		reference->ent_guid = &ent->guid;
+		reference->param = qof_class_get_parameter(ent->e_type, param->param_name);
+		cm_guid = qof_entity_get_guid(ref_ent);
+		guid_to_string_buff(cm_guid, cm_sa);
+		cm_string = g_strdup(cm_sa);
+		if(TRUE == string_to_guid(cm_string, reference->ref_guid)) {
+			return reference;
+		}
+	}
+	return NULL;
+}
+
+static void
+qof_entity_foreach_copy(gpointer data, gpointer user_data)
+{
+	QofEntity          *importEnt, *targetEnt, *referenceEnt;
+	QofEntityCopyData  *context;
+	QofEntityReference *reference;
+	gboolean           registered_type;
+	/* cm_ prefix used for variables that hold the data to commit */
+	QofParam     *cm_param;
+	gchar        *cm_string, *cm_char;
+	const GUID   *cm_guid;
+	KvpFrame     *cm_kvp;
+	/* function pointers and variables for parameter getters that don't use pointers normally */
+	gnc_numeric cm_numeric, (*numeric_getter) (QofEntity*, QofParam*);
+	double      cm_double,  (*double_getter)  (QofEntity*, QofParam*);
+	gboolean    cm_boolean, (*boolean_getter) (QofEntity*, QofParam*);
+	gint32      cm_i32,     (*int32_getter)   (QofEntity*, QofParam*);
+	gint64      cm_i64,     (*int64_getter)   (QofEntity*, QofParam*);
+	Timespec    cm_date,    (*date_getter)    (QofEntity*, QofParam*);
+	/* function pointers to the parameter setters */
+	void (*string_setter)    (QofEntity*, const char*);
+	void (*date_setter)      (QofEntity*, Timespec);
+	void (*numeric_setter)   (QofEntity*, gnc_numeric);
+	void (*guid_setter)      (QofEntity*, const GUID*);
+	void (*double_setter)    (QofEntity*, double);
+	void (*boolean_setter)   (QofEntity*, gboolean);
+	void (*i32_setter)       (QofEntity*, gint32);
+	void (*i64_setter)       (QofEntity*, gint64);
+	void (*char_setter)      (QofEntity*, char*);
+	void (*kvp_frame_setter) (QofEntity*, KvpFrame*);
+
+	g_return_if_fail(user_data != NULL);
+	context = (QofEntityCopyData*) user_data;
+	cm_date.tv_nsec = 0;
+	cm_date.tv_sec =  0;
+	importEnt = context->from;
+	targetEnt = context->to;
+	registered_type = FALSE;
+	cm_param = (QofParam*) data;
+	g_return_if_fail(cm_param != NULL);
+	if(safe_strcmp(cm_param->param_type, QOF_TYPE_STRING) == 0)  { 
+		cm_string = g_strdup((gchar*)cm_param->param_getfcn(importEnt, cm_param));
+		string_setter = (void(*)(QofEntity*, const char*))cm_param->param_setfcn;
+		if(string_setter != NULL) { string_setter(targetEnt, cm_string); }
+		registered_type = TRUE;
+	}
+	if(safe_strcmp(cm_param->param_type, QOF_TYPE_DATE) == 0) { 
+		date_getter = (Timespec (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+		cm_date = date_getter(importEnt, cm_param);
+		date_setter = (void(*)(QofEntity*, Timespec))cm_param->param_setfcn;
+		if(date_setter != NULL) { date_setter(targetEnt, cm_date); }
+		registered_type = TRUE;
+	}
+	if((safe_strcmp(cm_param->param_type, QOF_TYPE_NUMERIC) == 0)  ||
+	(safe_strcmp(cm_param->param_type, QOF_TYPE_DEBCRED) == 0)) { 
+		numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+		cm_numeric = numeric_getter(importEnt, cm_param);
+		numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_param->param_setfcn;
+		if(numeric_setter != NULL) { numeric_setter(targetEnt, cm_numeric); }
+		registered_type = TRUE;
+	}
+	if(safe_strcmp(cm_param->param_type, QOF_TYPE_GUID) == 0) { 
+		cm_guid = (const GUID*)cm_param->param_getfcn(importEnt, cm_param);
+		guid_setter = (void(*)(QofEntity*, const GUID*))cm_param->param_setfcn;
+		if(guid_setter != NULL) { guid_setter(targetEnt, cm_guid); }
+		registered_type = TRUE;
+	}
+	if(safe_strcmp(cm_param->param_type, QOF_TYPE_INT32) == 0) { 
+		int32_getter = (gint32 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+		cm_i32 = int32_getter(importEnt, cm_param);
+		i32_setter = (void(*)(QofEntity*, gint32))cm_param->param_setfcn;
+		if(i32_setter != NULL) { i32_setter(targetEnt, cm_i32); }
+		registered_type = TRUE;
+	}
+	if(safe_strcmp(cm_param->param_type, QOF_TYPE_INT64) == 0) { 
+		int64_getter = (gint64 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+		cm_i64 = int64_getter(importEnt, cm_param);
+		i64_setter = (void(*)(QofEntity*, gint64))cm_param->param_setfcn;
+		if(i64_setter != NULL) { i64_setter(targetEnt, cm_i64); }
+		registered_type = TRUE;
+	}
+	if(safe_strcmp(cm_param->param_type, QOF_TYPE_DOUBLE) == 0) { 
+		double_getter = (double (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+		cm_double = double_getter(importEnt, cm_param);
+		double_setter = (void(*)(QofEntity*, double))cm_param->param_setfcn;
+		if(double_setter != NULL) { double_setter(targetEnt, cm_double); }
+		registered_type = TRUE;
+	}
+	if(safe_strcmp(cm_param->param_type, QOF_TYPE_BOOLEAN) == 0){ 
+		boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+		cm_boolean = boolean_getter(importEnt, cm_param);
+		boolean_setter = (void(*)(QofEntity*, gboolean))cm_param->param_setfcn;
+		if(boolean_setter != NULL) { boolean_setter(targetEnt, cm_boolean); }
+		registered_type = TRUE;
+	}
+	if(safe_strcmp(cm_param->param_type, QOF_TYPE_KVP) == 0) { 
+		cm_kvp = kvp_frame_copy((KvpFrame*)cm_param->param_getfcn(importEnt,cm_param));
+		kvp_frame_setter = (void(*)(QofEntity*, KvpFrame*))cm_param->param_setfcn;
+		if(kvp_frame_setter != NULL) { kvp_frame_setter(targetEnt, cm_kvp); }
+		registered_type = TRUE;
+	}
+	if(safe_strcmp(cm_param->param_type, QOF_TYPE_CHAR) == 0) { 
+		cm_char = (gchar*)cm_param->param_getfcn(importEnt,cm_param);
+		char_setter = (void(*)(QofEntity*, char*))cm_param->param_setfcn;
+		if(char_setter != NULL) { char_setter(targetEnt, cm_char); }
+		registered_type = TRUE;
+	}
+	if(registered_type == FALSE) {
+		referenceEnt = (QofEntity*)cm_param->param_getfcn(importEnt, cm_param);
+		if(!referenceEnt || !referenceEnt->e_type) { return; }
+		reference = qof_entity_get_reference_from(importEnt, cm_param);
+		if(reference) {
+			qof_session_update_reference_list(context->new_session, reference);
+		}
+	}
+}
+
+static gboolean
+qof_entity_guid_match(QofSession *new_session, QofEntity *original)
+{
+	QofEntity *copy;
+	const GUID *g;
+	QofIdTypeConst type;
+	QofBook *targetBook;
+	QofCollection *coll;
+
+	copy = NULL;
+	g_return_val_if_fail(original != NULL, FALSE);
+	targetBook = qof_session_get_book(new_session);
+	g_return_val_if_fail(targetBook != NULL, FALSE);
+	g = qof_entity_get_guid(original);
+	type = g_strdup(original->e_type);
+	coll = qof_book_get_collection(targetBook, type);
+	copy = qof_collection_lookup_entity(coll, g);
+	if(copy) { return TRUE; }
+	return FALSE;
+}
+
+static void
+qof_entity_list_foreach(gpointer data, gpointer user_data)
+{
+	QofEntityCopyData *qecd;
+	QofEntity *original;
+	QofInstance *inst;
+	QofBook *book;
+	const GUID *g;
+
+	g_return_if_fail(data != NULL);
+	original = (QofEntity*)data;
+	g_return_if_fail(user_data != NULL);
+	qecd = (QofEntityCopyData*)user_data;
+	if(qof_entity_guid_match(qecd->new_session, original)) { return; }
+	qecd->from = original;
+	book = qof_session_get_book(qecd->new_session);
+	inst = (QofInstance*)qof_object_new_instance(original->e_type, book);
+	qecd->to = &inst->entity;
+	g = qof_entity_get_guid(original);
+	qof_entity_set_guid(qecd->to, g);
+	if(qecd->param_list != NULL) { 
+		g_slist_free(qecd->param_list);
+		qecd->param_list = NULL;
+	}
+	qof_class_param_foreach(original->e_type, qof_entity_param_cb, qecd);
+	qof_begin_edit(inst);
+	g_slist_foreach(qecd->param_list, qof_entity_foreach_copy, qecd);
+	qof_commit_edit(inst);
+}
+
+static void
+qof_entity_coll_foreach(QofEntity *original, gpointer user_data)
+{
+	QofEntityCopyData *qecd;
+	const GUID *g;
+	QofBook *targetBook;
+	QofCollection *coll;
+	QofEntity *copy;
+
+	g_return_if_fail(user_data != NULL);
+	copy = NULL;
+	qecd = (QofEntityCopyData*)user_data;
+	targetBook = qof_session_get_book(qecd->new_session);
+	g = qof_entity_get_guid(original);
+	coll = qof_book_get_collection(targetBook, original->e_type);
+	copy = qof_collection_lookup_entity(coll, g);
+	if(copy) { qecd->error = TRUE; }
+}
+
+static void
+qof_entity_coll_copy(QofEntity *original, gpointer user_data)
+{
+	QofEntityCopyData *qecd;
+	QofBook *book;
+	QofInstance *inst;
+	const GUID *g;
+
+	g_return_if_fail(user_data != NULL);
+	qecd = (QofEntityCopyData*)user_data;
+	book = qof_session_get_book(qecd->new_session);
+	inst = (QofInstance*)qof_object_new_instance(original->e_type, book);
+	qecd->to = &inst->entity;
+	qecd->from = original;
+	g = qof_entity_get_guid(original);
+	qof_entity_set_guid(qecd->to, g);
+	qof_begin_edit(inst);
+	g_slist_foreach(qecd->param_list, qof_entity_foreach_copy, qecd);
+	qof_commit_edit(inst);
+}
+
+gboolean qof_entity_copy_to_session(QofSession* new_session, QofEntity* original)
+{
+	QofEntityCopyData qecd;
+	QofInstance *inst;
+	QofBook *book;
+
+	if(!new_session || !original) { return FALSE; }
+	if(qof_entity_guid_match(new_session, original)) { return FALSE; }
+	gnc_engine_suspend_events();
+	qecd.param_list = NULL;
+	book = qof_session_get_book(new_session);
+	qecd.new_session = new_session;
+	qof_book_set_partial(book);
+	inst = (QofInstance*)qof_object_new_instance(original->e_type, book);
+	qecd.to = &inst->entity;
+	qecd.from = original;
+	qof_entity_set_guid(qecd.to, qof_entity_get_guid(original));
+	qof_begin_edit(inst);
+	qof_class_param_foreach(original->e_type, qof_entity_param_cb, &qecd);
+	qof_commit_edit(inst);
+	if(g_slist_length(qecd.param_list) == 0) { return FALSE; }
+	g_slist_foreach(qecd.param_list, qof_entity_foreach_copy, &qecd);
+	g_slist_free(qecd.param_list);
+	gnc_engine_resume_events();
+	return TRUE;
+}
+
+gboolean qof_entity_copy_list(QofSession *new_session, GList *entity_list)
+{
+	QofEntityCopyData *qecd;
+
+	if(!new_session || !entity_list) { return FALSE; }
+	ENTER (" list=%d", g_list_length(entity_list));
+	qecd = g_new0(QofEntityCopyData, 1);
+	gnc_engine_suspend_events();
+	qecd->param_list = NULL;
+	qecd->new_session = new_session;
+	qof_book_set_partial(qof_session_get_book(new_session));
+	g_list_foreach(entity_list, qof_entity_list_foreach, qecd);
+	gnc_engine_resume_events();
+	g_free(qecd);
+	LEAVE (" ");
+	return TRUE;
+}
+
+gboolean qof_entity_copy_coll(QofSession *new_session, QofCollection *entity_coll)
+{
+	QofEntityCopyData qecd;
+
+	gnc_engine_suspend_events();
+	qecd.param_list = NULL;
+	qecd.new_session = new_session;
+	qof_book_set_partial(qof_session_get_book(qecd.new_session));
+	qof_collection_foreach(entity_coll, qof_entity_coll_foreach, &qecd);
+	qof_class_param_foreach(qof_collection_get_type(entity_coll), qof_entity_param_cb, &qecd);
+	qof_collection_foreach(entity_coll, qof_entity_coll_copy, &qecd);
+	if(qecd.param_list != NULL) { g_slist_free(qecd.param_list); }
+	gnc_engine_resume_events();
+	return TRUE;
+}
+
+struct recurse_s
+{
+	QofSession *session;
+	gboolean   success;
+	GList      *ref_list;
+	GList      *ent_list;
+};
+
+static void
+recurse_collection_cb (QofEntity *ent, gpointer user_data)
+{
+	struct recurse_s *store;
+
+	if(user_data == NULL) { return; }
+	store = (struct recurse_s*)user_data;
+	if(!ent || !store) { return; }
+	store->success = qof_entity_copy_to_session(store->session, ent);
+	if(store->success) {
+		store->ent_list = g_list_append(store->ent_list, ent);
+	}
+}
+
+static void
+recurse_ent_cb(QofEntity *ent, gpointer user_data)
+{
+	GList      *ref_list, *i, *j, *ent_list, *child_list;
+	QofParam   *ref_param;
+	QofEntity  *ref_ent, *child_ent;
+	QofSession *session;
+	struct recurse_s *store;
+	gboolean   success;
+
+	if(user_data == NULL) { return; }
+	store = (struct recurse_s*)user_data;
+	session = store->session;
+	success = store->success;
+	ref_list = NULL;
+	child_ent = NULL;
+	ref_list = g_list_copy(store->ref_list);
+	if((!session)||(!ent)) { return; }
+	ent_list = NULL;
+	child_list = NULL;
+	i = NULL;
+	j = NULL;
+	for(i = ref_list; i != NULL; i=i->next)
+	{
+		if(i->data == NULL) { continue; }
+		ref_param = (QofParam*)i->data;
+		if(ref_param->param_name == NULL) { continue; }
+		if(ref_param->param_type == QOF_TYPE_COLLECT) {
+			QofCollection *col;
+			col = ref_param->param_getfcn(ent, ref_param);
+			qof_collection_foreach(col, recurse_collection_cb, store);
+			continue;
+		}
+		ref_ent = (QofEntity*)ref_param->param_getfcn(ent, ref_param);
+		if((ref_ent)&&(ref_ent->e_type))
+		{
+			store->success = qof_entity_copy_to_session(session, ref_ent);
+			if(store->success) { ent_list = g_list_append(ent_list, ref_ent); }
+		}
+	}
+	for(i = ent_list; i != NULL; i = i->next)
+	{
+		if(i->data == NULL) { continue; }
+		child_ent = (QofEntity*)i->data;
+		if(child_ent == NULL) { continue; }
+		ref_list = qof_class_get_referenceList(child_ent->e_type);
+		for(j = ref_list; j != NULL; j = j->next)
+		{
+			if(j->data == NULL) { continue; }
+			ref_param = (QofParam*)j->data;
+			ref_ent = ref_param->param_getfcn(child_ent, ref_param);
+			if(ref_ent != NULL)
+			{
+				success = qof_entity_copy_to_session(session, ref_ent);
+				if(success) { child_list = g_list_append(child_list, ref_ent); }
+			}
+		}
+	}
+	for(i = child_list; i != NULL; i = i->next)
+	{
+		if(i->data == NULL) { continue; }
+		ref_ent = (QofEntity*)i->data;
+		if(ref_ent == NULL) { continue; }
+		ref_list = qof_class_get_referenceList(ref_ent->e_type);
+		for(j = ref_list; j != NULL; j = j->next)
+		{
+			if(j->data == NULL) { continue; }
+			ref_param = (QofParam*)j->data;
+			child_ent = ref_param->param_getfcn(ref_ent, ref_param);
+			if(child_ent != NULL)
+			{
+				qof_entity_copy_to_session(session, child_ent);
+			}
+		}
+	}
+}
+
+gboolean
+qof_entity_copy_coll_r(QofSession *new_session, QofCollection *coll)
+{
+	struct recurse_s store;
+	gboolean success;
+
+	if((!new_session)||(!coll)) { return FALSE; }
+	store.session = new_session;
+	success = TRUE;
+	store.success = success;
+	store.ent_list = NULL;
+	store.ref_list = qof_class_get_referenceList(qof_collection_get_type(coll));
+	success = qof_entity_copy_coll(new_session, coll);
+	if(success){ qof_collection_foreach(coll, recurse_ent_cb, &store); }
+	return success;
+}
+
+gboolean qof_entity_copy_one_r(QofSession *new_session, QofEntity *ent)
+{
+	struct recurse_s store;
+	QofCollection *coll;
+	gboolean success;
+
+	if((!new_session)||(!ent)) { return FALSE; }
+	store.session = new_session;
+	success = TRUE;
+	store.success = success;
+	store.ref_list = qof_class_get_referenceList(ent->e_type);
+	success = qof_entity_copy_to_session(new_session, ent);
+	if(success == TRUE) {
+		coll = qof_book_get_collection(qof_session_get_book(new_session), ent->e_type);
+		qof_collection_foreach(coll, recurse_ent_cb, &store);
+	}
+	return success;
+}
+
+
+/* ====================================================================== */
+
+/* Programs that use their own backends also need to call
+the default QOF ones. The backends specified here are
+loaded only by applications that do not have their own. */
+struct backend_providers
+{
+	const char *libdir;
+	const char *filename;
+	const char *init_fcn;
+};
+
+/* All available QOF backends need to be described here
+and the last entry must be three NULL's.
+Remember: Use the libdir from the current build environment
+and use the .la NOT the .so - .so is not portable! */
+struct backend_providers backend_list[] = {
+	{ QOF_LIB_DIR, "libqof-backend-qsf.la", "qsf_provider_init" },
+#ifdef HAVE_DWI
+	{ QOF_LIB_DIR, "libqof_backend_dwi.la", "dwiend_provider_init" },
+#endif
+	{ NULL, NULL, NULL }
+};
+
+static void
+qof_session_load_backend(QofSession * session, char * access_method)
+{
+	GSList *p;
+	GList *node;
+	QofBackendProvider *prov;
+	QofBook *book;
+	char *msg;
+	gint num;
+	gboolean prov_type;
+	gboolean (*type_check) (const char*);
+	
+	ENTER (" list=%d", g_slist_length(provider_list));
+	prov_type = FALSE;
+	if (NULL == provider_list)
+	{
+		for (num = 0; backend_list[num].filename != NULL; num++) {
+			if(!qof_load_backend_library(backend_list[num].libdir,
+				backend_list[num].filename, backend_list[num].init_fcn))
+			{
+				PWARN (" failed to load %s from %s using %s",
+				backend_list[num].filename, backend_list[num].libdir,
+				backend_list[num].init_fcn);
+			}
+		}
+	}
+	p = g_slist_copy(provider_list);
+	while(p != NULL)
+	{
+		prov = p->data;
+		/* Does this provider handle the desired access method? */
+		if (0 == strcasecmp (access_method, prov->access_method))
+		{
+			/* More than one backend could provide this
+			access method, check file type compatibility. */
+			type_check = (gboolean (*)(const char*)) prov->check_data_type;
+			prov_type = (type_check)(session->book_id);
+			if(!prov_type)
+			{
+				PINFO(" %s not usable", prov->provider_name);
+				p = p->next;
+				continue;
+			}
+			PINFO (" selected %s", prov->provider_name);
+			if (NULL == prov->backend_new) 
+			{
+				p = p->next;
+				continue;
+			}
+			/* Use the providers creation callback */
+			session->backend = (*(prov->backend_new))();
+			session->backend->provider = prov;
+			/* Tell the books about the backend that they'll be using. */
+			for (node=session->books; node; node=node->next)
+			{
+				book = node->data;
+				qof_book_set_backend (book, session->backend);
+			}
+			LEAVE (" ");
+			return;
+		}
+		p = p->next;
+	}
+	msg = g_strdup_printf("failed to load '%s' using access_method", access_method);
+	qof_session_push_error (session, ERR_BACKEND_NO_HANDLER, msg);
+	LEAVE (" ");
+}
+
+/* ====================================================================== */
+
+static void
+qof_session_destroy_backend (QofSession *session)
+{
+  g_return_if_fail (session);
+
+  if (session->backend)
+  {
+    /* clear any error message */
+    char * msg = qof_backend_get_message (session->backend);
+    g_free (msg);
+
+    /* Then destroy the backend */
+    if (session->backend->destroy_backend)
+    {
+      session->backend->destroy_backend(session->backend);
+    }
+    else
+    {
+      g_free(session->backend);
+    }
+  }
+
+  session->backend = NULL;
+}
+
+void
+qof_session_begin (QofSession *session, const char * book_id, 
+                   gboolean ignore_lock, gboolean create_if_nonexistent)
+{
+  char *p, *access_method, *msg;
+  int err;
+
+  if (!session) return;
+
+  ENTER (" sess=%p ignore_lock=%d, book-id=%s", 
+         session, ignore_lock,
+         book_id ? book_id : "(null)");
+
+  /* Clear the error condition of previous errors */
+  qof_session_clear_error (session);
+
+  /* Check to see if this session is already open */
+  if (session->book_id)
+  {
+    qof_session_push_error (session, ERR_BACKEND_LOCKED, NULL);
+    LEAVE("push error book is already open ");
+    return;
+  }
+
+  /* seriously invalid */
+  if (!book_id)
+  {
+    qof_session_push_error (session, ERR_BACKEND_BAD_URL, NULL);
+    LEAVE("push error missing book_id");
+    return;
+  }
+
+  /* Store the session URL  */
+  session->book_id = g_strdup (book_id);
+
+  /* destroy the old backend */
+  qof_session_destroy_backend(session);
+
+  /* Look for something of the form of "file:/", "http://" or 
+   * "postgres://". Everything before the colon is the access 
+   * method.  Load the first backend found for that access method.
+   */
+  p = strchr (book_id, ':');
+  if (p)
+  {
+    access_method = g_strdup (book_id);
+    p = strchr (access_method, ':');
+    *p = 0;
+    qof_session_load_backend(session, access_method);
+    g_free (access_method);
+  }
+  else
+  {
+     /* If no colon found, assume it must be a file-path */
+     qof_session_load_backend(session, "file"); 
+  }
+
+  /* No backend was found. That's bad. */
+  if (NULL == session->backend)
+  {
+    qof_session_push_error (session, ERR_BACKEND_BAD_URL, NULL);
+    LEAVE (" BAD: no backend: sess=%p book-id=%s", 
+         session,  book_id ? book_id : "(null)");
+    return;
+  }
+
+  /* If there's a begin method, call that. */
+  if (session->backend->session_begin)
+  {
+      
+      (session->backend->session_begin)(session->backend, session,
+                                  session->book_id, ignore_lock,
+                                  create_if_nonexistent);
+      PINFO("Done running session_begin on backend");
+      err = qof_backend_get_error(session->backend);
+      msg = qof_backend_get_message(session->backend);
+      if (err != ERR_BACKEND_NO_ERR)
+      {
+          g_free(session->book_id);
+          session->book_id = NULL;
+          qof_session_push_error (session, err, msg);
+          LEAVE(" backend error %d %s", err, msg);
+          return;
+      }
+      if (msg != NULL) 
+      {
+          PWARN("%s", msg);
+          g_free(msg);
+      }
+  }
+
+  LEAVE (" sess=%p book-id=%s", 
+         session,  book_id ? book_id : "(null)");
+}
+
+/* ====================================================================== */
+
+void
+qof_session_load (QofSession *session,
+                  QofPercentageFunc percentage_func)
+{
+	QofBook *newbook, *ob;
+	QofBookList *oldbooks, *node;
+	QofBackend *be;
+	QofBackendError err;
+	
+	if (!session) return;
+	if (!session->book_id) return;
+	
+	ENTER ("sess=%p book_id=%s", session, session->book_id
+		 ? session->book_id : "(null)");
+	
+	/* At this point, we should are supposed to have a valid book 
+	* id and a lock on the file. */
+	
+	oldbooks = session->books;
+	
+	/* XXX why are we creating a book here? I think the books
+	* need to be handled by the backend ... especially since 
+	* the backend may need to load multiple books ... XXX. FIXME.
+	*/
+	newbook = qof_book_new();
+	session->books = g_list_append (NULL, newbook);
+	PINFO ("new book=%p", newbook);
+	
+	qof_session_clear_error (session);
+	
+	/* This code should be sufficient to initialize *any* backend,
+	* whether http, postgres, or anything else that might come along.
+	* Basically, the idea is that by now, a backend has already been
+	* created & set up.  At this point, we only need to get the
+	* top-level account group out of the backend, and that is a
+	* generic, backend-independent operation.
+	*/
+	be = session->backend;
+	qof_book_set_backend(newbook, be);
+	
+	/* Starting the session should result in a bunch of accounts
+	* and currencies being downloaded, but probably no transactions;
+	* The GUI will need to do a query for that.
+	*/
+	if (be)
+	{
+		be->percentage = percentage_func;
+		
+		if (be->load) 
+		{
+			be->load (be, newbook);
+			qof_session_push_error (session, qof_backend_get_error(be), NULL);
+		}
+	}
+
+	/* XXX if the load fails, then we try to restore the old set of books;
+	* however, we don't undo the session id (the URL).  Thus if the 
+	* user attempts to save after a failed load, they weill be trying to 
+	* save to some bogus URL.   This is wrong. XXX  FIXME.
+	*/
+	err = qof_session_get_error(session);
+	if ((err != ERR_BACKEND_NO_ERR) &&
+		(err != ERR_FILEIO_FILE_TOO_OLD) &&
+		(err != ERR_SQL_DB_TOO_OLD))
+	{
+		/* Something broke, put back the old stuff */
+		qof_book_set_backend (newbook, NULL);
+		qof_book_destroy (newbook);
+		g_list_free (session->books);
+		session->books = oldbooks;
+		LEAVE("error from backend %d", qof_session_get_error(session));
+		return;
+	}
+
+	for (node=oldbooks; node; node=node->next)
+	{
+		ob = node->data;
+		qof_book_set_backend (ob, NULL);
+		qof_book_destroy (ob);
+	}
+	
+	LEAVE ("sess = %p, book_id=%s", session, session->book_id
+         ? session->book_id : "(null)");
+}
+
+/* ====================================================================== */
+
+gboolean
+qof_session_save_may_clobber_data (QofSession *session)
+{
+  if (!session) return FALSE;
+  if (!session->backend) return FALSE;
+  if (!session->backend->save_may_clobber_data) return FALSE;
+
+  return (*(session->backend->save_may_clobber_data)) (session->backend);
+}
+
+static gboolean
+save_error_handler(QofBackend *be, QofSession *session)
+{
+    int err;
+    err = qof_backend_get_error(be);
+    
+    if (ERR_BACKEND_NO_ERR != err)
+    {
+        qof_session_push_error (session, err, NULL);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+void
+qof_session_save (QofSession *session,
+                  QofPercentageFunc percentage_func)
+{
+	GList *node;
+	QofBackend *be;
+	gboolean partial, change_backend;
+	QofBackendProvider *prov;
+	GSList *p;
+	QofBook *book, *abook;
+	int err;
+	gint num;
+	char *msg, *book_id;
+	
+	if (!session) return;
+	ENTER ("sess=%p book_id=%s", 
+		 session, session->book_id ? session->book_id : "(null)");
+	/* Partial book handling. */
+	book = qof_session_get_book(session);
+	partial = (gboolean)GPOINTER_TO_INT(qof_book_get_data(book, PARTIAL_QOFBOOK));
+	change_backend = FALSE;
+	msg = g_strdup_printf(" ");
+	book_id = g_strdup(session->book_id);
+	if(partial == TRUE)
+	{
+		if(session->backend && session->backend->provider) {
+			prov = session->backend->provider;
+			if(TRUE == prov->partial_book_supported)
+			{
+				/* if current backend supports partial, leave alone. */
+				change_backend = FALSE;
+			}
+			else { change_backend = TRUE; }
+		}
+		/* If provider is undefined, assume partial not supported. */
+		else { change_backend = TRUE; }
+	}
+	if(change_backend == TRUE)
+	{
+		qof_session_destroy_backend(session);
+		if (NULL == provider_list)
+		{
+			for (num = 0; backend_list[num].filename != NULL; num++) {
+				qof_load_backend_library(backend_list[num].libdir,
+					backend_list[num].filename, backend_list[num].init_fcn);
+			}
+		}
+		p = g_slist_copy(provider_list);
+		while(p != NULL)
+		{
+			prov = p->data;
+			if(TRUE == prov->partial_book_supported)
+			{
+			/** \todo check the access_method too, not in scope here, yet. */
+			/*	if((TRUE == prov->partial_book_supported) && 
+			(0 == strcasecmp (access_method, prov->access_method)))
+			{*/
+				if (NULL == prov->backend_new) continue;
+				/* Use the providers creation callback */
+				session->backend = (*(prov->backend_new))();
+				session->backend->provider = prov;
+				if (session->backend->session_begin)
+				{
+					/* Call begin - backend has been changed,
+					   so make sure a file can be written,
+					   use ignore_lock and create_if_nonexistent */
+					g_free(session->book_id);
+					session->book_id = NULL;
+					(session->backend->session_begin)(session->backend, session,
+						book_id, TRUE, TRUE);
+					PINFO("Done running session_begin on changed backend");
+					err = qof_backend_get_error(session->backend);
+					msg = qof_backend_get_message(session->backend);
+					if (err != ERR_BACKEND_NO_ERR)
+					{
+						g_free(session->book_id);
+						session->book_id = NULL;
+						qof_session_push_error (session, err, msg);
+						LEAVE("changed backend error %d", err);
+						return;
+					}
+					if (msg != NULL) 
+					{
+						PWARN("%s", msg);
+						g_free(msg);
+					}
+				}
+				/* Tell the books about the backend that they'll be using. */
+				for (node=session->books; node; node=node->next)
+				{
+					book = node->data;
+					qof_book_set_backend (book, session->backend);
+				}
+				p = NULL;
+			}
+			if(p) {
+				p = p->next;
+			}
+		}
+		if(!session->backend) 
+		{
+			msg = g_strdup_printf("failed to load backend");
+			qof_session_push_error(session, ERR_BACKEND_NO_HANDLER, msg);
+			return;
+		}
+	}
+	/* If there is a backend, and the backend is reachable
+	* (i.e. we can communicate with it), then synchronize with 
+	* the backend.  If we cannot contact the backend (e.g.
+	* because we've gone offline, the network has crashed, etc.)
+	* then give the user the option to save to the local disk. 
+	*
+	* hack alert -- FIXME -- XXX the code below no longer
+	* does what the words above say.  This needs fixing.
+	*/
+	be = session->backend;
+	if (be)
+	{
+		for (node = session->books; node; node=node->next)
+		{
+			abook = node->data;
+			/* if invoked as SaveAs(), then backend not yet set */
+			qof_book_set_backend (abook, be);
+			be->percentage = percentage_func;
+			if (be->sync)
+			{
+				(be->sync)(be, abook);
+				if (save_error_handler(be, session)) return;
+			}
+		}
+		/* If we got to here, then the backend saved everything 
+		* just fine, and we are done. So return. */
+		/* Return the book_id to previous value. */
+		qof_session_clear_error (session);
+		LEAVE("Success");
+		return;
+	}
+	else
+	{
+		msg = g_strdup_printf("failed to load backend");
+		qof_session_push_error(session, ERR_BACKEND_NO_HANDLER, msg);
+	}
+	LEAVE("error -- No backend!");
+}
+
+/* ====================================================================== */
+/* XXX This exports the list of accounts to a file.  It does not export
+ * any transactions.  Its a place-holder until full book-closing is implemented.
+ */
+
+#ifdef GNUCASH_MAJOR_VERSION
+
+gboolean
+qof_session_export (QofSession *tmp_session,
+                    QofSession *real_session,
+                    QofPercentageFunc percentage_func)
+{
+  QofBook *book;
+  QofBackend *be;
+
+  if ((!tmp_session) || (!real_session)) return FALSE;
+
+  book = qof_session_get_book (real_session);
+  ENTER ("tmp_session=%p real_session=%p book=%p book_id=%s", 
+         tmp_session, real_session, book,
+         tmp_session -> book_id
+         ? tmp_session->book_id : "(null)");
+
+  /* There must be a backend or else.  (It should always be the file
+   * backend too.)
+   */
+  be = tmp_session->backend;
+  if (!be)
+    return FALSE;
+
+  be->percentage = percentage_func;
+  if (be->export)
+    {
+
+      (be->export)(be, book);
+      if (save_error_handler(be, tmp_session)) return FALSE;
+    }
+
+  return TRUE;
+}
+#endif /* GNUCASH_MAJOR_VERSION */
+
+/* ====================================================================== */
+
+void
+qof_session_end (QofSession *session)
+{
+  if (!session) return;
+
+  ENTER ("sess=%p book_id=%s", session, session->book_id
+         ? session->book_id : "(null)");
+
+  /* close down the backend first */
+  if (session->backend && session->backend->session_end)
+  {
+    (session->backend->session_end)(session->backend);
+  }
+
+  qof_session_clear_error (session);
+
+  g_free (session->book_id);
+  session->book_id = NULL;
+
+  LEAVE ("sess=%p book_id=%s", session, session->book_id
+         ? session->book_id : "(null)");
+}
+
+void 
+qof_session_destroy (QofSession *session) 
+{
+  GList *node;
+  if (!session) return;
+
+  ENTER ("sess=%p book_id=%s", session, session->book_id
+         ? session->book_id : "(null)");
+
+  qof_session_end (session);
+
+  /* destroy the backend */
+  qof_session_destroy_backend(session);
+
+  for (node=session->books; node; node=node->next)
+  {
+    QofBook *book = node->data;
+    qof_book_set_backend (book, NULL);
+    qof_book_destroy (book);
+  }
+
+  session->books  = NULL;
+  if (session == current_session)
+    current_session = NULL;
+
+  g_free (session);
+
+  LEAVE ("sess=%p", session);
+}
+
+/* ====================================================================== */
+/* this call is weird. */
+
+void
+qof_session_swap_data (QofSession *session_1, QofSession *session_2)
+{
+  GList *books_1, *books_2, *node;
+
+  if (session_1 == session_2) return;
+  if (!session_1 || !session_2) return;
+
+  ENTER ("sess1=%p sess2=%p", session_1, session_2);
+
+  books_1 = session_1->books;
+  books_2 = session_2->books;
+
+  session_1->books = books_2;
+  session_2->books = books_1;
+
+  for (node=books_1; node; node=node->next)
+  {
+    QofBook *book_1 = node->data;
+    qof_book_set_backend (book_1, session_2->backend);
+  }
+  for (node=books_2; node; node=node->next)
+  {
+    QofBook *book_2 = node->data;
+    qof_book_set_backend (book_2, session_1->backend);
+  }
+
+  LEAVE (" ");
+}
+
+/* ====================================================================== */
+
+gboolean
+qof_session_events_pending (QofSession *session)
+{
+  if (!session) return FALSE;
+  if (!session->backend) return FALSE;
+  if (!session->backend->events_pending) return FALSE;
+
+  return session->backend->events_pending (session->backend);
+}
+
+gboolean
+qof_session_process_events (QofSession *session)
+{
+  if (!session) return FALSE;
+  if (!session->backend) return FALSE;
+  if (!session->backend->process_events) return FALSE;
+
+  return session->backend->process_events (session->backend);
+}
+
+/* ====================================================================== */
+
+#ifdef GNUCASH_MAJOR_VERSION
+
+/* this should go in a separate binary to create a rpc server */
+
+void
+gnc_run_rpc_server (void)
+{
+  const char * dll_err;
+  void * dll_handle;
+  int (*rpc_run)(short);
+  int ret;
+ 
+  /* open and resolve all symbols now (we don't want mystery 
+   * failure later) */
+#ifndef RTLD_NOW
+# ifdef RTLD_LAZY
+#  define RTLD_NOW RTLD_LAZY
+# endif
+#endif
+  dll_handle = dlopen ("libgnc_rpc.so", RTLD_NOW);
+  if (! dll_handle) 
+  {
+    dll_err = dlerror();
+    PWARN (" can't load library: %s\n", dll_err ? dll_err : "");
+    return;
+  }
+  
+  rpc_run = dlsym (dll_handle, "rpc_server_run");
+  dll_err = dlerror();
+  if (dll_err) 
+  {
+    dll_err = dlerror();
+    PWARN (" can't find symbol: %s\n", dll_err ? dll_err : "");
+    return;
+  }
+  
+  ret = (*rpc_run)(0);
+
+  /* XXX How do we force an exit? */
+}
+#endif /* GNUCASH_MAJOR_VERSION */
+
+/* =================== END OF FILE ====================================== */

Added: gnucash/trunk/lib/libqof/qof/qofsession.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofsession.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofsession.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,558 @@
+/********************************************************************\
+ * qofsession.h -- session access (connection to backend)           *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+/** @addtogroup Backend
+ *
+ * The QOF Session 
+ * encapsulates a connection to a storage backend.  That is, it
+ * manages the connection to a persistant data store; whereas
+ * the backend is the thing that performs the actual datastore 
+ * access.
+ *
+ * This class provides several important services:
+ *
+ * 1) It resolves and loads the appropriate backend, based on 
+ *    the URL.
+ *    
+ * 2) It reports backend errors (e.g. network errors, storage 
+ *    corruption errors) through a single, backend-independent 
+ *    API.
+ *
+ * 3) It reports non-error events received from the backend.
+ *
+ * 4) It helps manage global dataset locks.  For example, for the
+ *    file backend, the lock prevents multiple users from editing 
+ *    the same file at the same time, thus avoiding lost data due 
+ *    to race conditions.  Thus, an open session implies that the 
+ *    associated file is locked.
+ *
+ * 5) Misc utilities, such as a search path for the file to be 
+ *    edited, and/or other URL resolution utilities.  This should 
+ *    simplify install & maintenance problems for naive users who
+ *    may not have a good grasp on what a file system is, or where
+ *    they want to keep their data files.
+ *
+ * 6) In the future, this class is probably a good place to manage 
+ *    a portion of the user authentication process, and hold user
+ *    credentials/cookies/keys/tokens.  This is because at the 
+ *    coarsest level, authorization can happen at the datastore
+ *    level: i.e. does this user even have the authority to connect
+ *    to and open this datastore?
+ *
+ * A brief note about books & sessions:
+ * A book encapsulates the datasets manipulated by GnuCash.  A book
+ * holds the actual data.  By contrast, the session mediates the
+ * connection between a book (the thing that lives in virtual memory
+ * in the local process) and the datastore (the place where book
+ * data lives permanently, e.g., file, database).
+ *
+ * In the current design, a session may hold multiple books.  For 
+ * now, exactly what this means is somewhat vague, and code in  
+ * various places makes some implicit assumptions: first, only
+ * one book is 'current' and open for editing.  Next, its assumed 
+ * that all of the books in a session are related in some way.
+ * i.e. that they are all earlier accounting periods of the
+ * currently open book.  In particular, the backends probably 
+ * make that assumption, in order to store the different accounting
+ * periods in a clump so that one can be found, given another.
+ *
+
+   The session now calls QofBackendProvider->check_data_type
+   to check that the incoming path contains data that the
+   backend provider can open. The backend provider should
+   also check if it can contact it's storage media (disk,
+   network, server, etc.) and abort if it can't.  Malformed
+   file URL's would be handled the same way.
+  
+ @{ 
+ */
+
+/** @file qofsession.h
+ * @brief Encapsulates a connection to a backend (persistent store)
+ * @author Copyright (c) 1998, 1999, 2001, 2002 Linas Vepstas <linas at linas.org>
+ * @author Copyright (c) 2000 Dave Peticolas
+ * @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+ */
+
+#ifndef QOF_SESSION_H
+#define QOF_SESSION_H
+
+#include "qofbackend.h"
+#include "qofbook.h"
+#include "qofclass.h"
+#include "qofobject.h"
+
+#define QOF_MOD_SESSION "qof-session"
+
+/* PROTOTYPES ******************************************************/
+
+typedef struct _QofSession    QofSession;
+
+QofSession * qof_session_new (void);
+void         qof_session_destroy (QofSession *session);
+QofSession * qof_session_get_current_session (void);
+void	       qof_session_set_current_session (QofSession *session);
+
+/** The qof_session_swap_data () method swaps the book of
+ *    the two given sessions. It is useful
+ *    for 'Save As' type functionality. */
+void qof_session_swap_data (QofSession *session_1, QofSession *session_2);
+
+/** The qof_session_begin () method begins a new session.
+ *    It takes as an argument the book id. The book id must be a string
+ *    in the form of a URI/URL.
+ *    In the current implementation, the following URL's are supported
+ *    -- File URI of the form 
+ *       "file:/home/somewhere/somedir/file.xac"
+ *       The path part must be a valid path.  The file-part must be 
+ *       a valid old-style-xacc or new-style-gnucash-format file. Paths
+ *       may be relative or absolute. If the path is relative; that is, 
+ *       if the argument is  "file:somefile.xac" then a sequence of 
+ *       search paths are checked for a file of this name.
+ *
+ *    -- Postgres URI of the form
+ *       "postgres://hostname.com/dbname"
+ *       See the src/backend/postgres subdirectory for more info.
+ *
+ *    -- RPC URI of the form rpc://hostname.com/rpcserver.
+ *
+ *    The 'ignore_lock' argument, if set to TRUE, will cause this routine
+ *    to ignore any global-datastore locks (e.g. file locks) that it finds. 
+ *    If set to FALSE, then file/database-global locks will be tested and 
+ *    obeyed.
+ *
+ *    If the datastore exists, can be reached (e.g over the net), 
+ *    connected to, opened and read, and a lock can be obtained then 
+ *    a lock will be obtained.   Note that multi-user datastores 
+ *    (e.g. the SQL backend) typically will not need to get a global
+ *    lock, and thus, the user will not be locked out.  That's the
+ *    whole point of 'multi-user'.
+ *
+ *    If the file/database doesn't exist, and the create_if_nonexistent
+ *    flag is set to TRUE, then the database is created.
+ *
+ *    If an error occurs, it will be pushed onto the session error
+ *    stack, and that is where it should be examined.
+ */
+void qof_session_begin (QofSession *session, const char * book_id,
+                         gboolean ignore_lock, gboolean create_if_nonexistent);
+
+
+/**
+ * The qof_session_load() method causes the QofBook to be made ready to 
+ *    to use with this URL/datastore.   When the URL points at a file, 
+ *    then this routine would load the data from the file.  With remote
+ *    backends, e.g. network or SQL, this would load only enough data
+ *    to make the book actually usable; it would not cause *all* of the
+ *    data to be loaded.
+ *
+ * XXX the current design tries to accomodate multiple calls to 'load'
+ * for each session, each time wiping out the old books; this seems 
+ * wrong to me, and should be restricted to allow only one load per 
+ * session.
+ */
+typedef void (*QofPercentageFunc) (const char *message, double percent);
+void qof_session_load (QofSession *session,
+		       QofPercentageFunc percentage_func);
+
+/** @name Session Errors 
+ @{ */
+/** The qof_session_get_error() routine can be used to obtain the reason
+ *    for any failure.  Calling this routine returns the current error.
+ */
+QofBackendError qof_session_get_error (QofSession *session);
+const char * qof_session_get_error_message(QofSession *session);
+
+/**
+ * The qof_session_pop_error() routine can be used to obtain the reason
+ *    for any failure.  Calling this routine resets the error value.  
+ *
+ *    This routine allows an implementation of multiple error values, 
+ *    e.g. in a stack, where this routine pops the top value. The current 
+ *    implementation has a stack that is one-deep.
+ *
+ *    See qofbackend.h for a listing of returned errors.
+ */
+QofBackendError qof_session_pop_error (QofSession *session);
+/** @} */
+
+/** The qof_session_add_book() allows additional books to be added to
+ *    a session. 
+ * XXX Under construction, clarify the following when done:
+ * XXX There must already be an open book in the session already!?
+ * XXX Only one open book at a time per session is allowed!?
+ * XXX each book gets its own unique backend ???
+ */
+void qof_session_add_book (QofSession *session, QofBook *book);
+
+QofBook * qof_session_get_book (QofSession *session);
+
+/**
+ *    The qof_session_get_file_path() routine returns the fully-qualified file
+ *    path for the session. That is, if a relative or partial filename
+ *    was for the session, then it had to have been fully resolved to
+ *    open the session. This routine returns the result of this resolution.
+ *    The path is always guaranteed to reside in the local file system, 
+ *    even if the session itself was opened as a URL.  (currently, the
+ *    filepath is derived from the url by substituting commas for
+ *    slashes).
+ *
+ * The qof_session_get_url() routine returns the url that was opened.
+ *    URL's for local files take the form of 
+ *    file:/some/where/some/file.gml
+ */
+const char * qof_session_get_file_path (QofSession *session);
+
+const char * qof_session_get_url (QofSession *session);
+
+/**
+ * The qof_session_not_saved() subroutine will return TRUE
+ *    if any data in the session hasn't been saved to long-term storage.
+ */
+gboolean qof_session_not_saved(QofSession *session);
+
+/** FIXME: This isn't as thorough as we might want it to be... */
+gboolean qof_session_save_may_clobber_data (QofSession *session);
+
+/** The qof_session_save() method will commit all changes that have been
+ *    made to the session. For the file backend, this is nothing
+ *    more than a write to the file of the current AccountGroup & etc.
+ *    For the SQL backend, this is typically a no-op (since all data
+ *    has already been written out to the database.
+ */
+void     qof_session_save (QofSession *session,
+			   QofPercentageFunc percentage_func);
+/**
+ * The qof_session_end() method will release the session lock. For the
+ *    file backend, it will *not* save the account group to a file. Thus, 
+ *    this method acts as an "abort" or "rollback" primitive.  However,
+ *    for other backends, such as the sql backend, the data would have
+ *    been written out before this, and so this routines wouldn't 
+ *    roll-back anything; it would just shut the connection.
+ */
+void     qof_session_end  (QofSession *session);
+
+/** @name Copying entities between sessions.
+
+Only certain backends can cope with selective copying of
+entities and only fully defined QOF entities can be copied
+between sessions - see the \ref QSF (QSF) documentation 
+(::qsf_write_file) for more information.
+
+The recommended backend for the new session is QSF or a future
+SQL backend. Using any of these entity copy functions sets a 
+flag in the backend that this is now a partial QofBook.
+When you save a session containing a partial QofBook,
+the session will check that the backend is able to handle the
+partial book. If not, the backend will be replaced by one that
+can handle partial books, preferably one using the same
+::access_method. Currently, this means that a book 
+using the GnuCash XML v2 file backend will be switched to QSF.
+
+Copied entities are identical to the source entity, all parameters
+defined with ::QofAccessFunc and ::QofSetterFunc in QOF are copied
+and the ::GUID of the original ::QofEntity is set in the new entity.
+Sessions containing copied entities are intended for use
+as mechanisms for data export.
+
+It is acceptable to add entities to new_session in batches. Note that
+any of these calls will fail if an entity already exists in new_session
+with the same GUID as any entity to be copied. 
+
+To merge a whole QofBook or where there is any possibility
+of collisions or requirement for user intervention,
+see \ref BookMerge
+
+@{
+
+*/
+
+/** \brief Copy a single QofEntity to another session
+ 
+Checks first that no entity in the session book contains
+the GUID of the source entity. 
+
+ @param new_session - the target session
+ @param original - the QofEntity* to copy
+
+ at return FALSE without copying if the session contains an entity
+with the same GUID already, otherwise TRUE.
+*/
+
+gboolean qof_entity_copy_to_session(QofSession* new_session, QofEntity* original);
+
+/** @brief Copy a GList of entities to another session
+
+The QofBook in the new_session must \b not contain any entities
+with the same GUID as any of the source entities - there is
+no support for handling collisions, instead use \ref BookMerge
+
+Note that the GList (e.g. from ::qof_sql_query_run) can contain
+QofEntity pointers of any ::QofIdType, in any sequence. As long
+as all members of the list are ::QofEntity*, and all GUID's are
+unique, the list can be copied.
+
+ @param new_session - the target session
+ @param entity_list - a GList of QofEntity pointers of any type(s).
+
+ at return FALSE, without copying, if new_session contains any entities
+with the same GUID. Otherwise TRUE.
+
+*/
+gboolean qof_entity_copy_list(QofSession *new_session, GList *entity_list);
+
+/** @brief Copy a QofCollection of entities.
+
+The QofBook in the new_session must \b not contain any entities
+with the same GUID as any entities in the collection - there is
+no support for handling collisions - instead, use \ref BookMerge
+
+ at param new_session - the target session
+ at param entity_coll - a QofCollection of any QofIdType.
+
+ at return FALSE, without copying, if new_session contains any entities
+with the same GUID. Otherwise TRUE.
+*/
+
+gboolean qof_entity_copy_coll(QofSession *new_session, QofCollection *entity_coll);
+
+/** \brief Recursively copy a collection of entities to a session.
+
+\note This function creates a <b>partial QofBook</b>. See 
+::qof_entity_copy_to_session for more information.
+
+The QofBook in the new_session must \b not contain any entities
+with the same GUID as any entities to be copied - there is
+no support for handling collisions - instead, use \ref BookMerge
+
+Objects can be defined solely in terms of QOF data types or
+as a mix of data types and other objects, which may in turn
+include other objects. These references can be copied recursively
+down to the third level. e.g. ::GncInvoice refers to ::GncOwner which
+refers to ::GncCustomer which refers to ::GncAddress. See
+::QofEntityReference.
+
+\note This is a deep recursive copy - every referenced entity is copied
+to the new session, including all parameters. The starting point is all
+entities in the top level collection. It can take some time.
+
+ at param coll A QofCollection of entities that may or may not have 
+references.
+
+ at param new_session The QofSession to receive the copied entities.
+
+ at return TRUE on success; if any individual copy fails, returns FALSE.
+<b>Note</b> : Some entities may have been copied successfully even if
+one of the references fails to copy.
+
+*/
+gboolean
+qof_entity_copy_coll_r(QofSession *new_session, QofCollection *coll);
+
+/** \brief Recursively copy a single entity to a new session.
+
+Copy the single entity and all referenced entities to the second level.
+
+Only entities that are directly referenced by the top level entity are
+copied.
+
+This is a deep copy - all parameters of all referenced entities are copied. If 
+the top level entity has no references, this is identical to 
+::qof_entity_copy_to_session.
+
+ at param ent A single entity that may or may not have references.
+
+ at param new_session The QofSession to receive the copied entities.
+
+ at return TRUE on success; if any individual copy fails, returns FALSE.
+<b>Note</b> : Some entities may have been copied successfully even if
+one of the references fails to copy.
+*/
+gboolean
+qof_entity_copy_one_r(QofSession *new_session, QofEntity *ent);
+
+/** @} 
+*/
+
+/** @name Using a partial QofBook.
+
+Part of the handling for partial books requires a storage mechanism for
+references to entities that are not within reach of the partial book.
+This requires a GList in the book data to contain the reference 
+QofIdType and GUID so that when the book is written out, the
+reference can be included. See ::qof_book_get_data. 
+
+When the file is imported back in, the list needs to be rebuilt.
+The QSF backend rebuilds the references by linking to real entities. Other
+backends can process the hash table in similar ways.
+
+The list stores the QofEntityReference to the referenced entity -
+a struct that contains the GUID and the QofIdType of the referenced entity 
+as well as the parameter used to obtain the reference.
+
+Partial books need to be differentiated in the backend, the 
+flag in the book data is used by qof_session_save to prevent a partial
+book being saved using a backend that requires a full book.
+
+ @{ */
+
+
+/** \brief External references in a partial QofBook.
+
+For use by any session that deals with partial QofBooks.
+It is used by the entity copy functions and by the QSF backend.
+Creates a GList stored in the Book hashtable to contain
+repeated references for a single entity.
+*/
+typedef struct qof_entity_reference {
+	QofIdType       choice_type;/**< When the reference is a different type.*/
+	QofIdType       type;       /**< The type of entity */
+	GUID            *ref_guid;  /**< The GUID of the REFERENCE entity */
+	const QofParam  *param;      /**< The parameter name and type. */
+	const GUID      *ent_guid;   /**< The GUID of the original entity. */
+}QofEntityReference;
+
+/** \brief Get a reference from this entity to another entity.
+
+Used in the preparation of a partial QofBook when the known entity
+(the one currently being copied into the partial book) refers to
+any other entity, usually as a parent or child.
+The routine calls the param_getfcn of the supplied parameter,
+which must return an object (QofEntity*), not a known QOF data type, to
+retrieve the referenced entity and therefore the GUID. The GUID of
+both entities are stored in the reference which then needs to be added
+to the reference list which is added to the partial book data hash.
+The reference itself is used by the backend to preserve the relationship
+between entities within and outside the partial book.
+
+See also ::qof_class_get_referenceList to obtain the list of 
+parameters that provide references to the known entity whilst
+excluding parameters that return known QOF data types.
+
+Note that even if the referenced entity \b exists in the partial
+book (or will exist later), a reference must still be obtained and
+added to the reference list for the book itself. This maintains
+the integrity of the partial book during sequential copy operations.
+
+ at param ent   The known entity.
+ at param param  The parameter to use to get the referenced entity.
+
+ at return FALSE on error, otherwise a pointer to the QofEntityReference.
+*/
+QofEntityReference*
+qof_entity_get_reference_from(QofEntity *ent, const QofParam *param);
+
+/** \brief Adds a new reference to the partial book data hash.
+
+Retrieves any existing reference list and appends the new reference.
+
+If the book is not already marked as partial, it will be marked as partial.
+*/
+void
+qof_session_update_reference_list(QofSession *session, QofEntityReference *reference);
+
+/** Used as the key value for the QofBook data hash.
+ *
+ * Retrieved later by QSF (or any other suitable backend) to
+ * rebuild the references from the QofEntityReference struct
+ * that contains the QofIdType and GUID of the referenced entity
+ * of the original QofBook as well as the parameter data and the
+ * GUID of the original entity.
+ * */
+#define ENTITYREFERENCE "QofEntityReference"
+
+/** \brief Flag indicating a partial QofBook.
+
+When set in the book data with a gboolean value of TRUE,
+the flag denotes that only a backend that supports partial
+books can be used to save this session.
+*/
+
+#define PARTIAL_QOFBOOK "PartialQofBook"
+
+/** @}
+*/
+
+/** \brief Allow session data to be printed to stdout
+
+book_id can't be NULL and we do need to have an access_method,
+so use one to solve the other.
+
+To print a session to stdout, use ::qof_session_begin. Example:
+
+\a qof_session_begin(session,QOF_STDOUT,TRUE,FALSE);
+
+When you call qof_session_save(session, NULL), the output will appear
+on stdout and can be piped or redirected to other processes.
+
+Currently, only the QSF backend supports writing to stdout, other
+backends may return a ::QofBackendError.
+*/
+#define QOF_STDOUT "file:"
+
+/** @name Event Handling
+
+  @{ */
+/** The qof_session_events_pending() method will return TRUE if the backend
+ *    has pending events which must be processed to bring the engine
+ *    up to date with the backend.
+ */
+gboolean qof_session_events_pending (QofSession *session);
+
+/**  The qof_session_process_events() method will process any events indicated
+ *    by the qof_session_events_pending() method. It returns TRUE if the
+ *    engine was modified while engine events were suspended.
+ */
+gboolean qof_session_process_events (QofSession *session);
+/** @} */
+
+#ifdef GNUCASH_MAJOR_VERSION
+/** Run the RPC Server 
+ *  @deprecated  will go away */
+void gnc_run_rpc_server (void);
+
+/** XXX session_export really doesn't belong here .
+ * This functino exports the list of accounts to a file.  Its a stop-gap 
+ * measure until full book-closing is implemented.
+ */
+gboolean qof_session_export (QofSession *tmp_session,
+			     QofSession *real_session,
+			     QofPercentageFunc percentage_func);
+
+#endif /* GNUCASH_MAJOR_VERSION */
+
+/** Register a function to be called just before a session is closed.
+ *
+ *  @param fn The function to be called.  The function definition must
+ *  be func(gpointer session, gpointer user_data);
+ *
+ *  @param data The data to be passed to the function. */
+void qof_session_add_close_hook (GFunc fn, gpointer data);
+
+/** Call all registered session close hooks, informing them that the
+ *  specified session is about to be closed.
+ *
+ *  @param session A pointer to the session being closed. */
+void qof_session_call_close_hooks (QofSession *session);
+
+#endif /* QOF_SESSION_H */
+/** @} */

Added: gnucash/trunk/lib/libqof/qof/qofsql.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofsql.c	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofsql.c	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,928 @@
+/********************************************************************\
+ * qofsql.c -- QOF client-side SQL parser                           *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+/**
+    @file qofsql.c
+    @brief QOF client-side SQL parser.
+    @author Copyright (C) 2004 Linas Vepstas <linas at linas.org>
+
+*/
+#define _GNU_SOURCE
+
+#include <stdlib.h>   /* for working atoll */
+#include <errno.h>
+#include "glib.h"
+#include "config.h"
+#ifdef HAVE_GDA
+#include <sql/sql_parser.h>
+#else
+#include "sql_parser.h"
+#endif
+#include <time.h>
+#include "kvp_frame.h"
+#include "gnc-date.h"
+#include "gnc-numeric.h"
+#include "gnc-trace.h"
+#include "guid.h"
+#include "qofbook.h"
+#include "qofquery.h"
+#include "qofquerycore.h"
+#include "qofsql.h"
+#include "gnc-engine-util.h"
+#include "qofinstance-p.h"
+#include "qofobject.h"
+
+static QofLogModule log_module = QOF_MOD_QUERY;
+
+/* =================================================================== */
+
+struct _QofSqlQuery
+{
+	sql_statement *parse_result;
+	QofQuery *qof_query;
+	QofBook *book;
+	char * single_global_tablename;
+	KvpFrame *kvp_join;
+	GList *param_list;
+	QofEntity *inserted_entity;
+};
+
+/* ========================================================== */
+
+QofSqlQuery *
+qof_sql_query_new(void)
+{
+	QofSqlQuery * sqn = (QofSqlQuery *) g_new0 (QofSqlQuery, 1);
+
+	sqn->qof_query = NULL;
+	sqn->parse_result = NULL;
+	sqn->book = NULL;
+	sqn->single_global_tablename = NULL;
+	sqn->kvp_join = NULL;
+
+	return sqn;
+}
+
+/* ========================================================== */
+
+void 
+qof_sql_query_destroy (QofSqlQuery *q)
+{
+	if (!q) return;
+	qof_query_destroy (q->qof_query);
+	sql_destroy (q->parse_result);
+	g_free (q);
+}
+
+/* ========================================================== */
+
+QofQuery * 
+qof_sql_query_get_query (QofSqlQuery *q)
+{
+	if (!q) return NULL;
+	return q->qof_query;
+}
+
+/* ========================================================== */
+
+void 
+qof_sql_query_set_book (QofSqlQuery *q, QofBook *book)
+{
+	if (!q) return;
+	q->book = book;
+}
+
+/* ========================================================== */
+
+void 
+qof_sql_query_set_kvp (QofSqlQuery *q, KvpFrame *kvp)
+{
+	if (!q) return;
+	q->kvp_join = kvp;
+}
+
+/* ========================================================== */
+
+static inline void
+get_table_and_param (char * str, char **tab, char **param)
+{
+	char * end = strchr (str, '.');
+	if (!end) 
+	{
+		*tab = 0;
+		*param = str;
+		return;
+	}
+	*end = 0;
+	*tab = str;
+	*param = end+1;
+}
+
+static inline char * 
+dequote_string (char *str)
+{
+	size_t len;
+	/* strip out quotation marks ...  */
+	if (('\'' == str[0]) ||
+	    ('\"' == str[0]))
+	{
+		str ++;
+		len = strlen(str);
+		str[len-1] = 0;
+	}
+	return str;
+}
+
+static QofQuery *
+handle_single_condition (QofSqlQuery *query, sql_condition * cond)
+{
+	char tmpbuff[128];
+	GSList *param_list;
+	GList *guid_list;
+	QofQueryPredData *pred_data;
+	sql_field_item *sparam, *svalue;
+	char * qparam_name, *qvalue_name, *table_name, *param_name;
+	char *sep, *path,*str,*p;
+	QofQuery *qq;
+	KvpValue *kv, *kval;
+	KvpValueType kvt;
+	QofQueryCompare qop;
+	time_t exact;
+	int rc, len;
+	Timespec ts;
+	QofType param_type;
+	QofGuidMatch gm;
+
+	pred_data = NULL;
+	if (NULL == cond)
+	{
+		PWARN("missing condition");
+		return NULL;
+	}
+	/* -------------------------------- */
+	/* field to match, assumed, for now to be on the left */
+	/* XXX fix this so it can be either left or right */
+	if (NULL == cond->d.pair.left)
+	{
+		PWARN("missing left parameter");
+		return NULL;
+	}
+	sparam = cond->d.pair.left->item;
+	if (SQL_name != sparam->type)
+	{
+		PWARN("we support only parameter names at this time (parsed %d)",
+          sparam->type);
+		return NULL;
+	}
+	qparam_name = sparam->d.name->data;
+	if (NULL == qparam_name)
+	{
+		PWARN ("missing parameter name");
+		return NULL;
+	}
+
+	/* -------------------------------- */
+	/* value to match, assumed, for now, to be on the right. */
+	/* XXX fix this so it can be either left or right */
+	if (NULL == cond->d.pair.right)
+	{
+		PWARN ("missing right parameter");
+		return NULL;
+	}
+	svalue = cond->d.pair.right->item;
+	if (SQL_name != svalue->type)
+	{
+		PWARN("we support only simple values (parsed as %d)", svalue->type);
+		return NULL;
+	}
+	qvalue_name = svalue->d.name->data;
+	if (NULL == qvalue_name)
+	{
+		PWARN("missing value");
+		return NULL;
+	}
+	qvalue_name = dequote_string (qvalue_name);
+	qvalue_name = (char *) qof_util_whitespace_filter (qvalue_name);
+
+	/* Look to see if its the special KVP value holder.
+	 * If it is, look up the value. */
+	if (0 == strncasecmp (qvalue_name, "kvp://", 6))
+	{
+		if (NULL == query->kvp_join)
+		{
+			PWARN ("missing kvp frame");
+			return NULL;
+		}
+		kv = kvp_frame_get_value (query->kvp_join, qvalue_name+5);
+		/* If there's no value, its not an error; 
+		 * we just don't do this predicate */
+		if (!kv) return NULL;  
+		kvt = kvp_value_get_type (kv);
+
+		tmpbuff[0] = 0x0;
+		qvalue_name = tmpbuff;
+		switch (kvt)
+		{
+			case KVP_TYPE_GINT64:
+			{
+				gint64 ival = kvp_value_get_gint64(kv);
+				sprintf (tmpbuff, "%" G_GINT64_FORMAT "\n", ival);
+				break;
+			}
+			case KVP_TYPE_DOUBLE:
+			{
+				double ival = kvp_value_get_double(kv);
+				sprintf (tmpbuff, "%26.18g\n", ival);
+				break;
+			}
+			case KVP_TYPE_STRING:
+				/* If there's no value, its not an error; 
+				 * we just don't do this predicate */
+				qvalue_name = kvp_value_get_string (kv);
+				if (!qvalue_name) return NULL;
+				break;
+			case KVP_TYPE_GUID:
+			case KVP_TYPE_TIMESPEC:
+			case KVP_TYPE_BINARY:
+			case KVP_TYPE_GLIST:
+			case KVP_TYPE_NUMERIC:
+			case KVP_TYPE_FRAME:
+				PWARN ("unhandled kvp type=%d", kvt);
+				return NULL;
+		}
+	}
+
+	/* -------------------------------- */
+	/* Now start building the QOF parameter */
+	param_list = qof_query_build_param_list (qparam_name, NULL);
+
+	/* Get the where-term comparison operator */
+	switch (cond->op)
+	{
+		case SQL_eq:    qop = QOF_COMPARE_EQUAL; break;
+		case SQL_gt:    qop = QOF_COMPARE_GT; break;
+		case SQL_lt:    qop = QOF_COMPARE_LT; break;
+		case SQL_geq:   qop = QOF_COMPARE_GTE; break;
+		case SQL_leq:   qop = QOF_COMPARE_LTE; break;
+		case SQL_diff:  qop = QOF_COMPARE_NEQ; break;
+		default:
+			/* XXX for string-type queries, we should be able to
+			 * support 'IN' for substring search.  Also regex. */
+			PWARN ("Unsupported compare op (parsed as %u)", cond->op);
+			return NULL;
+	}
+
+	/* OK, need to know the type of the thing being matched 
+	 * in order to build the correct predicate.  Get the type 
+	 * from the object parameters. */
+	get_table_and_param (qparam_name, &table_name, &param_name);
+	if (NULL == table_name)
+	{
+		table_name = query->single_global_tablename;
+	}
+	if (NULL == table_name)
+	{
+		PWARN ("Need to specify an object class to query");
+		return NULL;
+	}
+
+	if (FALSE == qof_class_is_registered (table_name)) 
+	{
+		PWARN ("The query object \'%s\' is not known", table_name);
+		return NULL;
+	}
+
+	param_type = qof_class_get_parameter_type (table_name, param_name);
+	if (!param_type) 
+	{
+		PWARN ("The parameter \'%s\' on object \'%s\' is not known", 
+		       param_name, table_name);
+		return NULL;
+	}
+
+	if (!strcmp (param_type, QOF_TYPE_STRING))
+	{
+		pred_data = 
+		    qof_query_string_predicate (qop,        /* comparison to make */
+		          qvalue_name,                      /* string to match */
+		          QOF_STRING_MATCH_CASEINSENSITIVE,  /* case matching */
+		          FALSE);                            /* use_regexp */
+	}
+	else if (!strcmp (param_type, QOF_TYPE_CHAR))
+	{
+		QofCharMatch cm = QOF_CHAR_MATCH_ANY;
+		if (QOF_COMPARE_NEQ == qop) cm = QOF_CHAR_MATCH_NONE;
+		pred_data = qof_query_char_predicate (cm, qvalue_name);
+	}
+	else if (!strcmp (param_type, QOF_TYPE_INT32))
+	{
+		gint32 ival = atoi (qvalue_name);
+		pred_data = qof_query_int32_predicate (qop, ival);
+	}
+	else if (!strcmp (param_type, QOF_TYPE_INT64))
+	{
+		gint64 ival = atoll (qvalue_name);
+		pred_data = qof_query_int64_predicate (qop, ival);
+	}
+	else if (!strcmp (param_type, QOF_TYPE_DOUBLE))
+	{
+		double ival = atof (qvalue_name);
+		pred_data = qof_query_double_predicate (qop, ival);
+	}
+	else if (!strcmp (param_type, QOF_TYPE_BOOLEAN))
+	{
+		gboolean ival = qof_util_bool_to_int (qvalue_name);
+		pred_data = qof_query_boolean_predicate (qop, ival);
+	}
+	else if (!strcmp (param_type, QOF_TYPE_DATE))
+	{
+		/* Use a timezone independent setting */
+		qof_date_format_set(QOF_DATE_FORMAT_UTC);
+		rc = 0;
+		if(FALSE == qof_scan_date_secs (qvalue_name, &exact))
+		{
+			char *tail;
+			exact = strtoll(qvalue_name, &tail, 0);
+//			PWARN ("unable to parse date: %s", qvalue_name);
+//			return NULL;
+		}
+		ts.tv_sec = exact;
+		ts.tv_nsec = 0;
+		pred_data = qof_query_date_predicate (qop, QOF_DATE_MATCH_NORMAL, ts);
+	}
+	else if (!strcmp (param_type, QOF_TYPE_NUMERIC))
+	{
+		gnc_numeric ival;
+		string_to_gnc_numeric (qvalue_name, &ival);
+		pred_data = qof_query_numeric_predicate (qop, QOF_NUMERIC_MATCH_ANY, ival);
+	}
+	else if (!strcmp (param_type, QOF_TYPE_DEBCRED))
+	{
+		// XXX this probably needs some work ... 
+		gnc_numeric ival;
+		string_to_gnc_numeric (qvalue_name, &ival);
+		pred_data = qof_query_numeric_predicate (qop, QOF_NUMERIC_MATCH_ANY, ival);
+	}
+	else if (!strcmp (param_type, QOF_TYPE_GUID))
+	{
+		GUID guid;
+		gboolean rc = string_to_guid (qvalue_name, &guid);
+		if (0 == rc)
+		{
+			PWARN ("unable to parse guid: %s", qvalue_name);
+			return NULL;
+		}
+
+		// XXX less, than greater than don't make sense,
+		// should check for those bad conditions
+
+		gm = QOF_GUID_MATCH_ANY;
+		if (QOF_COMPARE_NEQ == qop) gm = QOF_GUID_MATCH_NONE;
+		guid_list = g_list_append (NULL, &guid);
+		pred_data = qof_query_guid_predicate (gm, guid_list);
+
+		g_list_free (guid_list);
+	}
+	else if (!strcmp (param_type, QOF_TYPE_KVP))
+	{
+		/* We are expecting an encoded value that looks like
+		 * /some/path/string:value
+		 */
+		sep = strchr (qvalue_name, ':');
+		if (!sep) return NULL;
+		*sep = 0;
+		path = qvalue_name;
+		str = sep +1;
+		/* If str has only digits, we know its a plain number.
+		 * If its numbers and a decimal point, assume a float
+		 * If its numbers and a slash, assume numeric
+		 * If its 32 bytes of hex, assume GUID
+		 * If it looks like an iso date ... 
+		 * else assume its a string.
+		 */
+		kval = NULL;
+		len = strlen (str);
+		if ((32 == len) && (32 == strspn (str, "0123456789abcdef")))
+		{
+			GUID guid;
+			string_to_guid (str, &guid);
+			kval = kvp_value_new_guid (&guid);
+		}
+		else
+		if (len == strspn (str, "0123456789"))
+		{
+			kval = kvp_value_new_gint64 (atoll(str));
+		}
+		else
+		if ((p=strchr (str, '.')) && 
+		    ((len-1) == (strspn (str, "0123456789") + 
+		                 strspn (p+1, "0123456789"))))
+		{
+			kval = kvp_value_new_double (atof(str));
+		}
+
+		else
+		if ((p=strchr (str, '/')) && 
+		    ((len-1) == (strspn (str, "0123456789") + 
+		                 strspn (p+1, "0123456789"))))
+		{
+			gnc_numeric num;
+			string_to_gnc_numeric (str, &num);
+			kval = kvp_value_new_gnc_numeric (num);
+		}
+		else
+		if ((p=strchr (str, '-')) && 
+		    (p=strchr (p+1, '-')) && 
+		    (p=strchr (p+1, ' ')) && 
+		    (p=strchr (p+1, ':')) && 
+		    (p=strchr (p+1, ':')))
+		{
+			kval = kvp_value_new_timespec (gnc_iso8601_to_timespec_gmt(str));
+		}
+
+		/* The default handler is a string */
+		if (NULL == kval)
+		{
+			kval = kvp_value_new_string (str);
+		}
+		pred_data = qof_query_kvp_predicate_path (qop, path, kval);
+	}
+	else
+	{
+		PWARN ("The predicate type \"%s\" is unsupported for now", param_type);
+		return NULL;
+	}
+
+	qq = qof_query_create();
+	qof_query_add_term (qq, param_list, pred_data, QOF_QUERY_FIRST_TERM);
+	return qq;
+}
+
+/* ========================================================== */
+
+static QofQuery *
+handle_where (QofSqlQuery *query, sql_where *swear)
+{
+	QofQueryOp qop;
+	QofQuery * qq;
+
+	switch (swear->type)
+	{
+		case SQL_pair:
+		{
+			QofQuery *qleft = handle_where (query, swear->d.pair.left);
+			QofQuery *qright = handle_where (query, swear->d.pair.right);
+			if (NULL == qleft) return qright;
+			if (NULL == qright) return qleft;
+			switch (swear->d.pair.op)
+			{
+				case SQL_and: qop = QOF_QUERY_AND; break;
+				case SQL_or: qop = QOF_QUERY_OR; break;
+				/* XXX should add support for nand, nor, xor */
+				default: 
+					qof_query_destroy (qleft);
+					qof_query_destroy (qright);
+					return NULL;
+			}
+			qq = qof_query_merge (qleft, qright, qop);
+			qof_query_destroy (qleft);
+			qof_query_destroy (qright);
+			return qq;
+		}
+		case SQL_negated:
+		{
+			QofQuery *qq = handle_where (query, swear->d.negated);
+			QofQuery *qneg = qof_query_invert (qq);
+			qof_query_destroy (qq);
+			return qneg;
+		}
+
+		case SQL_single:
+		{
+			sql_condition * cond = swear->d.single;
+			return handle_single_condition (query, cond);
+		}
+	}
+	return NULL;
+}
+
+/* ========================================================== */
+
+static void 
+handle_sort_order (QofSqlQuery *query, GList *sorder_list)
+{
+	GSList *qsp[3];
+	GList *n;
+	gboolean direction[3];
+	int i;
+	sql_order_field *sorder;
+	char * qparam_name;
+
+	if (!sorder_list) return;
+
+	for (i=0; i<3; i++)
+	{
+		qsp[i] = NULL;
+		direction[i] = 0;
+
+		if (sorder_list)
+		{
+			sorder = sorder_list->data;
+
+			/* Set the sort direction */
+			if (SQL_asc == sorder->order_type) direction[i] = TRUE;
+
+			/* Find the parameter name */
+			qparam_name = NULL;
+			n = sorder->name;
+			if (n)
+			{
+				qparam_name = n->data;
+				if (qparam_name) 
+				{
+					qsp[i] = qof_query_build_param_list (qparam_name, NULL);
+				}
+				n = n->next;   /* next parameter */
+			}
+			else
+			{
+				/* if no next parameter, then next order-by */
+				sorder_list = sorder_list->next;
+			}
+		}
+	}
+
+	qof_query_set_sort_order (query->qof_query, qsp[0], qsp[1], qsp[2]);
+	qof_query_set_sort_increasing (query->qof_query, direction[0],
+	                            direction[1], direction[2]);
+}
+
+/* ========================================================== */
+static void
+qof_queryForeachParam( QofParam* param, gpointer user_data) 
+{
+	QofSqlQuery *q;
+
+	g_return_if_fail(user_data != NULL);
+	q = (QofSqlQuery*)user_data;
+	g_return_if_fail(param != NULL);
+	if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
+		q->param_list = g_list_append(q->param_list, param);
+	}
+}
+
+static const char*
+qof_sql_get_value(sql_insert_statement *insert)
+{
+	GList *walk, *cur;
+	const char *insert_string;
+	sql_field *field;
+	sql_field_item * item;
+
+	/* how to cope with multiple results? */
+	if (insert->values == NULL) { return NULL; }
+	insert_string = NULL;
+	for (walk = insert->values; walk != NULL; walk = walk->next) 
+	{
+		field = walk->data;
+		item = field->item;
+		for (cur = item->d.name; cur != NULL; cur = cur->next)
+		{
+			insert_string = g_strdup_printf("%s", (char*)cur->data);
+		}
+	}
+	return insert_string;
+}
+
+static const QofParam*
+qof_sql_get_param(QofIdTypeConst type, sql_insert_statement *insert)
+{
+	GList *walk, *cur;
+	const char *param_name;
+	const QofParam *param;
+	sql_field *field;
+	sql_field_item *item;
+
+	param = NULL;
+	param_name = NULL;
+	if (insert->fields == NULL) { return NULL; }
+	for (walk = insert->fields; walk != NULL; walk = walk->next) 
+	{
+		field = walk->data;
+		item = field->item;
+		for (cur = item->d.name; cur != NULL; cur = cur->next)
+		{
+			param_name = g_strdup_printf("%s", (char*)cur->data);
+		}
+	}
+	param = qof_class_get_parameter(type, param_name);
+	return param;
+}
+
+static void
+qof_sql_insertCB( gpointer value, gpointer data)
+{
+	GList *param_list;
+	QofSqlQuery *q;
+	QofIdTypeConst type;
+	sql_insert_statement *sis;
+	const char *insert_string;
+	gboolean    registered_type;
+	QofEntity   *ent;
+	QofParam    *param;
+	struct tm   query_time;
+	time_t      query_time_t;
+	/* cm_ prefix used for variables that hold the data to commit */
+	gnc_numeric    cm_numeric;
+	double         cm_double;
+	gboolean       cm_boolean;
+	gint32         cm_i32;
+	gint64         cm_i64;
+	Timespec       cm_date;
+	char           cm_char, *tail;
+	GUID           *cm_guid;
+/*	KvpFrame       *cm_kvp;
+	KvpValue       *cm_value;
+	KvpValueType   cm_type;*/
+	const QofParam *cm_param;
+	void (*string_setter)    (QofEntity*, const char*);
+	void (*date_setter)      (QofEntity*, Timespec);
+	void (*numeric_setter)   (QofEntity*, gnc_numeric);
+	void (*double_setter)    (QofEntity*, double);
+	void (*boolean_setter)   (QofEntity*, gboolean);
+	void (*i32_setter)       (QofEntity*, gint32);
+	void (*i64_setter)       (QofEntity*, gint64);
+	void (*char_setter)      (QofEntity*, char);
+/*	void (*kvp_frame_setter) (QofEntity*, KvpFrame*);*/
+
+	q = (QofSqlQuery*)data;
+	ent = q->inserted_entity;
+	param = (QofParam*)value;
+	sis = q->parse_result->statement;
+	type = g_strdup_printf("%s", sis->table->d.simple);
+	insert_string = g_strdup(qof_sql_get_value(sis));
+	cm_param = qof_sql_get_param(type, sis);
+	param_list = g_list_copy(q->param_list);
+	while(param_list != NULL) {
+		if(safe_strcmp(cm_param->param_type, QOF_TYPE_STRING) == 0)  { 
+			string_setter = (void(*)(QofEntity*, const char*))cm_param->param_setfcn;
+			if(string_setter != NULL) { string_setter(ent, insert_string); }
+			registered_type = TRUE;
+		}
+		if(safe_strcmp(cm_param->param_type, QOF_TYPE_DATE) == 0) { 
+			date_setter = (void(*)(QofEntity*, Timespec))cm_param->param_setfcn;
+			strptime(insert_string, QOF_UTC_DATE_FORMAT, &query_time);
+			query_time_t = mktime(&query_time);
+			timespecFromTime_t(&cm_date, query_time_t);
+			if(date_setter != NULL) { date_setter(ent, cm_date); }
+		}
+		if((safe_strcmp(cm_param->param_type, QOF_TYPE_NUMERIC) == 0)  ||
+		(safe_strcmp(cm_param->param_type, QOF_TYPE_DEBCRED) == 0)) { 
+			numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_param->param_setfcn;
+			string_to_gnc_numeric(insert_string, &cm_numeric);
+			if(numeric_setter != NULL) { numeric_setter(ent, cm_numeric); }
+		}
+		if(safe_strcmp(cm_param->param_type, QOF_TYPE_GUID) == 0) { 
+			cm_guid = g_new(GUID, 1);
+			if(TRUE != string_to_guid(insert_string, cm_guid))
+			{
+				LEAVE (" string to guid failed for %s", insert_string);
+				return;
+			}
+/*			reference_type = xmlGetProp(node, QSF_OBJECT_TYPE);
+			if(0 == safe_strcmp(QOF_PARAM_GUID, reference_type)) 
+			{
+				qof_entity_set_guid(qsf_ent, cm_guid);
+			}
+			else {
+				reference = qof_entity_get_reference_from(qsf_ent, cm_param);
+				if(reference) {
+					params->referenceList = g_list_append(params->referenceList, reference);
+				}
+			}*/
+		}
+		if(safe_strcmp(cm_param->param_type, QOF_TYPE_INT32) == 0) { 
+			errno = 0;
+			cm_i32 = (gint32)strtol (insert_string, &tail, 0);
+			if(errno == 0) {
+				i32_setter = (void(*)(QofEntity*, gint32))cm_param->param_setfcn;
+				if(i32_setter != NULL) { i32_setter(ent, cm_i32); }
+			}
+//			else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
+		}
+		if(safe_strcmp(cm_param->param_type, QOF_TYPE_INT64) == 0) { 
+			errno = 0;
+			cm_i64 = strtoll(insert_string, &tail, 0);
+			if(errno == 0) {
+				i64_setter = (void(*)(QofEntity*, gint64))cm_param->param_setfcn;
+				if(i64_setter != NULL) { i64_setter(ent, cm_i64); }
+			}
+//			else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
+		}
+		if(safe_strcmp(cm_param->param_type, QOF_TYPE_DOUBLE) == 0) { 
+			errno = 0;
+			cm_double = strtod(insert_string, &tail);
+			if(errno == 0) {
+				double_setter = (void(*)(QofEntity*, double))cm_param->param_setfcn;
+				if(double_setter != NULL) { double_setter(ent, cm_double); }
+			}
+		}
+		if(safe_strcmp(cm_param->param_type, QOF_TYPE_BOOLEAN) == 0){ 
+			if(0 == safe_strcmp(insert_string, "TRUE")) {
+				cm_boolean = TRUE;
+			}
+			else { cm_boolean = FALSE; }
+			boolean_setter = (void(*)(QofEntity*, gboolean))cm_param->param_setfcn;
+			if(boolean_setter != NULL) { boolean_setter(ent, cm_boolean); }
+		}
+			if(safe_strcmp(cm_param->param_type, QOF_TYPE_KVP) == 0) {
+				
+			}
+		if(safe_strcmp(cm_param->param_type, QOF_TYPE_CHAR) == 0) { 
+			cm_char = *insert_string;
+			char_setter = (void(*)(QofEntity*, char))cm_param->param_setfcn;
+			if(char_setter != NULL) { char_setter(ent, cm_char); }
+		}
+		param_list = param_list->next;
+	}
+}
+
+static QofEntity*
+qof_query_insert(QofSqlQuery *query)
+{
+	QofIdType type;
+	QofInstance *inst;
+	sql_insert_statement *sis;
+	sql_table *sis_t;
+
+	query->param_list = NULL;
+	type = NULL;
+	sis = query->parse_result->statement;
+	switch(sis->table->type) {
+		case SQL_simple: {
+			sis_t = sis->table;
+			query->single_global_tablename = g_strdup_printf("%s", sis_t->d.simple);
+			type = g_strdup(query->single_global_tablename);
+			break;
+		}
+		default: {
+			fprintf(stderr, "default");
+		}
+	}
+	inst = (QofInstance*)qof_object_new_instance(type, query->book);
+	if(inst == NULL) { return NULL; }
+	query->param_list = NULL;
+	query->inserted_entity = &inst->entity;
+	qof_class_param_foreach((QofIdTypeConst)type, qof_queryForeachParam, query);
+	g_list_foreach(query->param_list, qof_sql_insertCB, query);
+	return query->inserted_entity;
+}
+
+static const char*
+sql_type_as_string(sql_statement_type type)
+{
+	switch (type)
+	{
+		case SQL_select : { return "SELECT"; }
+		case SQL_insert : { return "INSERT"; }
+		case SQL_delete : { return "DELETE"; }
+		case SQL_update : { return "UPDATE"; }
+		default : { return "unknown"; }
+	}
+}
+
+void 
+qof_sql_query_parse (QofSqlQuery *query, const char *str)
+{
+	GList *tables;
+	char *buf;
+	sql_select_statement *sss;
+	sql_where *swear;
+
+	if (!query) return;
+
+	/* Delete old query, if any */
+	if (query->qof_query)
+	{
+		qof_query_destroy (query->qof_query);
+		sql_destroy(query->parse_result);
+		query->qof_query = NULL;
+	}
+
+	/* Parse the SQL string */
+	buf = g_strdup(str);
+	query->parse_result = sql_parse (buf);
+	g_free(buf);
+
+	if (!query->parse_result) 
+	{
+		PWARN ("parse error"); 
+		return;
+	}
+
+	if ((SQL_select != query->parse_result->type)&&(SQL_insert != query->parse_result->type))
+	{
+		PWARN("currently, only SELECT or INSERT statements are supported, "
+		         "got type=%s", sql_type_as_string(query->parse_result->type));
+		return;
+	}
+
+	/* If the user wrote "SELECT * FROM tablename WHERE ..."
+	 * then we have a single global tablename.  But if the 
+	 * user wrote "SELECT * FROM tableA, tableB WHERE ..."
+	 * then we don't have a single unique table-name.
+	 */
+	tables = sql_statement_get_tables (query->parse_result);
+	if (1 == g_list_length (tables))
+	{
+		query->single_global_tablename = tables->data;
+	}
+	/* if this is an insert, we're done with the parse. */
+	if(SQL_insert == query->parse_result->type) {
+		query->qof_query = qof_query_create();
+		return;
+	}
+	sss = query->parse_result->statement;
+	swear = sss->where;
+	if (swear)
+	{
+		/* Walk over the where terms, turn them into QOF predicates */
+		query->qof_query = handle_where (query, swear);
+		if (NULL == query->qof_query) return;
+	}
+	else
+	{
+		query->qof_query = qof_query_create();
+	}
+	/* Provide support for different sort orders */
+	handle_sort_order (query, sss->order);
+
+	/* We also want to set the type of thing to search for.
+	 * If the user said SELECT * FROM ... then we should return
+	 * a list of QofEntity.  Otherwise, we return ... ?
+	 * XXX all this needs fixing.
+	 */
+	qof_query_search_for (query->qof_query, query->single_global_tablename);
+}
+
+/* ========================================================== */
+
+GList * 
+qof_sql_query_run (QofSqlQuery *query, const char *str)
+{
+	GList *results;
+
+	if (!query) return NULL;
+
+	qof_sql_query_parse (query, str);
+	if (NULL == query->qof_query) return NULL;
+
+	qof_query_set_book (query->qof_query, query->book);
+	if(SQL_insert == query->parse_result->type){
+		results = NULL;
+		results = g_list_append(results, qof_query_insert(query));
+		return results;
+	}
+	qof_query_print (query->qof_query);
+	results = qof_query_run (query->qof_query);
+
+	return results;
+}
+
+GList * 
+qof_sql_query_rerun (QofSqlQuery *query)
+{
+	GList *results;
+
+	if (!query) return NULL;
+
+	if (NULL == query->qof_query) return NULL;
+
+	qof_query_set_book (query->qof_query, query->book);
+
+	// qof_query_print (query->qof_query);
+	results = qof_query_run (query->qof_query);
+
+	return results;
+}
+
+/* ========================== END OF FILE =================== */

Added: gnucash/trunk/lib/libqof/qof/qofsql.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofsql.h	2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofsql.h	2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,201 @@
+/********************************************************************\
+ * qofsql.h -- QOF client-side SQL parser                           *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+/** @addtogroup Query
+@{ */
+/**
+    @file qofsql.h
+    @brief QOF client-side SQL parser.
+    @author Copyright (C) 2004 Linas Vepstas <linas at linas.org>
+*/
+
+#ifndef QOF_SQL_QUERY_H
+#define QOF_SQL_QUERY_H
+
+#include <glib.h>
+#include <kvp_frame.h>
+#include <qofbook.h>
+#include <qofquery.h>
+
+/** @addtogroup SQL SQL Interface to Query
+
+The types of SQL queries that are allowed at this point are very 
+limited.  In general, only the following types of queries are 
+supported:
+  SELECT * FROM SomeObj WHERE (param_a < 10.0) AND (param_b = "asdf")
+         SORT BY param_c DESC;
+  INSERT INTO SomeObj (param_a, param_b, param_c) VALUES
+        ("value_a", true, "0/1");
+
+For SELECT, the returned list is a list of all of the instances of 'SomeObj' that
+match the query.  The 'SORT' term is optional. The 'WHERE' term is
+optional; but if you don't include 'WHERE', you will get a list of 
+all of the object instances.  The Boolean operations 'AND' and 'OR'
+together with parenthesis can be used to construct arbitrarily 
+nested predicates.
+
+For INSERT, the returned list is a list containing the newly created instance
+of 'SomeObj'.
+
+Joins are not supported directly.
+  SELECT * FROM ObjA,ObjB WHERE (ObjA.param_id = ObjB.param_other_id);
+The problem with the above is that the search requires a nested
+search loop, aka a 'join', which is not currently supported in the
+underlying QofQuery code.
+
+However, by repeating queries and adding the entities to a new session using
+::qof_entity_copy_list, a series of queries can be added to a single
+book. e.g. You can insert multiple entities and save out as a QSF XML
+file or use multiple SELECT queries to build a precise list - this
+can be used to replicate most of the functionality of a SQL join.
+
+SELECT * from ObjA where param_id = value;
+SELECT * from ObjB where param_other_id = value;
+
+Equivalent to:
+SELECT * from ObjA,ObjB where param_id = param_other_id and param_id = value;
+
+When combined with a foreach callback on the value of param_id for each
+entity in the QofBook, you can produce the effect of a join from running
+the two SELECT queries for each value of param_id held in 'value'.
+
+See ::QofEntityForeachCB and ::qof_object_foreach.
+
+Date queries handle full date and time strings, using the format
+exported by the QSF backend. To query dates and times, convert
+user input into UTC time using the ::QOF_UTC_DATE_FORMAT string.
+e.g. set the UTC date format and call ::qof_print_time_buff
+with a time_t obtained via ::timespecToTime_t.
+
+If the param is a KVP frame, then we use a special markup to 
+indicate frame values.  The markup should look like 
+/some/kvp/path:value. Thus, for example,
+  SELECT * FROM SomeObj WHERE (param_a < '/some/kvp:10.0')
+will search for the object where param_a is a KVP frame, and this
+KVP frame contains a path '/some/kvp' and the value stored at that
+path is floating-point and that float value is less than 10.0.
+
+The following are types of queries are NOT supported:
+  SELECT a,b,c FROM ...
+I am thinking of implementing the above as a list of KVP's
+whose keys would be a,b,c and values would be the results of the
+search. (Linas)
+
+XXX (Neil W). Alternatively, I need to use something like this
+when converting QOF objects between applications by using the
+returned parameter values to create a second object. One application
+using QOF could register objects from two applications and convert
+data from one to the other by using SELECT a,b,c FROM ObjA;
+SELECT d,f,k FROM ObjB; qof_object_new_instance(); ObjC_set_a(value_c);
+ObjC_set_b(value_k) etc. What's needed is for the SELECT to return
+a complete object that only contains the parameters selected.
+
+ Also unsupported:  UPDATE. 
+ 
+Certain SQL commands can have no QOF equivalent and will
+generate a runtime parser error:
+ - ALTER
+ - CREATE
+ - DROP
+ - FLUSH
+ - GRANT
+ - KILL
+ - LOCK
+ - OPTIMIZE
+ - REVOKE
+ - USE
+
+  @{ */
+typedef struct _QofSqlQuery QofSqlQuery;
+
+/** Create a new SQL-syntax query machine.
+ */
+QofSqlQuery * qof_sql_query_new (void);
+void qof_sql_query_destroy (QofSqlQuery *);
+
+/** Set the book to be searched (you can search multiple books)
+ *  If no books are set, no results will be returned (since there
+ *  is nothing to search over).
+ */
+void qof_sql_query_set_book (QofSqlQuery *q, QofBook *book);
+
+/** \brief Perform the query, return the results.
+
+ *  The book must be set in order to be able to perform a query.
+ *
+ *  The returned list will have been sorted using the indicated sort 
+ *  order, (by default ascending order) and trimmed to the
+ *  max_results length.
+ *  Do NOT free the resulting list.  This list is managed internally
+ *  by QofSqlQuery.
+ *
+ */
+
+GList * qof_sql_query_run (QofSqlQuery *query, const char * str);
+
+/** Same ::qof_sql_query_run, but just parse/pre-process the query; do
+  not actually run it over the dataset.  The QofBook does not
+  need to be set before calling this function.
+*/
+
+void qof_sql_query_parse (QofSqlQuery *query, const char * str);
+
+/** Return the QofQuery form of the previously parsed query. */
+QofQuery * qof_sql_query_get_query (QofSqlQuery *);
+
+/** Run the previously parsed query.  The QofBook must be set 
+ *  before this function can be called.  Note, teh QofBook can
+ *  be changed between each successive call to this routine.
+ *  This routine can be called after either qof_sql_query_parse()
+ *  or qof_sql_query_run() because both will set up the parse.
+ */
+GList * qof_sql_query_rerun (QofSqlQuery *query);
+
+/** 
+ * Set the kvp frame to be used for formulating 'indirect' predicates.
+ *
+ * Although joins are not supported (see above), there is one special
+ * hack that one can use to pass data indirectly into the predicates.
+ * This is by using a KVP key name to reference the value to be used
+ * for a predicate.  Thus, for example, 
+ *   SELECT * FROM SomeObj WHERE (param_a = KVP:/some/key/path);
+ * will look up the value stored at '/some/key/path', and use that
+ * value to form the actual predicate.   So, for example, if 
+ * the value stored at '/some/key/path' was 2, then the actual 
+ * query run will be 
+ *   SELECT * FROM SomeObj WHERE (param_a = 2);
+ * The lookup occurs at the time that the query is formulated.
+ *
+ * The query does *not* take over ownership of the kvp frame,
+ * nor does it copy it. Thus, the kvp frame must exist when the
+ * query is formulated, and it is the responsibility of the 
+ * caller to free it when no longer needed.
+ *
+ * Note that because this feature is a kind of a hack put in place
+ * due to the lack of support for joins, it will probably go away
+ * (be deprecated) if/when joins are implemented. 
+ */
+void qof_sql_query_set_kvp (QofSqlQuery *, KvpFrame *);
+
+/** @} */
+#endif /* QOF_SQL_QUERY_H */
+/** @} */



More information about the gnucash-changes mailing list