[Gnucash-changes] QSF backend, support for partial QofBooks, merge fix, references and KVP

Neil Williams codehelp at cvs.gnucash.org
Sun Feb 20 12:04:19 EST 2005


Log Message:
-----------
QSF backend, support for partial QofBooks, merge fix, references and KVP support

Tags:
----
gnucash-gnome2-dev

Modified Files:
--------------
    gnucash:
        ChangeLog
    gnucash/src/app-file:
        gnc-file.c
    gnucash/src/backend/qsf:
        pilot-qsf-GnuCashInvoice.xml
        qsf-backend.c
        qsf-map.xsd.xml
        qsf-object.xsd.xml
        qsf-xml-map.c
        qsf-xml.c
        qsf-xml.h
    gnucash/src/engine:
        gnc-date.c
        kvp_frame.c
        kvp_frame.h
        qof_book_merge.c
        qofbackend-p.h
        qofbackend.h
        qofsession.c
        qofsession.h

Revision Data
-------------
Index: ChangeLog
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/ChangeLog,v
retrieving revision 1.1487.2.175
retrieving revision 1.1487.2.176
diff -LChangeLog -LChangeLog -u -r1.1487.2.175 -r1.1487.2.176
--- ChangeLog
+++ ChangeLog
@@ -1,3 +1,43 @@
+2005-02-20  Neil Williams  <linux at codehelp.co.uk>
+
+	* src/app-file/gnc-file.c: Adding messages to handlers for 
+	new QSF backend errors.
+	* src/backend/qsf/pilot-qsf-GnuCashInvoice.xml: Correcting
+	the schema namespace URL.
+	* src/backend/qsf/qsf-backend.c: 
+	* src/backend/qsf/qsf-xml-map.c: 
+	* src/backend/qsf/qsf-xml.c: Re-organising and adding
+	references and almost complete KVP support to QSF backend.
+	* src/backend/qsf/qsf-map.xsd.xml: 
+	* src/backend/qsf/qsf-object.xsd.xml: Correcting attribute
+	declarations and schema namespace URL. Adding KVP, double
+	and char support to object schema.
+	* src/backend/qsf/qsf-xml.h: Adding references support,
+	documenting use of a partial QofBook. Changing the namespace URL.
+	* src/engine/gnc-date.c: Fix the last-day-of-month computation for
+	leap years.  Need to use modulo, not divide - needed by other parts
+	of this update, already in HEAD.
+        * src/engine/qof_book_merge.c: Fix double free of the targetList
+        entities that cause xaccGroupGetNumSubAccounts to generate a
+        segmentation fault.
+        * src/engine/kvp_frame.c:
+        * src/engine/kvp_frame.h: Add kvp_value_to_bare_string to 
+	generate strings without debug information.
+	* src/engine/qofbackend-p.h: Support for a partial QofBook.
+	The backend needs to handle external references to entities 
+	outside this book and save a QofBook that contains any mix of 
+	QOF objects, whether or not any specific object exists in the book.
+	* src/engine/qofbackend.h: Adding a further QSF error code for
+	when a GUID string has been mangled and fails to convert to a GUID.
+	* src/engine/qofsession.c: Adding qof_entity_copy routines to
+	copy single, lists or collections of entities between session books.
+	Note: Each function creates a partial QofBook! Only certain backends
+	{QSF} can handle partial books! Use for data export.
+	* src/engine/qofsession.h: Documenting copying entities between
+	sessions and using a partial QofBook. Also, allow a session book to 
+	be printed to stdout.
+	
+
 2005-02-17  Derek Atkins  <derek at ihtfp.com>
 
 	* configure.in: properly notice that we've got a gtkhtml version.
Index: qsf-xml-map.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/qsf/Attic/qsf-xml-map.c,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -Lsrc/backend/qsf/qsf-xml-map.c -Lsrc/backend/qsf/qsf-xml-map.c -u -r1.1.2.1 -r1.1.2.2
--- src/backend/qsf/qsf-xml-map.c
+++ src/backend/qsf/qsf-xml-map.c
@@ -21,19 +21,6 @@
  *  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
-
-	The qsf-map.c source file is included in this documentation during development
-	to help explain certain methods. The doxygen tags can be
-	removed / set as ordinary comments once each method is finalised.
-
-    @{ */
-/** @file qsf-xml-map.c
-    @brief  QSF Map validation and processing.
-    @author Copyright (C) 2005 Neil Williams <linux at codehelp.co.uk>
-*/
 
 #define _GNU_SOURCE
 
@@ -45,8 +32,6 @@
 #include "qsf-xml.h"
 #include "qsf-dir.h"
 
-//static short int module = MOD_BACKEND;
-
 static void
 qsf_date_default_handler(const char *default_name, GHashTable *qsf_default_hash,
 	xmlNodePtr parent_tag, xmlNodePtr import_node, xmlNsPtr ns)
@@ -114,24 +99,6 @@
 	}
 }
 
-
-/** \brief Validates a QSF object against a specific QSF map.
-
- at param	path	path to the QSF object file
- at param	map_path	path to the QSF map file.
-
-Need to code for how to find these files.
-
-	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.
-
- at return FALSE (0) or < 0 on error, 1 on success
-
-Check QofBackendError for a description of the error.
-
-*/
 gboolean is_qsf_object_with_map_be(char *map_file, qsf_param *params)
 {
 	xmlDocPtr doc, map_doc;
@@ -193,7 +160,6 @@
 	valid_count += valid.valid_object_count;
 	g_hash_table_destroy(valid.validation_table);
 	if(valid_count == 0) {
-		/* clear any previous error */
 		qof_backend_get_error(params->be);
 		return TRUE;
 	}
@@ -270,24 +236,6 @@
 }
 
 
-/** \brief Handling defaults.
-
-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.
-
-*/
 static void
 qsf_map_default_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params )
 {
@@ -386,8 +334,7 @@
 	return NULL;
 }
 
-/** \brief Handles the set tag in the map.
-
+/* Handles the set tag in the map.
 This function will be overhauled once inside QOF 
 QOF hook required for "Lookup in the receiving application"
 */
@@ -566,6 +513,25 @@
 	}
 }
 
+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, QSF_OBJECT_TAG));
+	xmlNewProp(extra_node, QSF_OBJECT_TYPE,
+		xmlGetProp(params->cur_node, QSF_OBJECT_TYPE));
+	property = xmlCharStrdup(str->str);
+	xmlNewProp(extra_node, QSF_OBJECT_COUNT, property);
+	return extra_node;
+}
+
 void
 qsf_map_object_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params)
 {
Index: qsf-object.xsd.xml
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/qsf/Attic/qsf-object.xsd.xml,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -Lsrc/backend/qsf/qsf-object.xsd.xml -Lsrc/backend/qsf/qsf-object.xsd.xml -u -r1.1.2.1 -r1.1.2.2
--- src/backend/qsf/qsf-object.xsd.xml
+++ src/backend/qsf/qsf-object.xsd.xml
@@ -1,5 +1,6 @@
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-targetNamespace="urn:qof-qsf-container" xmlns="urn:qof-qsf-container">
+targetNamespace="http://qof.sourceforge.net/"
+xmlns:qof-qsf="http://qof.sourceforge.net/">
  <xsd:annotation>
     <xsd:documentation xml:lang="en">
 	Query Object Framework Serialization Format (QSF)
@@ -18,16 +19,16 @@
 	Temple	Place, Suite 330, Boston, MA 02111-1307 USA
     </xsd:documentation>
   </xsd:annotation>
-<xsd:element name="qof-qsf" type="qsf-type"/>
-<xsd:complexType name="qsf-type">
+<xsd:element name="qof-qsf" qof-qsf:type="qsftype"/>
+<xsd:complexType name="qsftype">
  <xsd:sequence>
-   <xsd:element name="book" type="qofbook"/>
+   <xsd:element name="book" qof-qsf:type="qofbook"/>
  </xsd:sequence>
 </xsd:complexType>
 <xsd:complexType name="qofbook">
  <xsd:sequence>
    <xsd:element name="book-guid" type="xsd:string"/>
-   <xsd:element name="object" type="qsfobject" minOccurs="1" maxOccurs="unbounded"/>
+   <xsd:element name="object" qof-qsf:type="qsfobject" minOccurs="1" maxOccurs="unbounded"/>
  </xsd:sequence>
  <xsd:attribute name="count" type="xsd:positiveInteger"/>
 </xsd:complexType>
@@ -96,8 +97,38 @@
      </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"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+    </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="char" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:string">
+       <xsd:attribute name="type" type="xsd:string"/>
+      </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"/>
+       <xsd:attribute name="path" type="xsd:string"/>
+       <xsd:attribute name="value" type="xsd:string"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
 </xsd:sequence>
  <xsd:attribute name="type" type="xsd:string"/>
  <xsd:attribute name="count" type="xsd:positiveInteger"/>
 </xsd:complexType>
 </xsd:schema>
+
Index: qsf-xml.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/qsf/Attic/qsf-xml.h,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -Lsrc/backend/qsf/qsf-xml.h -Lsrc/backend/qsf/qsf-xml.h -u -r1.1.2.1 -r1.1.2.2
--- src/backend/qsf/qsf-xml.h
+++ src/backend/qsf/qsf-xml.h
@@ -70,7 +70,16 @@
 
 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.
+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 
@@ -89,6 +98,10 @@
 		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
@@ -117,8 +130,51 @@
 #include "qofbook.h"
 #include "qofclass.h"
 #include "qofobject.h"
+#include "kvp_frame.h"
 #include "qofbackend-p.h"
-#include "qofsession.h"
+#include "qofsession-p.h"
+#include "qofbook-p.h"
+
+/* KVP XML
+ *
+ * <kvp type="ACCOUNT_KVP", path="/book/accounting-period" value="string">week</kvp>
+ * (ACCOUNT_KVP only for clarity here, actual is "kvp"
+ *
+ * <kvp type="kvp" path="/from-sched-xaction" value="guid">c858b9a3235723b55bc1179f0e8c1322</kvp>
+ *
+ * The relevance of kvp type won't be evident in GnuCash, they all use "kvp".
+ *
+ * need switch statement on kvp_value_get_type(val) val = kvp_value* val,
+ * itself used by g_hash_table_foreach(kvp_frame_get_hash(frame), add_kvp_slot, ret);
+ * xmlNodePtr ret;
+ *
+ * Then retrieve the kvp_frame as the parameter from the entity. Use that frame to
+ * copy the key and value - become slots for this entity.
+ *
+ * kvp:key == path
+ * kvp:value == content converted according to the type of value.
+ * kvp:type == QofParam->name
+ * 
+ * Elsewhere:
+ * string:type == QofParam->name
+ * i.e. <string type="to_do_note"/> == a to_do_note type string
+ * 
+ *
+ * Consider wholesale change from type="" to name="" for object parameters?
+ * (but name has special meaning in the KVP documentation).
+ * 
+ *  
+ * <kvp type="kvp" path="/from-sched-xaction" value="guid">c858b9a3235723b55bc1179f0e8c1322</kvp>
+ * A kvp type KVP parameter located at $path containing a $value.
+ *
+ * A non-GnuCash example helps:
+ * <kvp type="pilot_addr_kvp" path="/user/name" value="guid">c858b9a3235723b55bc1179f0e8c1322</kvp>
+ * A pilot_addr_kvp type KVP parameter located at /user/name containing a guid value.
+ *
+ * 
+ *
+ * */
+
 
 typedef enum  {
 	QSF_UNDEF = 0, /**< Initial undefined value. */
@@ -148,7 +204,7 @@
 Make sure the same version of QOF is in use in both applications.
 */
 #define QSF_ROOT_TAG	"qof-qsf" /**< The top level root tag */
-#define QSF_DEFAULT_NS	"urn:qof-qsf-container" /**< Default namespace for QSF 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.
 */
@@ -158,6 +214,8 @@
 #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_KVP  "path" /**< The path to this KVP value in the entity frame. */
+#define QSF_OBJECT_VALUE "value" /**< The KVP Value. */
 #define QSF_OBJECT_COUNT	"count" /**< Sequential counter for each QSF object in this file */
 #define QSF_XML_VERSION	"1.0"  /**< The current XML version. */
 #define MAP_ROOT_TAG	"qsf-map" /**< Top level root tag for QSF Maps */
@@ -175,7 +233,8 @@
 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.
+#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.
@@ -186,7 +245,10 @@
 
 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 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.
@@ -195,6 +257,7 @@
 [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
@@ -202,13 +265,29 @@
 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.
+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.
@@ -223,7 +302,7 @@
 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.
+#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,
@@ -255,6 +334,8 @@
 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"
 #define QSF_XSD_TIME	"%Y-%m-%dT%H:%M:%SZ" /**< xsd:dateTime format in coordinated universal time, UTC.
 
@@ -356,17 +437,20 @@
 	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 */
+	GHashTable *referenceTable;  /**< 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. */
+	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.
@@ -375,8 +459,12 @@
 	char *filepath; /**< Path to the QSF file. */
 }qsf_param;
 
-void qsf_free_params(qsf_param *params);
+/** \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
 
@@ -438,6 +526,18 @@
 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);
 
@@ -446,9 +546,7 @@
 
 void
 qsf_map_object_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params);
-
-xmlNodePtr
-qsf_add_object_tag(qsf_param *params, int count);
+/** @} */
 
 /** \brief Compares an xmlDoc in memory against the schema file.
 
@@ -523,26 +621,18 @@
 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.
+
+
 @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);
 
-/** \brief Main processing routine
-
-Currently, only a test routine but this will process the QSF map, when required,
-process the QSF object(s) and output a suitable QofBook ready for ::BookMerge
-
- at return TRUE on success, FALSE otherwise.
-
-*/
-gboolean qsf_test_main(void);
-
-
 /**	\brief QOF processing routine.
 
-To replace the qsf_test_main routine with genuine calls to QofSession and 
-qof_book_merge. Accepts QSF_OBJECT.
+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.
 
@@ -552,27 +642,81 @@
 
 /**	\brief QOF processing routine.
 
-To replace the qsf_test_main routine with genuine calls to QofSession and 
-qof_book_merge. Accepts QSF_GNC_OBJECT.
-
+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 Export a QofBook as QSF
+/** \brief Book and book-guid node handler.
 
- at param	book	QofBook*
-
- at return NULL on error, otherwise a pointer to the xml document in memory, xmlDocPtr,
-	use xmlDocDump() to write the XML to a file.
+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.
 */
-xmlDocPtr
-qofbook_to_qsf(QofBook *book);
-
 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.
+
+\todo references
+
+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 (pending.)
+
+*/
 void qsf_param_init(qsf_param *params);
 
 
@@ -652,38 +796,24 @@
 
 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.
+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.
 
 @return TRUE if the map validates, otherwise FALSE.
 */
 gboolean is_qsf_map(const char *path);
 
-/** \brief Write a QofBook to QSF
-
-This function can be used to write any QofBook to QSF. 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).
-
-The file is validated against the QSF object schema before being written.
-Check the QofBackendError - don't assume the file is OK.
-
-*/
-void
-write_qsf_from_book(FILE *out, QofBook *book);
-
 /** \brief Determine the type of QSF and load it into the QofBook
 
- at param	qsf_doc Pointer to the QSF object in memory, xmlDocPtr.
+- 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.
 
 @return NULL on error, otherwise a pointer to the QofBook. Use
 	the qof_book_merge API to merge the new data into the current
@@ -692,6 +822,12 @@
 void
 qsf_file_type (QofBackend *be, QofBook *book);
 
+/** \brief Describe this backend to the application. 
+
+Sets QSF Backend Version 0.1, access method = file:
+
+This is the QOF backend interface, not the GnuCash module.
+*/
 void qsf_provider_init(void);
 
 void
@@ -708,7 +844,8 @@
 into a second QofSession.
 
 @param first_session A QofSession pointer to the original session. This
-	will become the target of the subsequent qof_book_merge.
+will become the target of the subsequent qof_book_merge.
+
 @param path	Absolute or relative path to the file to be loaded
 
 @return ERR_BACKEND_NO_ERR == 0 on success, otherwise the QofBackendError
@@ -717,22 +854,49 @@
 \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);
+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.
+ 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);
+QofBackendError 
+qof_session_load_qsf_object(QofSession *first_session, const char *path);
 	
 void qsf_destroy_backend (QofBackend *be);
 
-/** \brief Backend routine to open a file to write
+/** \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.
 
-Calls write_qsf_from_book to convert the QofBook to QSF XML.
 */
 void qsf_write_file(QofBackend *be, QofBook *book);
 
Index: pilot-qsf-GnuCashInvoice.xml
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/qsf/Attic/pilot-qsf-GnuCashInvoice.xml,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -Lsrc/backend/qsf/pilot-qsf-GnuCashInvoice.xml -Lsrc/backend/qsf/pilot-qsf-GnuCashInvoice.xml -u -r1.1.2.1 -r1.1.2.2
--- src/backend/qsf/pilot-qsf-GnuCashInvoice.xml
+++ src/backend/qsf/pilot-qsf-GnuCashInvoice.xml
@@ -3,7 +3,7 @@
 <!-- maps use the same sequence of parameter types as other QSF -->
 <!-- Trans:desc can be set by expenses or datebook -->
 <qsf-map
-xmlns="urn:qof-qsf-map">
+xmlns="http://qof.sourceforge.net/">
 <definition qof_version="3">
   <define e_type="qof-expenses">Pilot-link QOF expenses</define>
   <define e_type="qof-datebook">Pilot-link QOF datebook</define>
Index: qsf-map.xsd.xml
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/qsf/Attic/qsf-map.xsd.xml,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -Lsrc/backend/qsf/qsf-map.xsd.xml -Lsrc/backend/qsf/qsf-map.xsd.xml -u -r1.1.2.1 -r1.1.2.2
--- src/backend/qsf/qsf-map.xsd.xml
+++ src/backend/qsf/qsf-map.xsd.xml
@@ -1,5 +1,6 @@
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-targetNamespace="urn:qof-qsf-map" xmlns="urn:qof-qsf-map">
+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)
@@ -18,18 +19,18 @@
 	Temple	Place, Suite 330, Boston, MA 02111-1307 USA
     </xsd:documentation>
   </xsd:annotation>
-<xsd:element name="qsf-map" type="map-type"/>
+<xsd:element name="qsf-map" qsf-map:type="map-type"/>
 <xsd:complexType name="map-type">
  <xsd:sequence>
-   <xsd:element name="definition" type="qsfdefinition"/>
-   <xsd:element name="object" type="mapobject" minOccurs="1" maxOccurs="unbounded"/>
+   <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" type="qsfdefine" minOccurs="2" maxOccurs="unbounded"/>
-   <xsd:element name="default" type="qsfdefault" minOccurs="0" maxOccurs="unbounded"/>
-   <xsd:element name="variable" type="qsfvariable" minOccurs="0" maxOccurs="unbounded"/>
+   <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>
@@ -60,17 +61,17 @@
 </xsd:complexType>
 <xsd:complexType name="mapobject">
  <xsd:sequence>
-   <xsd:element name="calculate" type="qsfcalculate" minOccurs="1" maxOccurs="unbounded"/>
+   <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" type="qsf_set" minOccurs="0" maxOccurs="1"/>
-   <xsd:element name="if" type="qsf_if" minOccurs="0" maxOccurs="unbounded"/>
-   <xsd:element name="equals" type="qsf_equal" minOccurs="0" maxOccurs="unbounded"/>
-   <xsd:element name="else" type="qsf_else" minOccurs="0" maxOccurs="unbounded"/>
+   <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"/>
@@ -86,8 +87,8 @@
 </xsd:complexType>
 <xsd:complexType name="qsf_if">
  <xsd:sequence>
-   <xsd:element name="set" type="qsf_set" minOccurs="0" maxOccurs="1"/>
-   <xsd:element name="equals" type="qsf_equal" minOccurs="0" maxOccurs="1"/>
+   <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"/>
@@ -96,19 +97,20 @@
 </xsd:complexType>
 <xsd:complexType name="qsf_else">
  <xsd:sequence>
-   <xsd:element name="set" type="qsf_set" minOccurs="0" maxOccurs="1"/>
-   <xsd:element name="equals" type="qsf_equal" minOccurs="0" maxOccurs="1"/>
+   <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" type="qsf_set" minOccurs="0" maxOccurs="1"/>
-   <xsd:element name="equals" type="qsf_equal" minOccurs="0" maxOccurs="1"/>
+   <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>
+
Index: qsf-xml.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/qsf/Attic/qsf-xml.c,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -Lsrc/backend/qsf/qsf-xml.c -Lsrc/backend/qsf/qsf-xml.c -u -r1.1.2.1 -r1.1.2.2
--- src/backend/qsf/qsf-xml.c
+++ src/backend/qsf/qsf-xml.c
@@ -20,90 +20,27 @@
  *  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
-
-	The qsf-xml.c source file is included in this documentation during development
-	to help explain certain methods. The doxygen tags can be
-	removed / set as ordinary comments once each method is finalised.
-
-    @{ */
-/** @file qsf-xml.c
-    @brief  QSF Object validation, input and export.
-    @author Copyright (C) 2004-2005 Neil Williams <linux at codehelp.co.uk>
-*/
 
 #define _GNU_SOURCE
-#include <errno.h>
 
-#include "libxml/xmlversion.h"
+#include <libxml/xmlversion.h>
 #include "qsf-dir.h"
-/** The libxml pre-processor conditional has been removed
-from the QOF and pilot-link versions of this file. Development
-within GnuCash requires the conditional. 
-uncomment this (and the conditionals that depend on it)
-only for GnuCash.
-To allow doxygen to process the C source
-Change the doxygen config to include statics:
-EXTRACT_STATIC         = YES
-EXTRACT_LOCAL_CLASSES  = YES
-and todo's:
-GENERATE_TODOLIST      = YES
-INLINE_SOURCES         = YES
-
-if defined(LIBXML_VERSION) && LIBXML_VERSION >= 20000
-*/
 #include "qsf-xml.h"
 #include "qofbook-p.h"
 
-static short int module = MOD_BACKEND;
-
 void qsf_free_params(qsf_param *params)
 {
-	while(params->qsf_object_list != NULL)
-	{
-		/** \todo Change to free the struct instead. */
-/*		g_hash_table_destroy(params->qsf_parameter_hash);
-		params->qsf_parameter_hash = params->qsf_object_list->data;
-		params->qsf_object_list = g_list_next(params->qsf_object_list);
-*/	}
 	g_hash_table_destroy(params->qsf_calculate_hash);
 	g_hash_table_destroy(params->qsf_default_hash);
+	if(params->referenceTable) {
+		g_hash_table_destroy(params->referenceTable);
+	}
 	g_slist_free(params->supported_types);
 	xmlFreeDoc(params->output_doc);
 	xmlFreeNs(params->qsf_ns);
 	xmlFreeNs(params->map_ns);
 }
 
-/** \brief Output
-
-Adds the object tag and object type to the output node.
-
-\todo Rationalise the node variable names in ::qsf_add_object_tag
-*/
-xmlNodePtr
-qsf_add_object_tag(qsf_param *params, int count)
-{
-	xmlNodePtr lister;
-	GString *str;
-	xmlChar *property;
-
-	str = g_string_new (" ");
-	/** \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.*/
-	g_string_printf(str, "%i", count);
-	lister = NULL;
-	lister = xmlAddChild(params->output_node,
-		xmlNewNode(params->qsf_ns, QSF_OBJECT_TAG));
-	xmlNewProp(lister, QSF_OBJECT_TYPE,
-		xmlGetProp(params->cur_node, QSF_OBJECT_TYPE));
-	property = xmlCharStrdup(str->str);
-	xmlNewProp(lister, QSF_OBJECT_COUNT, property);
-	return lister;
-}
-
 int
 qsf_compare_tag_strings(const xmlChar *node_name, char *tag_name)
 {
@@ -202,174 +139,6 @@
 	}
 }
 
-/** \brief Set the QSF-specific parameter order
-
-::qof_class_param_foreach callback.
-
-- All string parameters, any order
-- All GUID parameters, including references to parent or child objects, in any order.
-- all boolean parameters, any order
-- all numeric parameters, any order
-- all date parameters, any order
-- all int32 parameters, any order
-- all int64 parameters, any order
-
-Receive one QOF_TYPE and iterate over the current object in params
-	to build a GSList of parameters in the correct sequence
-	for this object and put into another GHashTable.
-	One hash table containing all defined objects.
-	Each value is a GSList of the parameters in the QSF sequence.
-	Exclude using the qof_book_merge criterion: QofAccessFunc && QofSetterFunc
-	not NULL.
-
-	GSList *supported_types;
-
-*/
-static void
-qsf_object_sequence(QofParam *qof_param, gpointer data)
-{
-	qsf_param *params;
-
-	g_return_if_fail(data != NULL);
-	params = (qsf_param*) data;
-	if(0 == safe_strcmp(qof_param->param_type, params->qof_type))
-	{
-		params->qsf_sequence = g_slist_append(params->qsf_sequence, qof_param);
-	}
-}	
-
-/** \brief Dictates which QOF types are supported and in which \b order
-
-Called once for each supported type, for each object.
-Filters the parameter list to set each type in order:
-- QOF_TYPE_STRING
-- QOF_TYPE_GUID
-- QOF_TYPE_BOOLEAN
-- QOF_TYPE_NUMERIC
-- QOF_TYPE_DATE
-- QOF_TYPE_INT32
-- QOF_TYPE_INT64
-
-*/
-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;
-	qof_class_param_foreach(params->qof_obj_type, qsf_object_sequence, params);
-}
-
-/** \brief Export routine.
-
-For each entity, builds the output tags for the parameters, in QSF order.
-*/
-static void
-qsf_entity_foreach(QofEntity *ent, gpointer data)
-{
-	qsf_param *params;
-	GSList *param_list;
-	xmlNodePtr node, object_node;
-	xmlNsPtr ns;
-	gchar *string_buffer;
-	GString *buffer;
-	QofParam *qof_param;
-	int param_count;
-	
-	g_return_if_fail(data != NULL);
-	params = (qsf_param*)data;
-	param_count = params->count;
-	ns = params->qsf_ns;
-	object_node = xmlNewChild(params->book_node, params->qsf_ns, QSF_OBJECT_TAG, NULL);
-	xmlNewProp(object_node, QSF_OBJECT_TYPE, ent->e_type); 
-	buffer = g_string_new(" ");
-	g_string_printf(buffer, "%i", param_count);
-	xmlNewProp(object_node, QSF_OBJECT_COUNT, buffer->str);
-	param_list = g_slist_copy(params->qsf_sequence);
-	while(param_list != NULL) {
-		qof_param = param_list->data;
-		g_return_if_fail(qof_param != NULL);
-		if(((qof_param->param_setfcn != NULL) && (qof_param->param_getfcn != NULL)) ||
-			(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_GUID)))
-		{
-			node = xmlAddChild(object_node, xmlNewNode(ns, qof_param->param_type));
-			string_buffer = g_strdup(qof_book_merge_param_as_string(qof_param, ent));
-			xmlNodeAddContent(node, string_buffer);
-			xmlNewProp(node, QSF_OBJECT_TYPE ,qof_param->param_name);
-		}
-		param_list = g_slist_next(param_list);
-	}
-}
-
-/** \brief Export routine. For each registered object type, get parameters. 
-
-Runs for each object type.
-Gets the parameter list for this object type,
-sorts into the QSF order and
-moves on to the
-next object type.
-*/
-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)){
-		DEBUG (" 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);
-}
-
-/** \brief Export routine
-
-	QSF only uses one QofBook per file, so far.
-*/
-xmlDocPtr
-qofbook_to_qsf(QofBook *book)
-{
-	xmlNodePtr top_node, node;//, book_node;
-	xmlDocPtr doc;
-//	xmlChar *output;
-	gchar buffer[GUID_ENCODING_LENGTH + 1];
-//	gint counter;
-	qsf_param *params;
-	const GUID *book_guid;
-	
-	g_return_val_if_fail(book != NULL, NULL);
-	params = g_new(qsf_param, 1);
-	qsf_param_init(params);
-	params->book = book;
-	doc = xmlNewDoc(QSF_XML_VERSION);
-	top_node = xmlNewNode(NULL, QSF_ROOT_TAG);
-	xmlDocSetRootElement(doc, top_node);
-	xmlSetNs(top_node, xmlNewNs(top_node, QSF_DEFAULT_NS, NULL));
-	params->qsf_ns = top_node->ns;
-	node = xmlNewChild(top_node, params->qsf_ns, QSF_BOOK_TAG, NULL);
-	params->book_node = node;
-	xmlNewProp(node, QSF_BOOK_COUNT, "1");
-	book_guid = qof_book_get_guid(book);
-	guid_to_string_buff(book_guid, buffer);
-	xmlNewChild(params->book_node, params->qsf_ns, QSF_BOOK_GUID, buffer);
-	params->output_doc = doc;
-	params->book_node = node;
-	params->output_node = node;
-	qof_object_foreach_type(qsf_foreach_obj_type, params);
-	return params->output_doc;
-}
-
 gboolean is_our_qsf_object(const char *path)
 {
 	xmlDocPtr doc;
@@ -516,20 +285,8 @@
 }
 
 
-/** \brief Handles the QSF object file
-
-Despite the name, this function handles the QSF object book tag
-\b AND the object tags.
-
-\todo FIXME: rationalise?
-
-Stores details of the object data for map processing later.
-
-Need to work on only one object at a time
-	Each new object, create a new GHashTable in the GList.
-	The GHashTable contains all the object data, 
-	indexed by the type attribute.
-*/
+/* Despite the name, this function handles the QSF object book tag
+AND the object tags. */
 static void
 qsf_object_node_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params)
 {
@@ -557,12 +314,6 @@
 	}
 }
 
-/** \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 ns, qsf_param *params)
 {
@@ -598,167 +349,3 @@
 		}
 	}
 }
-
-/** \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)
-{
-	qsf_param 		*params;
-	qsf_objects		*object_set;
-	xmlNodePtr		node;
-//	QofEntity 		*referenceEnt;
-//	GSList 			*linkage;
-	const char		*qof_type, *parameter_name;
-	QofIdType		obj_type;
-	QofEntity		*qsf_ent;
-	struct tm		qsf_time;
-	time_t			qsf_time_t;
-	char			*tail;
-	/* function pointers and variables for parameter getters that don't use pointers normally */
-	gnc_numeric 	cm_numeric;
-	double 			cm_double;
-	gboolean 		cm_boolean;
-	gint32 			cm_i32;
-	gint64 			cm_i64;
-	Timespec 		cm_date;
-	/* cm_ prefix used for variables that hold the data to commit */
-	gchar 			/**cm_string,*/ *cm_char;
-	GUID 			*cm_guid;
-//	KvpFrame 		*cm_kvp;
-	QofSetterFunc 	cm_setter;
-	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*);
-	
-	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 = node->name;
-	qsf_ent = params->qsf_ent;
-	obj_type = xmlGetProp(node->parent, QSF_OBJECT_TYPE);
-	if(0 == safe_strcasecmp(obj_type, parameter_name)) { return; }
-	cm_setter = qof_class_get_parameter_setter(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, xmlNodeGetContent(node)); }
-	}
-	if(safe_strcmp(qof_type, QOF_TYPE_DATE) == 0) { 
-		date_setter = (void(*)(QofEntity*, Timespec))cm_setter;
-		strptime(xmlNodeGetContent(node), QSF_XSD_TIME, &qsf_time);
-		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(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);
-		g_return_if_fail(TRUE == string_to_guid(xmlNodeGetContent(node), cm_guid));
-		qof_entity_set_guid(qsf_ent, cm_guid);
-		}
-	if(safe_strcmp(qof_type, QOF_TYPE_INT32) == 0) { 
-		errno = 0;
-		cm_i32 = (gint32)strtol (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(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(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(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_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(qof_type, QOF_TYPE_CHAR) == 0) { 
-		cm_char = xmlNodeGetContent(node);
-		char_setter = (void(*)(QofEntity*, char*))cm_setter;
-		if(char_setter != NULL) { char_setter(qsf_ent, cm_char); }
-	}
-/*		if(registered_type == FALSE) {
-			linkage = g_slist_copy(rule->linkedEntList);
-			referenceEnt = NULL;
-			reference_setter = (void(*)(QofEntity*, QofEntity*))cm_param->param_setfcn;
-			if((linkage == NULL)&&(rule->mergeResult == MERGE_NEW)) {
-				referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param);
-				reference_setter(rule->targetEnt, qof_book_mergeLocateReference(referenceEnt, mergeData));
-			}
-			while(linkage != NULL) {
-				referenceEnt = linkage->data;
-				if((referenceEnt)
-					&&(referenceEnt->e_type)
-					&&(safe_strcmp(referenceEnt->e_type, qof_type) == 0)) {
-					// The function behind reference_setter must create objects for any non-QOF references
-					reference_setter(rule->targetEnt, qof_book_mergeLocateReference(referenceEnt, mergeData));
-				}
-				linkage = g_slist_next(linkage);
-			}
-		}
-*/
-}
Index: qsf-backend.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/qsf/Attic/qsf-backend.c,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -Lsrc/backend/qsf/qsf-backend.c -Lsrc/backend/qsf/qsf-backend.c -u -r1.1.2.1 -r1.1.2.2
--- src/backend/qsf/qsf-backend.c
+++ src/backend/qsf/qsf-backend.c
@@ -21,31 +21,15 @@
  *  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
-
-	The qsf-backend.c source file is included in this documentation during development
-	to help explain certain methods. The doxygen tags can be
-	removed / set as ordinary comments once each method is finalised.
-
-    @{ */
-/** @file qsf-backend.c
-    @brief  QSF Backend definition, creation and handling.
-    @author Copyright (C) 2004-2005 Neil Williams <linux at codehelp.co.uk>
-*/
 
 #define _GNU_SOURCE
 
 #include "qsf-xml.h"
 #include "qsf-dir.h"
+#include <errno.h>
 
-//static short module = MOD_BACKEND;
-
-/** \brief QSF wrapper for QofBackend
+static short int module = MOD_BACKEND;
 
-This makes a qsf_param struct available to the backend.
-*/
 struct QSFBackend_s 
 {
 	QofBackend be;
@@ -55,9 +39,6 @@
 
 typedef struct QSFBackend_s QSFBackend;
 
-/** \brief Backend init routine.
-
-*/
 void
 qsf_param_init(qsf_param *params)
 {
@@ -83,6 +64,7 @@
 	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->referenceTable = 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);
@@ -92,7 +74,7 @@
 	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_KVP);
 	qsf_time_precision = "%j";
 	qsf_time_now_t = time(NULL);
 	qsf_ts = g_new(Timespec, 1);
@@ -106,8 +88,7 @@
 	g_hash_table_insert(params->qsf_default_hash, "qsf_time_string", qsf_time_string);
 }
 
-/** \brief GnuCash does LOTS of filesystem work, QSF is going to leave most of it to libxml2. :-)
-
+/* 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.
 */
@@ -121,8 +102,12 @@
 	g_return_if_fail(be != NULL);
 	qsf_be = (QSFBackend*)be;
 	g_return_if_fail(qsf_be->params != NULL);
-	g_return_if_fail(book_path != 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);
@@ -139,7 +124,6 @@
 	qof_backend_set_error(be, ERR_BACKEND_NO_ERR);
 }
 
-/** \brief Clean up the backend and the libxml2 parser. */
 static void
 qsf_session_end( QofBackend *be)
 {
@@ -159,21 +143,8 @@
 	g_free(be);
 }
 
-void
-qsf_write_file(QofBackend *be, QofBook *book)
-{
-	QSFBackend *qsf_be;
-	FILE *out;
-	char *path;
-	
-	qsf_be = (QSFBackend*)be;
-	path = strdup(qsf_be->fullpath);
-	out = fopen(path, "w");
-	write_qsf_from_book(out, book);
-	fclose(out);
-}
-
-QofBackendError qof_session_load_our_qsf_object(QofSession *first_session, const char *path)
+QofBackendError 
+qof_session_load_our_qsf_object(QofSession *first_session, const char *path)
 {
 	QofSession *qsf_session;
 	
@@ -184,23 +155,12 @@
 	return qof_session_get_error(qsf_session);
 }
 
-QofBackendError qof_session_load_qsf_object(QofSession *first_session, const char *path)
+QofBackendError 
+qof_session_load_qsf_object(QofSession *first_session, const char *path)
 {
 	return ERR_QSF_NO_MAP;
 }
 
-/** \brief Types of QSF file:
-
-- 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.
-*/
 void
 qsf_file_type(QofBackend *be, QofBook *book)
 {
@@ -219,7 +179,6 @@
 	params->book = book;
 	path = g_strdup(qsf_be->fullpath);
 	params->filepath = g_strdup(path);
-	/* remove any previous error from the backend stack.*/
 	qof_backend_get_error(be);
 	result = is_our_qsf_object_be(params);
 	if(result == TRUE) {
@@ -242,63 +201,263 @@
 	}
 }
 
-/** \todo Create a QofBook from \b our QSF document.
-
-Method:
-
-input_doc must be of file_type OUR_QSF_OBJ.
-
-Iterate over the existing params->qsf_parameter_hash hashtable
-qsf_node_foreach(qsf_root, qsf_book_node_handler, &iter, params);
+/*================================================
+	Load QofEntity into QofBook from XML in memory
+==================================================*/
 
-1. object handler -> create new QofEntity 
-2. parameter handler -> param_setfcn	
-*/
-static gboolean qsfdoc_to_qofbook(xmlDocPtr doc, qsf_param *params)
+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(doc != NULL, FALSE);
-	/* qsf_param_init must have been run and the doc correctly validated. */
 	g_return_val_if_fail(params->book != NULL, FALSE);
 	g_return_val_if_fail(params->file_type == OUR_QSF_OBJ, FALSE);
 
 	qsf_root = xmlDocGetRootElement(doc);
 	qsf_ns = qsf_root->ns;
 	iter.ns = qsf_ns;
-	/* qsf_book_node_handler calls the object and parameter handlers recursively */
+	book = params->book;
+	params->referenceTable = (GHashTable*)qof_book_get_data(book, ENTITYREFERENCE);
+	if(params->referenceTable == NULL) {
+		params->referenceTable = g_hash_table_new(NULL, NULL);
+	}
 	qsf_node_foreach(qsf_root, qsf_book_node_handler, &iter, params);
-	/* All the QSF data (one object at a time) is in qsf_parameter_hash */
 	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, params->book);
+		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;
 		g_hash_table_foreach(params->qsf_parameter_hash, qsf_object_commitCB, params);
 		object_list = g_list_next(object_list);
 	}
+	qof_book_set_data(book, ENTITYREFERENCE, params->referenceTable);
 	return TRUE;
 }
 
-/** \brief Writes a QofBook to an open filehandle.
+static void
+qsf_object_sequence(QofParam *qof_param, gpointer data)
+{
+	qsf_param *params;
+
+	g_return_if_fail(data != NULL);
+	params = (qsf_param*) data;
+	if(0 == safe_strcmp(qof_param->param_type, params->qof_type))
+	{
+		params->qsf_sequence = g_slist_append(params->qsf_sequence, qof_param);
+	}
+}	
+
+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 void
+qsf_from_kvp_helper(gpointer key, gpointer value, gpointer data)
+{
+	qsf_param *params;
+	QofParam *qof_param;
+	xmlNodePtr node;
+	KvpValue *content;
+	gchar *path;
+
+	params = (qsf_param*)data;
+	qof_param = (QofParam*)params->qof_param;
+	node = params->output_node;
+	path = (gchar*)key;
+	content = (KvpValue*)value;
+	xmlNodeAddContent(node, kvp_value_to_bare_string(content));
+	xmlNewProp(node, QSF_OBJECT_TYPE ,qof_param->param_name);
+	xmlNewProp(node, QSF_OBJECT_KVP, path);
+	switch(kvp_value_get_type(content))
+	{
+		case KVP_TYPE_STRING:
+			xmlNewProp(node, QSF_OBJECT_VALUE, QOF_TYPE_STRING);
+			break;
+		case KVP_TYPE_GUID:
+			xmlNewProp(node, QSF_OBJECT_VALUE, QOF_TYPE_GUID);
+			break;
+		case KVP_TYPE_BINARY:
+//			xmlNewProp(node, QSF_OBJECT_VALUE, QOF_TYPE_STRING);
+			break;
+		case KVP_TYPE_GLIST:
+//			xmlNewProp(node, QSF_OBJECT_VALUE, QOF_TYPE_STRING);
+			break;
+		case KVP_TYPE_FRAME:
+//			xmlNewProp(node, QSF_OBJECT_VALUE, QOF_TYPE_STRING);
+			break;
+		case KVP_TYPE_GINT64:
+			xmlNewProp(node, QSF_OBJECT_VALUE, QOF_TYPE_INT64);
+			break;
+		case KVP_TYPE_DOUBLE:
+			xmlNewProp(node, QSF_OBJECT_VALUE, QOF_TYPE_DOUBLE);
+			break;
+		case KVP_TYPE_NUMERIC:
+			xmlNewProp(node, QSF_OBJECT_VALUE, QOF_TYPE_NUMERIC);
+			break;
+		default:
+		break;
+	}
+}
 
- at param	out Pointer to a FILE filehandle.
- at param	book Pointer to a QofBook to write as QSF XML.
+/*=====================================
+	Convert QofEntity to QSF XML node
+=======================================*/
+static void
+qsf_entity_foreach(QofEntity *ent, gpointer data)
+{
+	qsf_param *params;
+	GSList *param_list;
+	xmlNodePtr node, object_node;
+	xmlNsPtr ns;
+	gchar *string_buffer, qsf_guid[GUID_ENCODING_LENGTH + 1];
+	GString *buffer;
+	QofParam *qof_param;
+	QofEntityReference *reference;
+	KvpFrame 	*qsf_kvp;
+	GHashTable *kvp_hash;
+	int param_count;
+	gboolean own_guid;
+	
+	g_return_if_fail(data != NULL);
+	params = (qsf_param*)data;
+	param_count = params->count;
+	ns = params->qsf_ns;
+	own_guid = FALSE;
+	object_node = xmlNewChild(params->book_node, params->qsf_ns, QSF_OBJECT_TAG, NULL);
+	xmlNewProp(object_node, QSF_OBJECT_TYPE, ent->e_type); 
+	buffer = g_string_new(" ");
+	g_string_printf(buffer, "%i", param_count);
+	xmlNewProp(object_node, QSF_OBJECT_COUNT, buffer->str);
+	param_list = g_slist_copy(params->qsf_sequence);
+	while(param_list != NULL) {
+		qof_param = param_list->data;
+		g_return_if_fail(qof_param != NULL);
+		if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_GUID))
+		{
+			if(!own_guid) {
+				node = xmlAddChild(object_node, xmlNewNode(ns, qof_param->param_type));
+				string_buffer = g_strdup(qof_book_merge_param_as_string(qof_param, ent));
+				xmlNodeAddContent(node, string_buffer);
+				xmlNewProp(node, QSF_OBJECT_TYPE ,qof_param->param_name);
+				own_guid = TRUE;
+			}
+			reference = (QofEntityReference*)g_hash_table_lookup(params->referenceTable, 
+				qof_entity_get_guid(ent));
+			if(reference != NULL) {
+				if(0 == safe_strcmp(reference->type, qof_param->param_name))
+				{
+					node = xmlAddChild(object_node, xmlNewNode(ns, qof_param->param_type));
+					guid_to_string_buff(reference->guid, qsf_guid);
+					xmlNodeAddContent(node, qsf_guid);
+					xmlNewProp(node, QSF_OBJECT_TYPE ,qof_param->param_name);
+				}
+			}
+		}
+		if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_KVP))
+		{
+			/** Special KVP handling - the book_merge function doesn't render KVP */
+			qsf_kvp = kvp_frame_copy(qof_param->param_getfcn(ent,qof_param));
+			kvp_hash = kvp_frame_get_hash(qsf_kvp);
+			params->qof_param = qof_param;
+			if(kvp_hash)
+			{
+				node = xmlAddChild(object_node, xmlNewNode(ns, qof_param->param_type));
+				params->output_node = node;
+				g_hash_table_foreach(kvp_hash, qsf_from_kvp_helper, params);
+			}
+		}
+		if((qof_param->param_setfcn != NULL) && (qof_param->param_getfcn != NULL))
+		{
+			node = xmlAddChild(object_node, xmlNewNode(ns, qof_param->param_type));
+			string_buffer = g_strdup(qof_book_merge_param_as_string(qof_param, ent));
+			xmlNodeAddContent(node, string_buffer);
+			xmlNewProp(node, QSF_OBJECT_TYPE ,qof_param->param_name);
+		}
+		
+		param_list = g_slist_next(param_list);
+	}
+}
 
-Any QofBook can be written to QSF XML, it does not need an
-AccountGroup or any specific QOF object. Any process that can
-create a new QofSession and populate the QofBook with QOF objects
-can write the data as QSF XML.
-*/
-void
+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)){
+		DEBUG (" 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)
+{
+	xmlNodePtr top_node, node;;
+	xmlDocPtr doc;
+	gchar buffer[GUID_ENCODING_LENGTH + 1];
+	qsf_param *params;
+	const GUID *book_guid;
+	
+	g_return_val_if_fail(book != NULL, NULL);
+	params = g_new(qsf_param, 1);
+	qsf_param_init(params);
+	params->book = book;
+	params->referenceTable = (GHashTable*)qof_book_get_data(book, ENTITYREFERENCE);
+	if(params->referenceTable == NULL) {
+		params->referenceTable = g_hash_table_new(NULL, NULL);
+	}
+	doc = xmlNewDoc(QSF_XML_VERSION);
+	top_node = xmlNewNode(NULL, QSF_ROOT_TAG);
+	xmlDocSetRootElement(doc, top_node);
+	xmlSetNs(top_node, xmlNewNs(top_node, QSF_DEFAULT_NS, NULL));
+	params->qsf_ns = top_node->ns;
+	node = xmlNewChild(top_node, params->qsf_ns, QSF_BOOK_TAG, NULL);
+	params->book_node = node;
+	xmlNewProp(node, QSF_BOOK_COUNT, "1");
+	book_guid = qof_book_get_guid(book);
+	guid_to_string_buff(book_guid, buffer);
+	xmlNewChild(params->book_node, params->qsf_ns, QSF_BOOK_GUID, buffer);
+	params->output_doc = doc;
+	params->book_node = node;
+//	params->output_node = node;
+	qof_object_foreach_type(qsf_foreach_obj_type, params);
+	return params->output_doc;
+}
+
+static void
 write_qsf_from_book(FILE *out, QofBook *book)
 {
 	xmlDocPtr qsf_doc;
@@ -310,6 +469,38 @@
 	xmlFreeDoc(qsf_doc);
 }
 
+static void
+write_qsf_to_stdout(QofBook *book)
+{
+	xmlDocPtr qsf_doc;
+	
+	qsf_doc = qofbook_to_qsf(book);
+	g_return_if_fail(qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, qsf_doc) == TRUE);
+	xmlDocFormatDump(stdout, qsf_doc, 1);
+	fprintf(stdout, "\n");
+	xmlFreeDoc(qsf_doc);
+}
+
+void
+qsf_write_file(QofBackend *be, QofBook *book)
+{
+	QSFBackend *qsf_be;
+	FILE *out;
+	char *path;
+	
+	qsf_be = (QSFBackend*)be;
+	/* if fullpath is blank, book_id was set to QOF_STDOUT */
+	if(0 == safe_strcmp(qsf_be->fullpath, "")) {
+		write_qsf_to_stdout(book);
+		return;
+	}
+	path = strdup(qsf_be->fullpath);
+	out = fopen(path, "w");
+	write_qsf_from_book(out, book);
+	g_free(path);
+	fclose(out);
+}
+
 /** \brief QofBackend routine to load from file - needs a map.
 */
 gboolean
@@ -317,7 +508,6 @@
 {
 	xmlNodePtr qsf_root;
 
-	/* is_qsf_object has already been run, doc should not fail to parse. */
 	params->input_doc = xmlParseFile(fullpath);
 	if (params->input_doc == NULL) {
 		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
@@ -336,11 +526,6 @@
 	return FALSE;
 }
 
-/** \brief Load a QSF XML file without a map.
-
-The QSF XML file must ONLY contain QOF objects already known to the host
-application. i.e. which return TRUE in qof_class_is_registered();
-*/
 gboolean
 load_our_qsf_object(QofBook *book, const char *fullpath, qsf_param *params)
 {
@@ -357,6 +542,217 @@
 	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;
+	GSList			*linkedEntList;
+	QofBook			*targetBook;
+	const char		*qof_type, *parameter_name;
+	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 */
+	char 		    cm_sa[GUID_ENCODING_LENGTH + 1];
+	gchar 			*cm_string;
+	gnc_numeric 	cm_numeric;
+	double 			cm_double;
+	gboolean 		cm_boolean;
+	gint32 			cm_i32;
+	gint64 			cm_i64;
+	Timespec 		cm_date;
+	gchar 			*cm_char;
+	GUID 			*cm_guid;
+	const GUID      *cm_const_guid;
+//	KvpFrame 		*cm_kvp;
+//	KvpValue        *cm_value;
+	QofSetterFunc 	cm_setter;
+	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 = node->name;
+	qsf_ent = params->qsf_ent;
+	targetBook = params->book;
+	linkedEntList = NULL;
+	obj_type = xmlGetProp(node->parent, QSF_OBJECT_TYPE);
+	if(0 == safe_strcasecmp(obj_type, parameter_name)) { return; }
+	cm_setter = qof_class_get_parameter_setter(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, xmlNodeGetContent(node)); }
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_DATE) == 0) { 
+		date_setter = (void(*)(QofEntity*, Timespec))cm_setter;
+		strptime(xmlNodeGetContent(node), QSF_XSD_TIME, &qsf_time);
+		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(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(xmlNodeGetContent(node), cm_guid))
+		{
+			qof_backend_set_error(params->be, ERR_QSF_BAD_OBJ_GUID);
+			return;
+		}
+		reference_type = xmlGetProp(node, QSF_OBJECT_TYPE);
+		if(0 == safe_strcmp(qsf_ent->e_type, reference_type)) 
+		{
+			qof_entity_set_guid(qsf_ent, cm_guid);
+		}
+		else {
+			reference = g_new(QofEntityReference, 1);
+			reference->type = g_strdup(qsf_ent->e_type);
+			reference->guid = g_new(GUID, 1);
+			cm_const_guid = qof_entity_get_guid(qsf_ent);
+			guid_to_string_buff(cm_const_guid, cm_sa);
+			cm_string = g_strdup(cm_sa);
+			if(TRUE == string_to_guid(cm_string, reference->guid)) {
+				cm_guid = &qsf_ent->guid;
+				g_hash_table_insert(params->referenceTable, cm_guid, reference);
+			}
+		}
+	}
+	if(safe_strcmp(qof_type, QOF_TYPE_INT32) == 0) { 
+		errno = 0;
+		cm_i32 = (gint32)strtol (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(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(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(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) { 
+			// build the KVP frame from xml <kvp type="" path="">values</kvp>
+			// conditional on "value" and use kvp_value_new.
+			//if(0 == safe_strcmp(
+	
+/*			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(qof_type, QOF_TYPE_CHAR) == 0) { 
+		cm_char = xmlNodeGetContent(node);
+		char_setter = (void(*)(QofEntity*, char*))cm_setter;
+		if(char_setter != NULL) { char_setter(qsf_ent, cm_char); }
+	}
+}
+
 QofBackend*
 qsf_backend_new(void)
 {
@@ -371,29 +767,25 @@
 	qsf_param_init(qsf_be->params);
 	qsf_be->be.session_begin = qsf_session_begin;
 
-	/** \todo Add correctly typed functions for each remaining component 
-	of the QofBackend for QSF */
 	be->session_end = qsf_session_end;
 	be->destroy_backend = qsf_destroy_backend;
 	be->load = qsf_file_type;
-	be->save_may_clobber_data = NULL; /* gboolean qof_session_save_may_clobber_data (QofSession * session)*/
-	be->begin = NULL; /*   void (*begin) (QofBackend *, QofInstance *); */
-	be->commit = NULL; /*   void (*commit) (QofBackend *, QofInstance *); */
-	be->rollback = NULL; /* void (*rollback) (QofBackend *, QofInstance *); */
-	/* The QSF backend doesn't always load all data ... change these! */
+	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;
-	
-	/* Is the QSF backend to be multi-user?? */
+	/* The QSF backend is not multi-user. */
 	be->events_pending = NULL;
 	be->process_events = NULL;
 	
 	be->sync = qsf_write_file;
-	
 	qsf_be->fullpath = NULL;
-	
 	return be;
 }
 
@@ -410,7 +802,6 @@
 	g_free (prov);
 }
 
-/** \brief Describe this backend to the application. */
 void
 qsf_provider_init(void)
 {
@@ -418,11 +809,8 @@
 	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->provider_free = qsf_provider_free;
 	qof_backend_register_provider (prov);
 }
-
-
-/** @} */
-/** @} */
Index: gnc-file.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/app-file/gnc-file.c,v
retrieving revision 1.25.4.8
retrieving revision 1.25.4.9
diff -Lsrc/app-file/gnc-file.c -Lsrc/app-file/gnc-file.c -u -r1.25.4.8 -r1.25.4.9
--- src/app-file/gnc-file.c
+++ src/app-file/gnc-file.c
@@ -100,6 +100,11 @@
       uh_oh = FALSE;
       break;
 
+	case ERR_BACKEND_NO_HANDLER: {
+		fmt = _("No suitable backend was found for\n%s.");
+		gnc_error_dialog(parent, fmt, newfile);
+		break;
+	}
     case ERR_BACKEND_NO_BACKEND:
       fmt = _("The URL \n    %s\n"
               "is not supported by this version of GnuCash.");
@@ -206,6 +211,13 @@
 		gnc_error_dialog(parent, fmt, newfile);
 		break; 
 	}
+	case ERR_QSF_BAD_OBJ_GUID: {
+		fmt = _("The selected QSF object file\n%s\n contains one or more invalid GUIDs."
+				"The file cannot be processed - please check the source of the file"
+				" and try again.");
+		gnc_error_dialog(parent, fmt, newfile);
+		break;
+	}
 	case ERR_QSF_NO_MAP: {
 		fmt = _("The selected QSF Object file\n%s\nrequires a map but it was not provided.");
 		gnc_error_dialog(parent, fmt, newfile);
@@ -226,6 +238,12 @@
 	  gnc_error_dialog(parent, fmt, newfile);
 	  break; 
 	}
+	case ERR_QSF_OVERFLOW : {
+		fmt = _("When converting XML strings into numbers, an overflow "
+			"has been detected. The QSF object file\n%s\n contains invalid "
+			"data in a field that is meant to hold a number.");
+		gnc_error_dialog(parent, fmt, newfile);
+	}
     case ERR_FILEIO_FILE_BAD_READ:
       fmt = _("There was an error reading the file.\n"
               "Do you want to continue?");
Index: qof_book_merge.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/qof_book_merge.c,v
retrieving revision 1.2.2.2
retrieving revision 1.2.2.3
diff -Lsrc/engine/qof_book_merge.c -Lsrc/engine/qof_book_merge.c -u -r1.2.2.2 -r1.2.2.3
--- src/engine/qof_book_merge.c
+++ src/engine/qof_book_merge.c
@@ -104,10 +104,6 @@
 	}
 		mergeData->mergeList = g_list_next(mergeData->mergeList);
 	}
-	while(mergeData->targetList != NULL) {
-		g_free(mergeData->targetList->data);
-		mergeData->targetList = g_slist_next(mergeData->targetList);
-	}
 	g_list_free(mergeData->mergeList);
 	g_slist_free(mergeData->mergeObjectParams);
 	g_slist_free(mergeData->targetList);
@@ -286,10 +282,6 @@
 		g_slist_free(currentRule->linkedEntList);
 		mergeData->mergeList = g_list_next(mergeData->mergeList);
 	}
-	while(mergeData->targetList != NULL) {
-		g_free(mergeData->targetList->data);
-		mergeData->targetList = g_slist_next(mergeData->targetList);
-	}
 	g_list_free(mergeData->mergeList);
 	g_slist_free(mergeData->mergeObjectParams);
 	g_slist_free(mergeData->targetList);
Index: kvp_frame.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/kvp_frame.c,v
retrieving revision 1.30.4.6
retrieving revision 1.30.4.7
diff -Lsrc/engine/kvp_frame.c -Lsrc/engine/kvp_frame.c -u -r1.30.4.6 -r1.30.4.7
--- src/engine/kvp_frame.c
+++ src/engine/kvp_frame.c
@@ -1636,6 +1636,81 @@
 }
 
 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("%lld",(long long int) 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;
Index: gnc-date.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/gnc-date.c,v
retrieving revision 1.6.2.3
retrieving revision 1.6.2.4
diff -Lsrc/engine/gnc-date.c -Lsrc/engine/gnc-date.c -u -r1.6.2.3 -r1.6.2.4
--- src/engine/gnc-date.c
+++ src/engine/gnc-date.c
@@ -260,12 +260,12 @@
      /*   leap   */ {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
 
   /* Is this a leap year? */
-  if (year / 2000 == 0)
+  if (year % 2000 == 0)
     is_leap = TRUE;
-  else if (year / 400 == 0)
+  else if (year % 400 == 0)
       is_leap = FALSE;
   else
-    is_leap = (year / 4 == 0);
+    is_leap = (year % 4 == 0);
 
   return days_in_month[is_leap][month-1];
 }
Index: qofsession.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/qofsession.c,v
retrieving revision 1.2.4.8
retrieving revision 1.2.4.9
diff -Lsrc/engine/qofsession.c -Lsrc/engine/qofsession.c -u -r1.2.4.8 -r1.2.4.9
--- src/engine/qofsession.c
+++ src/engine/qofsession.c
@@ -30,6 +30,7 @@
  * Created by Linas Vepstas December 1998
  * Copyright (c) 1998-2004 Linas Vepstas <linas at linas.org>
  * Copyright (c) 2000 Dave Peticolas
+ * Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
  */
 
   /* TODO: XXX we should probably move this resolve function to the
@@ -50,11 +51,13 @@
 
 #include <glib.h>
 
+#include "gnc-engine-util.h"
 #include "gnc-event.h"
 #include "gnc-trace.h"
 #include "qofbackend-p.h"
 #include "qofbook.h"
 #include "qofbook-p.h"
+#include "qofobject.h"
 #include "qofsession.h"
 #include "qofsession-p.h"
 
@@ -77,6 +80,7 @@
 }
 
 /* ====================================================================== */
+
 /* hook routines */
 
 void
@@ -309,12 +313,308 @@
    return session->book_id;
 }
 
+/* =============================================================== */
+
+typedef struct qof_entity_copy_data {
+	QofEntity *from;
+	QofEntity *to;
+	GHashTable *referenceTable;	
+	GSList *param_list;
+	QofSession *new_session;
+	gboolean error;
+}QofEntityCopyData;
+
+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(qecd != NULL);
+	if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
+			qecd->param_list = g_slist_append(qecd->param_list, param);
+	}
+	if(g_slist_length(qecd->param_list) == 0) { qecd->error = TRUE; }
+}
+
+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;
+	GUID            *cm_src_guid;
+	KvpFrame 		*cm_kvp;
+	char 		    cm_sa[GUID_ENCODING_LENGTH + 1];
+	/* 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(data != NULL);
+	g_return_if_fail(user_data != NULL);
+	context = (QofEntityCopyData*) user_data;
+	importEnt = context->from;
+	targetEnt = context->to;
+	registered_type = FALSE;
+	cm_param = (QofParam*) data;
+	if(safe_strcmp(cm_param->param_type, QOF_TYPE_STRING) == 0)  { 
+		cm_string = g_strdup(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 = 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(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 = 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 = cm_param->param_getfcn(importEnt, cm_param);
+		reference = g_new(QofEntityReference, 1);
+		reference->type = g_strdup(referenceEnt->e_type);
+		reference->guid = g_new(GUID, 1);
+		cm_guid = qof_entity_get_guid(referenceEnt);
+		guid_to_string_buff(cm_guid, cm_sa);
+		cm_string = g_strdup(cm_sa);
+		if(TRUE == string_to_guid(cm_string, reference->guid)) {
+			cm_src_guid = &importEnt->guid;
+			g_hash_table_insert(context->referenceTable, cm_src_guid, 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;
+	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);
+	qof_class_param_foreach(original->e_type, qof_entity_param_cb, qecd);
+	g_slist_foreach(qecd->param_list, qof_entity_foreach_copy, qecd);
+	qof_book_set_data(book, ENTITYREFERENCE, qecd->referenceTable);
+	qof_book_set_data(book, PARTIAL_QOFBOOK, (gboolean*)TRUE);
+}
+
+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);
+	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_class_param_foreach(original->e_type, qof_entity_param_cb, qecd);
+	g_slist_foreach(qecd->param_list, qof_entity_foreach_copy, qecd);
+	qof_book_set_data(book, ENTITYREFERENCE, qecd->referenceTable);
+	qof_book_set_data(book, PARTIAL_QOFBOOK, (gboolean*)TRUE);
+}
+
+gboolean qof_entity_copy_to_session(QofSession* new_session, QofEntity* original)
+{
+	QofEntityCopyData qecd;
+	QofInstance *inst;
+	QofBook *book;
+	const GUID *g;
+
+	if(qof_entity_guid_match(new_session, original)) return FALSE;
+	qecd.param_list = NULL;
+	qecd.referenceTable = g_hash_table_new(NULL, NULL);
+	qecd.new_session = new_session;
+	qecd.error = FALSE;
+	book = qof_session_get_book(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_class_param_foreach(original->e_type, qof_entity_param_cb, &qecd);
+	g_slist_foreach(qecd.param_list, qof_entity_foreach_copy, &qecd);
+	qof_book_set_data(book, ENTITYREFERENCE, qecd.referenceTable);
+	qof_book_set_data(book, PARTIAL_QOFBOOK, (gboolean*)TRUE);
+	return TRUE;
+}
+
+gboolean qof_entity_copy_list(QofSession *new_session, GList *entity_list)
+{
+	GList *e;
+	QofEntity *original;
+	QofEntityCopyData qecd;
+
+	qecd.param_list = NULL;
+	qecd.new_session = new_session;
+	qecd.referenceTable = g_hash_table_new(NULL, NULL);
+	qecd.error = FALSE;
+	for(e=entity_list; e; e=e->next)
+	{
+		original = (QofEntity*) e->data;
+		/* The GList can contain mixed entity types, it may 
+		appear slow, but we do need to check the type every time
+		because we can't re-use the QofCollection or QofIdType.	*/
+		if(qof_entity_guid_match(new_session, original)) return FALSE;
+	}
+	g_list_foreach(entity_list, qof_entity_list_foreach, &qecd);
+	return TRUE;
+}
+
+gboolean qof_entity_copy_coll(QofSession *new_session, QofCollection *entity_coll)
+{
+	QofEntityCopyData qecd;
+
+	qecd.param_list = NULL;
+	qecd.new_session = new_session;
+	qecd.referenceTable = g_hash_table_new(NULL, NULL);
+	qecd.error = FALSE;
+	qof_collection_foreach(entity_coll, qof_entity_coll_foreach, &qecd);
+	if(qecd.error == TRUE) return FALSE;
+	qof_collection_foreach(entity_coll, qof_entity_coll_copy, &qecd);
+	return TRUE;
+}
+
 /* ====================================================================== */
+
 /* Specify a library, and a function name. Load the library, 
  * call the function name in the library.  */
 static void
 load_backend_library (const char * libso, const char * loadfn)
 {
+	void (*initfn) (void);
 	void *dl_hand = dlopen (libso, RTLD_LAZY);
 	if (NULL == dl_hand)
 	{
@@ -322,7 +622,7 @@
 		PERR("Can't load %s backend, %s\n", libso, err_str);
 		return;
 	}
-	void (*initfn) (void)  = dlsym (dl_hand, loadfn);
+	initfn = dlsym (dl_hand, loadfn);
 	if (initfn)
 	{
 		 (*initfn)();
@@ -354,45 +654,40 @@
 static void
 qof_session_load_backend(QofSession * session, char * backend_name)
 {
-  GSList *p;
   GList *node;
   QofBook *book;
-  GNCModule  mod = 0;
+	GNCModule  mod;
+	GSList    *p;
+	char       *mod_name, *access_method, *msg;
   QofBackend    *(* be_new_func)(void);
-  char 	*access_method;
-  char       * mod_name = g_strdup_printf("gnucash/backend/%s", backend_name);
+	QofBackendProvider *prov;
 
+	mod	= 0;
+	mod_name = g_strdup_printf("gnucash/backend/%s", backend_name);
+	msg = g_strdup_printf(" ");
   /* FIXME : reinstate better error messages with gnc_module errors */
   ENTER (" ");
   /* FIXME: this needs to be smarter with version numbers. */
   /* FIXME: this should use dlopen(), instead of guile/scheme, 
    *    to load the modules.  Right now, this requires the engine to
    *    link to scheme, which is an obvious architecture flaw. 
-   *    XXX this is fexed below, in the non-gnucash version. Cut
+	*    XXX this is fixed below, in the non-gnucash version. Cut
    *    over at some point.
    */
-
   mod = gnc_module_load(mod_name, 0);
-
   if (mod) 
   {
     be_new_func = gnc_module_lookup(mod, "gnc_backend_new");
-
     if(be_new_func) 
     {
       session->backend = be_new_func();
-
       for (node=session->books; node; node=node->next)
       {
          book = node->data;
          qof_book_set_backend (book, session->backend);
       }
     }
-    else
-    {
-      qof_session_int_backend_load_error(session, " can't find backend_new ",
-                                         "");
-    }      
+		else { qof_session_int_backend_load_error(session, " can't find backend_new ",""); }
   }
   else
   {
@@ -401,22 +696,16 @@
 	  as it would be in QOF, instead of a resolved module name.
 	*/
 	access_method = g_strdup(backend_name);
-	if (NULL == provider_list)
-	{
-        load_backend_library ("libqsf-backend-file.so", "qsf_provider_init" );
-  }
 	for (p = provider_list; p; p=p->next)
 	{
-		QofBackendProvider *prov = p->data;
-
+		prov = p->data;
 		/* Does this provider handle the desired access method? */
 		if (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;
 			/* Tell the books about the backend that they'll be using. */
 			for (node=session->books; node; node=node->next)
 	{
@@ -426,12 +715,8 @@
 		return;
 	}
 	}
-	qof_session_push_error (session, ERR_BACKEND_NO_HANDLER, NULL);
-
-/*    qof_session_int_backend_load_error(session,
-                                       " failed to load '%s' backend", 
-                                       backend_name);*/
-    g_free(access_method);
+	msg = g_strdup_printf("failed to load '%s' backend", backend_name);
+	qof_session_push_error (session, ERR_BACKEND_NO_HANDLER, msg);
 	}
   g_free(mod_name);
   LEAVE (" ");
@@ -443,40 +728,44 @@
 qof_session_load_backend(QofSession * session, char * access_method)
 {
 	GSList *p;
-	ENTER (" ");
+	GList *node;
+	QofBackendProvider *prov;
+	QofBook *book;
 
+	ENTER (" ");
 	/* If the provider list is null, try to register the 'well-known'
-	 *  backends. Right now, there are only two. */
+	 *  backends. Right now, there's only two. */
 	if (NULL == provider_list)
 	{
+		/* hack alert: If you change this, change qof_session_save as well. */
+#ifdef BUILD_DWI
 		load_backend_library ("libqof_backend_dwi.so", "dwiend_provider_init");
-        load_backend_library ("libqsf-backend-file.so", "qsf_provider_init" );
+#endif
+		load_backend_library ("libqof-backend-qsf.so", "qsf_provider_init" );
 	}
-
-	for (p = provider_list; p; p=p->next)
+	p = g_slist_copy(provider_list);
+	while(p != NULL)
 	{
-		QofBackendProvider *prov = p->data;
-
+		prov = p->data;
 		/* Does this provider handle the desired access method? */
 		if (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;
 			/* Tell the books about the backend that they'll be using. */
-			GList *node;
 			for (node=session->books; node; node=node->next)
 			{
-				QofBook *book = node->data;
+				book = node->data;
 				qof_book_set_backend (book, session->backend);
 			}
 			return;
 		}
+		p = p->next;
 	}
-
-	qof_session_push_error (session, ERR_BACKEND_NO_HANDLER, NULL);
+	msg = g_strdup_printf("failed to load '%s' backend", backend_name);
+	qof_session_push_error (session, ERR_BACKEND_NO_HANDLER, msg);
 	LEAVE (" ");
 }
 #endif /* GNUCASH */
@@ -512,8 +801,9 @@
 qof_session_begin (QofSession *session, const char * book_id, 
                    gboolean ignore_lock, gboolean create_if_nonexistent)
 {
-  char * p;
+  char *p, *access_method, *msg;
   if (!session) return;
+  int err;
 
   ENTER (" sess=%p ignore_lock=%d, book-id=%s", 
          session, ignore_lock,
@@ -544,14 +834,14 @@
   /* destroy the old backend */
   qof_session_destroy_backend(session);
 
-  /* Look for somthing of the form of "file:/", "http://" or 
+  /* 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)
   {
-    char * access_method = g_strdup (book_id);
+    access_method = g_strdup (book_id);
     p = strchr (access_method, ':');
     *p = 0;
     qof_session_load_backend(session, access_method);
@@ -575,8 +865,6 @@
   /* If there's a begin method, call that. */
   if (session->backend->session_begin)
   {
-      int err;
-      char * msg;
       
       (session->backend->session_begin)(session->backend, session,
                                   session->book_id, ignore_lock,
@@ -609,7 +897,7 @@
 qof_session_load (QofSession *session,
                   QofPercentageFunc percentage_func)
 {
-  QofBook *newbook;
+	QofBook *newbook, *ob;
   QofBookList *oldbooks, *node;
   QofBackend *be;
   QofBackendError err;
@@ -620,7 +908,6 @@
   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. */
 
@@ -682,7 +969,7 @@
 
   for (node=oldbooks; node; node=node->next)
   {
-     QofBook *ob = node->data;
+		ob = node->data;
      qof_book_set_backend (ob, NULL);
      qof_book_destroy (ob);
   }
@@ -712,17 +999,6 @@
     if (ERR_BACKEND_NO_ERR != err)
     {
         qof_session_push_error (session, err, NULL);
-      
-        /* We close the backend here ... isn't this a bit harsh ??? 
-         * Actually, yes, it is harsh, and causes bug #117657,
-         * so let's NOT end the session just because it failed to save.
-         */
-#if cause_crash_when_saves_fail
-        if (be->session_end)
-        {
-            (be->session_end)(be);
-        }
-#endif
         return TRUE;
     }
     return FALSE;
@@ -734,12 +1010,101 @@
 {
   GList *node;
   QofBackend *be;
+	gboolean partial, change_backend;
+	QofBackendProvider *prov;
+	GSList *p;
+	QofBook *book, *abook;
+	int err;
+	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)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->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)
+		{
+			load_backend_library ("libqsf-backend-file.so", "qsf_provider_init" );
+		}
+		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 - what values to use for booleans? */
+					g_free(session->book_id);
+					session->book_id = NULL;
+					(session->backend->session_begin)(session->backend, session,
+						book_id, TRUE, FALSE);
+					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;
+		}
+	}
+	g_free(book_id);
   /* 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.
@@ -754,26 +1119,27 @@
   {
     for (node = session->books; node; node=node->next)
     {
-      QofBook *abook = node->data;
-
+			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. */
     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!");
 }
 
Index: qofbackend-p.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/qofbackend-p.h,v
retrieving revision 1.2.4.2
retrieving revision 1.2.4.3
diff -Lsrc/engine/qofbackend-p.h -Lsrc/engine/qofbackend-p.h -u -r1.2.4.2 -r1.2.4.3
--- src/engine/qofbackend-p.h
+++ src/engine/qofbackend-p.h
@@ -243,6 +243,14 @@
    */
   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);
 
@@ -279,6 +287,8 @@
 
   QofBePercentageFunc percentage;
 
+  QofBackendProvider *provider;
+
   /** Document Me !!! what is this supposed to do ?? */
   gboolean (*save_may_clobber_data) (QofBackend *);
 
Index: qofbackend.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/qofbackend.h,v
retrieving revision 1.3.4.3
retrieving revision 1.3.4.4
diff -Lsrc/engine/qofbackend.h -Lsrc/engine/qofbackend.h -u -r1.3.4.3 -r1.3.4.4
--- src/engine/qofbackend.h
+++ src/engine/qofbackend.h
@@ -69,6 +69,7 @@
   /* 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.
   
Index: kvp_frame.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/kvp_frame.h,v
retrieving revision 1.22.4.8
retrieving revision 1.22.4.9
diff -Lsrc/engine/kvp_frame.h -Lsrc/engine/kvp_frame.h -u -r1.22.4.8 -r1.22.4.9
--- src/engine/kvp_frame.h
+++ src/engine/kvp_frame.h
@@ -209,15 +209,14 @@
 
 /** @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.
+/** 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.
@@ -228,15 +227,16 @@
 /** @name KvpFrame Glist Bag Storing */
 /*@{*/
 
-/** The kvp_frame_add_gint64() routine will add (as in append, not as
- *  in sum) 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.
+/** 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);
@@ -594,26 +594,12 @@
 void        * kvp_value_get_binary(const KvpValue * value,
                                    guint64 * size_return); 
 
-/** Returns the GList of kvp_frames (not to be confused with GList's
+/** 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.  
- *
- * CAS: Maybe I'm confused (because this case really is too
- * confusing), but I think this comment is wrong.  I think the
- * returned GList is a list of whatever values were added by
- * kvp_frame_add_*().  So, if you called kvp_frame_add_frame, then,
- * yes, it's a list frames, but if you called
- * kvp_frame_add_{something_else}(), then it's a list of
- * something_elses.
- *
- * IMO, this glist case SHOULD NOT BE USED.  Why would you make a
- * KvpValue a glist of frames when a frame is already inherently a
- * list of subframes?  And why would you make a KvpValue a glist of
- * any other type when, apparently, the interface that requires you to
- * specify value types doesn't enforce type consistency in the list,
- * and provides no record of the type anyway?  Talk about asking for
- * headaches.  (I'm too chicken to search for callers. :)
-*/
+ * The returned GList is a list of  KvpValues of any type, including mixed. 
+ * Cast each list gpointer to a KvpValue and use the KvpValue->type to
+ * determine the type of value at each position in the list. */
 GList       * kvp_value_get_glist(const KvpValue * value);
 
 /** Value accessor. This one is non-copying -- the caller can modify
@@ -628,7 +614,17 @@
 
 /*@}*/
 
+/** \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: 
Index: qofsession.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/qofsession.h,v
retrieving revision 1.2.4.3
retrieving revision 1.2.4.4
diff -Lsrc/engine/qofsession.h -Lsrc/engine/qofsession.h -u -r1.2.4.3 -r1.2.4.4
--- src/engine/qofsession.h
+++ src/engine/qofsession.h
@@ -82,6 +82,7 @@
  * @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
@@ -236,7 +237,168 @@
  */
 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 - see 
+below. 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
+
+@{
+
+*/
+
+/** 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.
+*/
+#define ENTITYREFERENCE "QofEntityReference"
+
+/** \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);
+
+/** @} 
+*/
+
+/** @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 hash table 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 hash table 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 hashtable key is the GUID of the known entity and the value is a 
+QofEntityReference to the referenced entity - a struct that contains the
+GUID and the QofIdType of the referenced entity.
+
+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.
+
+This data is built into a hash table for use by any session that
+deals with partial QofBooks. It is used by the entity copy
+functions and by the QSF backend. The hashtable key is
+the GUID of the known entity and the value is a 
+QofEntityReference to the referenced entity. 
+*/
+typedef struct qof_entity_reference {
+	QofIdType type;
+	GUID      *guid;
+}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
@@ -264,7 +426,7 @@
 			     QofSession *real_session,
 			     QofPercentageFunc percentage_func);
 
-#endif /* GNUCASH_MJOR_VERSION */
+#endif /* GNUCASH_MAJOR_VERSION */
 
 /** Register a function to be called just before a session is closed.
  *


More information about the gnucash-changes mailing list