[Gnucash-changes] Neil Williams' QSF Backend.

Derek Atkins warlord at cvs.gnucash.org
Wed Jan 26 20:16:28 EST 2005


Log Message:
-----------
	Neil Williams' QSF Backend.

	* configure.in: add checks for libxml2 (requires >= 2.5.10)
	* src/backend/qsf: new backend directory
	* src/app-file/gnc-file.c: add new QSF error strings
	* src/backend/file/gnc-backend-file.[ch]: detect for QSF input files
	* src/engine/qofbackend.[ch]: know how to load the QSF backend

Tags:
----
gnucash-gnome2-dev

Modified Files:
--------------
    gnucash:
        ChangeLog
        configure.in
    gnucash/src/app-file:
        gnc-file.c
    gnucash/src/backend:
        Makefile.am
    gnucash/src/backend/file:
        Makefile.am
        gnc-backend-file.c
        gnc-backend-file.h
    gnucash/src/backend/file/test:
        Makefile.am
    gnucash/src/engine:
        qofbackend.h
        qofsession.c

Added Files:
-----------
    gnucash/src/backend/qsf:
        Makefile.am
        pilot-qsf-GnuCashInvoice.xml
        qsf-backend.c
        qsf-dir.h.in
        qsf-map.xsd.xml
        qsf-object.xsd.xml
        qsf-xml-map.c
        qsf-xml.c
        qsf-xml.h

Revision Data
-------------
Index: configure.in
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/configure.in,v
retrieving revision 1.359.2.36
retrieving revision 1.359.2.37
diff -Lconfigure.in -Lconfigure.in -u -r1.359.2.36 -r1.359.2.37
--- configure.in
+++ configure.in
@@ -336,6 +336,12 @@
 AC_SUBST(XML_CFLAGS)
 AC_SUBST(XML_LIBS)
 
+LIBXML2_REQUIRED=2.5.10
+PKG_CHECK_MODULES(LIBXML2, libxml-2.0 >= $LIBXML2_REQUIRED)
+LIBXML_VERSION=`$PKG_CONFIG --version xml`
+AC_SUBST(LIBXML2_CFLAGS)
+AC_SUBST(LIBXML2_LIBS)
+AC_SUBST(LIBXML_VERSION)
 
 oLIBS="$LIBS"
 LIBS="$LIBS $XML_LIBS"
@@ -381,6 +387,7 @@
 
 GNC_ACCOUNTS_DIR='${GNC_SHAREDIR}/accounts'
 GNC_GLADE_DIR='${GNC_SHAREDIR}/glade'
+QSF_SCHEMA_DIR='${GNC_SHAREDIR}/xml/qsf'
 GNC_UI_DIR='${GNC_SHAREDIR}/ui'
 GNC_GWRAP_LIBDIR='${GNC_SHAREDIR}/guile-modules/g-wrapped'
 GNC_MODULE_DIR='${pkglibdir}'
@@ -390,6 +397,7 @@
 AC_SUBST(GNC_CONFIGDIR)
 AC_SUBST(GNC_DOC_INSTALL_DIR)
 AC_SUBST(GNC_GLADE_DIR)
+AC_SUBST(QSF_SCHEMA_DIR)
 AC_SUBST(GNC_UI_DIR)
 AC_SUBST(GNC_GWRAP_LIBDIR)
 AC_SUBST(GNC_INCLUDE_DIR)
@@ -1113,6 +1121,7 @@
           src/backend/file/test/Makefile
           src/backend/file/test/test-files/Makefile
           src/backend/file/test/test-files/xml2/Makefile
+	  src/backend/qsf/Makefile
           src/backend/postgres/Makefile
           src/backend/postgres/test/Makefile
           src/backend/rpc/Makefile
Index: ChangeLog
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/ChangeLog,v
retrieving revision 1.1487.2.159
retrieving revision 1.1487.2.160
diff -LChangeLog -LChangeLog -u -r1.1487.2.159 -r1.1487.2.160
--- ChangeLog
+++ ChangeLog
@@ -1,3 +1,13 @@
+2005-01-26  Derek Atkins  <derek at ihtfp.com>
+
+	Neil Williams' QSF Backend.
+
+	* configure.in: add checks for libxml2 (requires >= 2.5.10)
+	* src/backend/qsf: new backend directory
+	* src/app-file/gnc-file.c: add new QSF error strings
+	* src/backend/file/gnc-backend-file.[ch]: detect for QSF input files
+	* src/engine/qofbackend.[ch]: know how to load the QSF backend
+
 2005-01-22  Derek Atkins  <derek at ihtfp.com>
 
 	Stephen Evanchik's patch to convert GncItemEdit to GObject/GLib
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/Makefile.am,v
retrieving revision 1.4
retrieving revision 1.4.6.1
diff -Lsrc/backend/Makefile.am -Lsrc/backend/Makefile.am -u -r1.4 -r1.4.6.1
--- src/backend/Makefile.am
+++ src/backend/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS=file ${SQL_DIR} ${RPC_DIR}
+SUBDIRS=qsf file ${SQL_DIR} ${RPC_DIR}
 
 DIST_SUBDIRS=file net postgres rpc
 
Index: gnc-backend-file.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/file/gnc-backend-file.h,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -Lsrc/backend/file/gnc-backend-file.h -Lsrc/backend/file/gnc-backend-file.h -u -r1.1.2.1 -r1.1.2.2
--- src/backend/file/gnc-backend-file.h
+++ src/backend/file/gnc-backend-file.h
@@ -55,6 +55,9 @@
     GNC_BOOK_BIN_FILE,
     GNC_BOOK_XML1_FILE,
     GNC_BOOK_XML2_FILE,
+    QSF_GNC_OBJECT,
+    QSF_OBJECT,
+    QSF_MAP,
 } QofBookFileType;
 
 QofBackend * libgncmod_backend_file_LTX_gnc_backend_new(void);
Index: gnc-backend-file.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/file/gnc-backend-file.c,v
retrieving revision 1.23.4.4
retrieving revision 1.23.4.5
diff -Lsrc/backend/file/gnc-backend-file.c -Lsrc/backend/file/gnc-backend-file.c -u -r1.23.4.4 -r1.23.4.5
--- src/backend/file/gnc-backend-file.c
+++ src/backend/file/gnc-backend-file.c
@@ -58,6 +58,7 @@
 #include "qofbackend-p.h"
 #include "qofbook-p.h"
 #include "qofsession.h"
+#include "qsf-xml.h"
 
 static short module = MOD_BACKEND;
 
@@ -396,6 +397,12 @@
         return GNC_BOOK_XML1_FILE;
     } else if(is_gzipped_file(path)) {
         return GNC_BOOK_XML2_FILE;
+    } else if(is_our_qsf_object(path)) {
+        return QSF_GNC_OBJECT;  /**< QSF object file using only GnuCash QOF objects */
+	} else if(is_qsf_object(path)) {
+		return QSF_OBJECT;  	/**< QSF object file that needs a QSF map */
+	} else if(is_qsf_map(path)) {
+		return QSF_MAP;  		/**< QSF map file */
     } else {
         return GNC_BOOK_BIN_FILE;
     }
@@ -806,10 +813,11 @@
 static void
 gnc_file_be_load_from_file (QofBackend *bend, QofBook *book)
 {
-    QofBackendError error = ERR_BACKEND_NO_ERR;
+    QofBackendError error;
     gboolean rc;
     FileBackend *be = (FileBackend *) bend;
 
+	error = ERR_BACKEND_NO_ERR;
     be->primary_book = book;
 
     switch (gnc_file_be_determine_file_type(be->fullpath))
@@ -824,12 +832,24 @@
         if (FALSE == rc) error = ERR_FILEIO_PARSE_ERROR;
         break;
 
+	case QSF_GNC_OBJECT:
+		error = qof_session_load_our_qsf_object(qof_session_get_current_session(), be->fullpath);
+		break;
+
+	case QSF_OBJECT:
+		/* a QSF object file needs a QSF map to convert external objects */
+		error = qof_session_load_qsf_object(qof_session_get_current_session(), be->fullpath);
+		break;
+
+	case QSF_MAP:
+		error = ERR_QSF_MAP_NOT_OBJ;
+		break;
+
     case GNC_BOOK_BIN_FILE:
         /* presume it's an old-style binary file */
         qof_session_load_from_binfile(book, be->fullpath);
         error = gnc_get_binfile_io_error();
         break;
-
     default:
         PWARN("File not any known type");
         error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/file/Makefile.am,v
retrieving revision 1.16.4.3
retrieving revision 1.16.4.4
diff -Lsrc/backend/file/Makefile.am -Lsrc/backend/file/Makefile.am -u -r1.16.4.3 -r1.16.4.4
--- src/backend/file/Makefile.am
+++ src/backend/file/Makefile.am
@@ -9,6 +9,7 @@
   -I${top_srcdir}/src/gnc-module \
   -I${top_srcdir}/src/core-utils\
   -I${top_srcdir}/lib/libc\
+  -I${top_srcdir}/src/backend/qsf \
   ${XML_CFLAGS} \
   ${GLIB_CFLAGS}
 
@@ -56,4 +57,6 @@
 libgncmod_backend_file_la_LDFLAGS = -module
 libgncmod_backend_file_la_LIBADD = \
    ${GLIB_LIBS} ${XML_LIBS} \
-   ${top_builddir}/src/engine/libgncmod-engine.la 
+   ${top_builddir}/src/engine/libgncmod-engine.la \
+   ${top_builddir}/src/backend/qsf/libqof-backend-qsf.la
+
Index: Makefile.am
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/backend/file/test/Makefile.am,v
retrieving revision 1.28.4.2
retrieving revision 1.28.4.3
diff -Lsrc/backend/file/test/Makefile.am -Lsrc/backend/file/test/Makefile.am -u -r1.28.4.2 -r1.28.4.3
--- src/backend/file/test/Makefile.am
+++ src/backend/file/test/Makefile.am
@@ -67,6 +67,7 @@
   -I${top_srcdir}/src/engine \
   -I${top_srcdir}/src/engine/test-core \
   -I${top_srcdir}/src/backend/file \
+  -I${top_srcdir}/src/backend/qsf \
   ${XML_CFLAGS} \
   ${GLIB_CFLAGS} \
   ${GUILE_INCS}
--- /dev/null
+++ src/backend/qsf/qsf-xml-map.c
@@ -0,0 +1,649 @@
+/***************************************************************************
+ *            qsf-xml-map.c
+ *
+ *  Sat Jan  1 07:31:55 2005
+ *  Copyright  2005  Neil Williams
+ *  linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+ /** @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
+
+#include <libxml/xmlversion.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlschemas.h>
+#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)
+{
+	xmlNodePtr output_parent;
+	time_t *qsf_time;
+	char date_as_string[QSF_DATE_LENGTH];
+
+	output_parent = xmlAddChild(parent_tag, xmlNewNode(ns,
+		xmlGetProp(import_node, QSF_OBJECT_TYPE)));
+	xmlNewProp(output_parent, QSF_OBJECT_TYPE, xmlGetProp(import_node, MAP_VALUE_ATTR));
+	qsf_time = (time_t*)g_hash_table_lookup(qsf_default_hash, default_name);
+	strftime(date_as_string, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(qsf_time));
+	xmlNodeAddContent(output_parent, date_as_string);
+}
+
+static void
+qsf_string_default_handler(const char *default_name, GHashTable *qsf_default_hash,
+	xmlNodePtr parent_tag, xmlNodePtr import_node, xmlNsPtr ns)
+{
+	xmlNodePtr node;
+	xmlChar *output;
+
+	node = xmlAddChild(parent_tag,
+		xmlNewNode(ns, xmlGetProp(import_node, QSF_OBJECT_TYPE)));
+	xmlNewProp(node, QSF_OBJECT_TYPE, xmlGetProp(import_node, MAP_VALUE_ATTR));
+	output = (xmlChar *)g_hash_table_lookup(qsf_default_hash, default_name);
+	xmlNodeAddContent(node, output);
+}
+
+void
+qsf_map_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid)
+{
+	gchar *qof_version;
+	GString *buff;
+	xmlNodePtr child_node;
+	xmlChar *match;
+
+	if (qsf_is_element(child, ns, MAP_DEFINITION_TAG)) {
+		qof_version = xmlGetProp(child, MAP_QOF_VERSION);
+		buff = g_string_new(" ");
+		g_string_printf(buff, "%i", QSF_QOF_VERSION);
+		if(xmlStrcmp(qof_version, buff->str) != 0)
+		{
+			valid->error_state = ERR_QSF_BAD_QOF_VERSION;
+			return;
+		}
+		for(child_node = child->children; child_node != NULL;
+			child_node = child_node->next)
+		{
+			if (qsf_is_element(child_node, ns, MAP_DEFINE_TAG)) {
+				g_hash_table_insert(valid->validation_table,
+					xmlGetProp(child_node, MAP_E_TYPE),
+					xmlNodeGetContent(child_node));
+			}
+		}
+	}
+	if(qsf_is_element(child, ns, MAP_OBJECT_TAG)) {
+		match = NULL;
+		match = (xmlChar*) g_hash_table_lookup( valid->validation_table,
+			xmlGetProp(child, MAP_TYPE_ATTR));
+		if(match) {
+			valid->map_calculated_count++;
+		}
+	}
+}
+
+
+/** \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;
+	int valid_count;
+	struct qsf_node_iterate iter;
+	xmlNodePtr map_root, object_root;
+	xmlNsPtr map_ns;
+	qsf_validator valid;
+	char *path;
+	gchar *map_path;
+
+	g_return_val_if_fail((params != NULL),FALSE);
+	path = g_strdup(params->filepath);
+	map_path = g_strdup_printf("%s/%s", QSF_SCHEMA_DIR, map_file);
+	if(path == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+		return FALSE; 
+	}
+	doc = xmlParseFile(path);
+	if(doc == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) { 
+		qof_backend_set_error(params->be, ERR_QSF_INVALID_OBJ);
+		return FALSE; 
+	}
+	object_root = xmlDocGetRootElement(doc);
+	if(map_path == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+		return FALSE; 
+	}
+	valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+	map_doc = xmlParseFile(map_path);
+	if(map_doc == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, map_doc)) { 
+		qof_backend_set_error(params->be, ERR_QSF_INVALID_MAP);
+		return FALSE; 
+	}
+	map_root = xmlDocGetRootElement(map_doc);
+	valid.map_calculated_count = 0;
+	valid.valid_object_count = 0;
+	valid.error_state = ERR_BACKEND_NO_ERR;
+	map_ns = map_root->ns;
+	iter.ns = map_ns;
+	qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+	iter.ns = object_root->ns;
+	qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+	if (valid.error_state != ERR_BACKEND_NO_ERR) {
+		qof_backend_set_error(params->be, valid.error_state);
+		g_hash_table_destroy(valid.validation_table);
+		return FALSE;
+	}
+	valid_count = 0 - g_hash_table_size(valid.validation_table);
+	valid_count += valid.map_calculated_count;
+	valid_count += valid.valid_object_count;
+	g_hash_table_destroy(valid.validation_table);
+	if(valid_count == 0) {
+		/* clear any previous error */
+		qof_backend_get_error(params->be);
+		return TRUE;
+	}
+	qof_backend_set_error(params->be, ERR_QSF_WRONG_MAP);
+	return FALSE;
+}
+
+gboolean is_qsf_map_be(qsf_param *params)
+{
+	xmlDocPtr doc;
+	struct qsf_node_iterate iter;
+	qsf_validator valid;
+	xmlNodePtr map_root;
+	xmlNsPtr map_ns;
+	char *path;
+
+	g_return_val_if_fail((params != NULL),FALSE);
+	qof_backend_get_error(params->be);
+	path = g_strdup(params->filepath);
+	if(path == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+		return FALSE;
+	}
+	doc = xmlParseFile(path);
+	if(doc == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, doc)) {
+		qof_backend_set_error(params->be, ERR_QSF_INVALID_MAP);
+		return FALSE; 
+	}
+	map_root = xmlDocGetRootElement(doc);
+	map_ns = map_root->ns;
+	iter.ns = map_ns;
+	valid.error_state = ERR_BACKEND_NO_ERR;
+	qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+	if (valid.error_state != ERR_BACKEND_NO_ERR) {
+		qof_backend_set_error(params->be, valid.error_state);
+		g_hash_table_destroy(valid.validation_table);
+		return FALSE;
+	}
+	qof_backend_get_error(params->be);
+	g_hash_table_destroy(valid.validation_table);
+	return TRUE;
+}
+
+gboolean is_qsf_map(const char *path)
+{
+	xmlDocPtr doc;
+	struct qsf_node_iterate iter;
+	qsf_validator valid;
+	xmlNodePtr map_root;
+	xmlNsPtr map_ns;
+
+	g_return_val_if_fail((path != NULL),FALSE);
+	if(path == NULL) { return FALSE; }
+	doc = xmlParseFile(path);
+	if(doc == NULL) { return FALSE; }
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, doc)) {
+		return FALSE; 
+	}
+	map_root = xmlDocGetRootElement(doc);
+	map_ns = map_root->ns;
+	iter.ns = map_ns;
+	valid.error_state = ERR_BACKEND_NO_ERR;
+	qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+	if (valid.error_state != ERR_BACKEND_NO_ERR) {
+		g_hash_table_destroy(valid.validation_table);
+		return FALSE;
+	}
+	g_hash_table_destroy(valid.validation_table);
+	return TRUE;
+}
+
+
+/** \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 )
+{
+	xmlChar *qsf_enum;
+	
+	g_return_if_fail(params->qsf_define_hash != NULL);
+	if (qsf_is_element(child, ns, MAP_DEFINE_TAG)) {
+		if(NULL == g_hash_table_lookup(params->qsf_define_hash,
+			xmlGetProp(child, MAP_E_TYPE)))
+		{
+			g_hash_table_insert(params->qsf_define_hash,
+				xmlGetProp(child, MAP_E_TYPE), params->child_node);
+		}
+		else {
+			qof_backend_set_error(params->be, ERR_QSF_BAD_MAP);
+			return;
+		}
+	}
+	if(qsf_is_element(child, ns, MAP_DEFAULT_TAG)) {
+		if(qsf_strings_equal(xmlGetProp(child, MAP_TYPE_ATTR), MAP_ENUM_TYPE))
+		{
+			qsf_enum = xmlNodeGetContent(child);
+			/** Use content to discriminate enums in QOF */
+			/** \todo FIXME: the default enum value is not used
+			implemented properly or fully handled.
+			*/
+			if(NULL == g_hash_table_lookup(params->qsf_default_hash,
+				xmlNodeGetContent(child)))
+			{
+				g_hash_table_insert(params->qsf_default_hash, 
+					xmlNodeGetContent(child), child);
+			}
+			else
+			{
+				qof_backend_set_error(params->be, ERR_QSF_BAD_MAP);
+				return;
+			}
+		}
+		/** Non-enum defaults */
+		else {
+			if(NULL == g_hash_table_lookup(params->qsf_default_hash,
+					xmlGetProp(child, MAP_NAME_ATTR)))
+			{
+				g_hash_table_insert(params->qsf_default_hash,
+					xmlGetProp(child, MAP_NAME_ATTR), child);
+			}
+			else
+/*					if(0 != xmlHashAddEntry(params->default_map,
+				xmlGetProp(child_node, MAP_NAME_ATTR), child_node))*/
+			{
+				qof_backend_set_error(params->be, ERR_QSF_BAD_MAP);
+				return;
+			}
+		}
+	}
+}
+
+void
+qsf_map_top_node_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params)
+{
+	gchar *qof_version;
+	GString *buff;
+/*	xmlChar *qsf_enum;
+	xmlNodePtr child_node;*/
+	struct qsf_node_iterate iter;
+
+	if(!params->qsf_define_hash) return;
+	if(!params->qsf_default_hash) return;
+	if(qsf_is_element(child, ns, MAP_DEFINITION_TAG)) {
+		qof_version = xmlGetProp(child, MAP_QOF_VERSION);
+		buff = g_string_new(" ");
+		g_string_printf(buff, "%i", QSF_QOF_VERSION);
+		if(xmlStrcmp(qof_version, buff->str) != 0) {
+			qof_backend_set_error(params->be, ERR_QSF_BAD_QOF_VERSION);
+			return;
+		}
+		iter.ns = ns;
+		qsf_node_foreach(child, qsf_map_default_handler, &iter, params);
+	}
+}
+
+static char*
+qsf_else_set_value(xmlNodePtr parent, GHashTable *default_hash,
+		char *content, xmlNsPtr map_ns)
+{
+	xmlNodePtr cur_node;
+
+	content = NULL;
+	for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+	{
+		if(qsf_is_element(cur_node, map_ns, QSF_CONDITIONAL_SET)) {
+			content = xmlNodeGetContent(cur_node);
+			return content;
+		}
+	}
+	return NULL;
+}
+
+/** \brief Handles the set tag in the map.
+
+This function will be overhauled once inside QOF 
+QOF hook required for "Lookup in the receiving application"
+*/
+static char*
+qsf_set_handler(xmlNodePtr parent, GHashTable *default_hash,
+	char *content, qsf_param *params)
+{
+	xmlNodePtr cur_node, lookup_node;
+
+	content = NULL;
+	for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+	{
+		if(qsf_is_element(cur_node, params->map_ns, QSF_CONDITIONAL_SET)) 
+		{
+			content = xmlGetProp(cur_node, QSF_OPTION);
+			if(qsf_strings_equal(xmlGetProp(cur_node, QSF_OPTION), "qsf_lookup_string")) 
+			{
+				lookup_node = (xmlNodePtr) g_hash_table_lookup(default_hash, 
+					xmlNodeGetContent(cur_node));
+				content = xmlGetProp(lookup_node, MAP_VALUE_ATTR);
+				/** \todo FIXME: do the lookup. */
+				g_message("Lookup %s in the receiving application\n", content );
+				return content;
+			}
+			if(content) 
+			{
+				lookup_node = (xmlNodePtr) g_hash_table_lookup(default_hash, 
+					xmlNodeGetContent(cur_node));
+				content = xmlGetProp(lookup_node, "value");
+				return content;
+			}
+			content = xmlGetProp(parent, "boolean");
+			if(!content) {
+				/** \todo Check qsf_parameter_hash arguments */
+				lookup_node = (xmlNodePtr) g_hash_table_lookup(params->qsf_parameter_hash,
+					xmlGetProp(parent->parent, MAP_TYPE_ATTR));
+				if(lookup_node) { return xmlNodeGetContent(lookup_node); }
+				return xmlNodeGetContent(cur_node);
+			}
+		}
+	}
+	return NULL;
+}
+
+static void
+qsf_calculate_else(xmlNodePtr param_node, xmlNodePtr child, qsf_param *params)
+{
+	xmlNodePtr export_node;
+	xmlChar *output_content, *object_data;
+
+	if(qsf_is_element(param_node, params->map_ns, QSF_CONDITIONAL_ELSE)) {
+		if(params->boolean_calculation_done == 0) {
+			output_content = object_data = NULL;
+			output_content = qsf_set_handler(param_node,
+				params->qsf_default_hash, output_content, params);
+			if(output_content == NULL) {
+				output_content = xmlGetProp(param_node, MAP_TYPE_ATTR);
+				object_data = qsf_else_set_value(param_node, params->qsf_default_hash,
+					output_content, params->map_ns);
+				output_content = xmlGetProp( (xmlNodePtr) g_hash_table_lookup(
+					params->qsf_default_hash, object_data), MAP_VALUE_ATTR);
+			}
+			if(object_data != NULL) {
+				export_node =(xmlNodePtr) g_hash_table_lookup(
+					params->qsf_parameter_hash,
+					xmlGetProp(params->child_node, QSF_OBJECT_TYPE));
+				object_data = xmlNodeGetContent(export_node);
+			}
+			if(output_content != NULL) { object_data = output_content; }
+			export_node = xmlAddChild(params->lister, xmlNewNode(params->qsf_ns,
+				xmlGetProp(child, QSF_OBJECT_TYPE)));
+			xmlNewProp(export_node, QSF_OBJECT_TYPE,
+				xmlGetProp(child, MAP_VALUE_ATTR));
+			xmlNodeAddContent(export_node, object_data);
+			params->boolean_calculation_done = 1;
+		}
+	}
+}
+
+static void
+qsf_set_format_value(xmlChar *format, char *qsf_time_now_as_string,
+	xmlNodePtr cur_node, qsf_param *params)
+{
+	int result;
+	xmlChar *content;
+	time_t *output;
+	struct tm *tmp;
+	time_t tester;
+	xmlNodePtr kl;
+	regex_t reg;
+
+	/** Comments retained - this behaves a little strangely */
+
+	result = 0;
+	if(format == NULL) { return; }
+	content = xmlNodeGetContent(cur_node);
+	output = (time_t*) g_hash_table_lookup(params->qsf_default_hash, content);
+	if(!output) {
+		/** No default time set, use the object time */
+		/** fill the time structs with temp data */
+		tester = time(NULL);
+		tmp = gmtime(&tester);
+		/** Lookup the object value to read */
+		/** \todo qsf_parameter_hash check correct arguments */
+		kl = (xmlNodePtr) g_hash_table_lookup(params->qsf_parameter_hash, content);
+		if(!kl) {
+			printf("no suitable date set.\n");
+			return;
+		}
+		/** Read the object value as a dateTime  */
+		strptime(xmlNodeGetContent(kl), QSF_XSD_TIME, tmp);
+		if(!tmp) {
+			printf("empty date field in QSF object.\n");
+			return;
+		}
+		tester = mktime(tmp);
+		output = &tester;
+	}
+	result = regcomp(&reg, "%[a-zA-Z]", REG_EXTENDED|REG_NOSUB);
+	result = regexec(&reg, format,(size_t)0,NULL,0);
+	if(result == REG_NOMATCH) { format = "%F"; }
+	regfree(&reg);
+	/** QSF_DATE_LENGTH preset for all internal and QSF_XSD_TIME string formats.
+	 */
+	strftime(qsf_time_now_as_string, QSF_DATE_LENGTH, format, gmtime(output));
+}
+
+static void
+qsf_boolean_set_value(xmlNodePtr parent, qsf_param *params,
+		char *content, xmlNsPtr map_ns)
+{
+	xmlNodePtr cur_node;
+	xmlChar *boolean_name;
+
+	boolean_name = NULL;
+	for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next) {
+		if(qsf_is_element(cur_node, map_ns, QSF_CONDITIONAL_SET)) {
+			boolean_name = xmlGetProp(cur_node, QSF_FORMATTING_OPTION);
+			qsf_set_format_value(boolean_name, content, cur_node, params);
+		}
+	}
+}
+
+static void
+qsf_calculate_conditional(xmlNodePtr param_node, xmlNodePtr child, qsf_param *params)
+{
+	xmlNodePtr export_node;
+	xmlChar *output_content;
+
+	output_content = NULL;
+	if(qsf_is_element(param_node, params->map_ns, QSF_CONDITIONAL)) {
+		printf("param_node=%s\n", param_node->name);
+		if(params->boolean_calculation_done == 0) {
+		/* set handler */
+		output_content = qsf_set_handler(param_node, params->qsf_default_hash, output_content, params);
+		/* If the 'if' contains a boolean that has a default value */
+		if(output_content == NULL) {
+			if(NULL != xmlGetProp(param_node, QSF_BOOLEAN_DEFAULT)) {
+			output_content = xmlGetProp( (xmlNodePtr) g_hash_table_lookup(
+				params->qsf_default_hash, xmlGetProp(param_node, QSF_BOOLEAN_DEFAULT) ),
+					MAP_VALUE_ATTR);
+			}
+			/* Is the default set to true? */
+			if( 0 == qsf_compare_tag_strings(output_content, QSF_XML_BOOLEAN_TEST)) 
+			{
+				qsf_boolean_set_value(param_node, params, output_content, params->map_ns);
+				export_node = xmlAddChild(params->lister, xmlNewNode(params->qsf_ns,
+					xmlGetProp(child, QSF_OBJECT_TYPE)));
+				xmlNewProp(export_node, QSF_OBJECT_TYPE,
+					xmlGetProp(child, MAP_VALUE_ATTR));
+				xmlNodeAddContent(export_node, output_content);
+				params->boolean_calculation_done = 1;
+				}
+			}
+		}
+	}
+}
+
+void
+qsf_map_object_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params)
+{
+	xmlNodePtr param_node, export_node;
+	xmlNsPtr map_ns, qsf_ns;
+	xmlChar *output_content;
+	int result, count;
+
+	map_ns = ns;
+	qsf_ns = params->qsf_ns;
+	param_node = NULL;
+	result = 0;
+	count = params->count;
+	if(child == NULL) { return; }
+	if(ns == NULL) { return; }
+	params->boolean_calculation_done = 0;
+	
+	if(qsf_is_element(child, map_ns, MAP_CALCULATE_TAG)) {
+		params->boolean_calculation_done = 0;
+		for(param_node = child->children; param_node != NULL;
+			param_node = param_node->next)
+		{
+			output_content = NULL;
+			export_node = NULL;
+			if(!params->lister) { params->lister = qsf_add_object_tag(params, count); }
+			if(qsf_is_element(param_node, map_ns, "set"))
+			{
+				/* Map the pre-defined defaults */
+				if(0 == qsf_compare_tag_strings(
+					xmlNodeGetContent(param_node), "qsf_enquiry_date"))
+				{
+					qsf_string_default_handler("qsf_enquiry_date",
+						params->qsf_default_hash, params->lister, child, qsf_ns);
+				}
+				if(0 == qsf_compare_tag_strings(
+					xmlNodeGetContent(param_node), "qsf_time_now"))
+				{
+					qsf_date_default_handler("qsf_time_now",
+						params->qsf_default_hash, params->lister, child, qsf_ns);
+				}
+				if(0 == qsf_compare_tag_strings(
+					xmlNodeGetContent(param_node), "qsf_time_string"))
+				{
+					qsf_string_default_handler("qsf_time_string",
+						params->qsf_default_hash, params->lister, child, qsf_ns);
+				}
+				output_content = xmlNodeGetContent(param_node);
+				if(output_content != NULL) {
+					output_content = xmlNodeGetContent((xmlNodePtr) g_hash_table_lookup(
+						params->qsf_parameter_hash, xmlGetProp(child, MAP_TYPE_ATTR)));
+					if(output_content != NULL) {
+						export_node = xmlAddChild(params->lister, xmlNewNode(qsf_ns,
+							xmlGetProp(child, QSF_OBJECT_TYPE)));
+						xmlNewProp(export_node, QSF_OBJECT_TYPE,
+							xmlGetProp(child, MAP_VALUE_ATTR));
+						xmlNodeAddContent(export_node, output_content);
+						xmlFree(output_content);
+					}
+				}
+			}
+
+			qsf_calculate_conditional( param_node, child, params);
+			qsf_calculate_else(param_node, child, params);
+		}
+
+		/* calculate_map currently not in use */
+		/* ensure uniqueness of the key before re-instating */
+/*		result = xmlHashAddEntry2(calculate_map,
+			xmlGetProp(child_node, MAP_TYPE_ATTR),
+			xmlGetProp(child_node, MAP_VALUE_ATTR), child_node);
+		if(result != 0) {
+			printf("add entry to calculate hash failed. %s\t%s\t%s.\n",
+				xmlGetProp(child_node, MAP_TYPE_ATTR),
+			xmlGetProp(child_node, MAP_VALUE_ATTR), child_node->name);
+			return;
+		}
+		
+		is_qsf_object_with_map(path, map_path);
+*/
+	}
+}
--- /dev/null
+++ src/backend/qsf/qsf-object.xsd.xml
@@ -0,0 +1,103 @@
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+targetNamespace="urn:qof-qsf-container" xmlns="urn:qof-qsf-container">
+ <xsd:annotation>
+    <xsd:documentation xml:lang="en">
+	Query Object Framework Serialization Format (QSF)
+	Copyright 2004 Neil Williams linux at codehelp.co.uk
+	QSF is part of QOF.
+	QOF is free software; you can redistribute it and/or modify it
+	under the terms of the GNU General Public License as published by the
+	Free Software Foundation; either version 2 of the License, or (at your
+	option) any later version.
+	This program is distributed in the hope that it will be useful, but
+	WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+	General Public License for more details.
+	You should have received a copy of the GNU General Public License along
+	with this program; if not, write to the Free Software Foundation, Inc., 59
+	Temple	Place, Suite 330, Boston, MA 02111-1307 USA
+    </xsd:documentation>
+  </xsd:annotation>
+<xsd:element name="qof-qsf" type="qsf-type"/>
+<xsd:complexType name="qsf-type">
+ <xsd:sequence>
+   <xsd:element name="book" 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:sequence>
+ <xsd:attribute name="count" type="xsd:positiveInteger"/>
+</xsd:complexType>
+<xsd:complexType name="qsfobject">
+ <xsd:sequence>
+   <xsd:element name="string" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:string">
+       <xsd:attribute name="type" type="xsd:string"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="guid" 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="boolean" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:boolean">
+       <xsd:attribute name="type" type="xsd:string"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="numeric" minOccurs="0" maxOccurs="unbounded">
+   <xsd:complexType>
+    <xsd:simpleContent>
+     <xsd:extension base="xsd:string">
+      <xsd:attribute name="type" type="xsd:string"/>
+     </xsd:extension>
+    </xsd:simpleContent>
+   </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="date" minOccurs="0" maxOccurs="unbounded" >
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:dateTime">
+       <xsd:attribute name="type" type="xsd:string"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="gint32" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:int">
+       <xsd:attribute name="type" type="xsd:string"/>
+      </xsd:extension>
+     </xsd:simpleContent>
+     </xsd:complexType>
+   </xsd:element>
+   <xsd:element name="gint64" minOccurs="0" maxOccurs="unbounded">
+     <xsd:complexType>
+     <xsd:simpleContent>
+      <xsd:extension base="xsd:int">
+       <xsd:attribute name="type" 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>
--- /dev/null
+++ src/backend/qsf/qsf-xml.h
@@ -0,0 +1,746 @@
+/***************************************************************************
+ *            qsf-xml.h
+ *
+ *  Fri Nov 26 19:29:47 2004
+ *  Copyright  2004-2005  Neil Williams  <linux at codehelp.co.uk>
+ *
+ ****************************************************************************/
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+ #ifndef QSF_XML_H
+ #define QSF_XML_H
+
+/** @addtogroup Backend
+    @{ */
+/** @addtogroup QSF QOF Serialisation Format
+
+QSF - QOF Serialization Format is an XML serialization format
+i.e. it lays out a QOF object in a series of XML tags. The format will  
+consist of two component formats:
+
+qof-qsf for the QSF object data and
+
+qsf-map to map sets of QSF objects between QOF applications.
+
+QSF object files will contain user data and are to be exported from QOF applications 
+under user control or they can be hand-edited. QSF maps contain application data and 
+can be created by application developers from application source code. Tools may be 
+created later to generate maps interactively but maps require application support as 
+well as an understanding of QOF objects in the import and output applications and are 
+intended to remain within the scope of application developers rather than users.
+
+A QSF file written by one QOF application will need an appropriate QSF map before the 
+data can be accessed by a different application using QOF. Any QSF objects that are 
+not defined in the map will be ignored. QSF files written and accessed by the same 
+application can use maps if necessary or can simply import the QSF data as a whole.
+
+Unless specifically mentioned otherwise, all defined strings are case-sensitive.
+
+Full documentation of this format is at:
+
+http://code.neil.williamsleesmill.me.uk/qsf.html
+
+QSF itself is now being built into the QOF library for use with pilot-link to allow
+Palm objects to be described in QOF, written to XML as QSF and imported directly into 
+GnuCash and other QOF-compliant applications. As a generic format, it does not depend 
+on any pre-defined objects - as the current GnuCash XML format depends on AccountGroup. 
+Instead, QSF is a simple container for all QOF objects.
+
+QSF grew from the qof_book_merge code base and uses the qof_book_merge code that is now 
+part of QOF. Any QofBook generated by QSF still needs to be merged into the existing 
+application data using qof_book_merge. See ::BookMerge.
+
+QSF can be used as an export or offline storage format for QOF applications (although 
+long term storage may be best performed using separate (non-XML) methods, depending 
+on the application).
+
+QSF is designed to cope with partial QofBooks at the QofObject level. There is no 
+requirement for specific objects to always be defined, as long as each QOF object 
+specified is fully defined, no orphan or missing parameters are allowed.
+
+Work is continuing on supporting QSF in GnuCash and QOF. QSF is a very open format - 
+the majority of the work will be in standardising object types and creating maps that 
+convert between objects. Applications that read QSF should ignore any objects that do 
+not match the available maps and warn the user about missing data. 
+
+Anyone is free to create their own QSF objects, subject to the GNU GPL. It is intended 
+that QSF can be used as the flexible, open and free format for QOF data - providing 
+all that is lacking from a typical CSV export with all the validation benefits of XML 
+and the complex data handling of QOF. The QSF object and map formats remain under the 
+GNU GPL licence and QSF is free software.
+
+\todo
+	- Adding more map support, some parts of the map are still not coded. equals, 
+		variables and the conditional logic may not be up to the task of the
+		datebook repetition calculations.
+	- Rationalise the API - remove functions that shouldn't be public.
+
+QSF is in three sections:
+	- QSF Backend : a QofBackend for file:/ QSF objects and maps.
+		qsf-backend.c
+	- QSF Object  : Input, export and validation of QSF object files.
+		qsf-xml.c
+	- QSF Map : Validation, processing and conversion routines.
+		qsf-xml-map.c
+
+    @{ */
+/** @file qsf-xml.h
+    @brief  QSF API - Backend, maps and objects.
+    @author Copyright (C) 2004-2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <regex.h>
+#include <time.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlschemas.h>
+#include "gnc-date.h"
+#include "qof_book_merge.h"
+#include "qofbook.h"
+#include "qofclass.h"
+#include "qofobject.h"
+#include "qofbackend-p.h"
+#include "qofsession.h"
+
+typedef enum  {
+	QSF_UNDEF = 0, /**< Initial undefined value. */
+	IS_QSF_MAP,   /**< A QSF map */
+	IS_QSF_OBJ,   /**< A QSF object without a map - it may or may not need one. */
+	HAVE_QSF_MAP, /**< A QSF object with the map it needs. */
+	OUR_QSF_OBJ,  /**< A QSF object that can be loaded without a map. */
+}qsf_type;
+	
+/** \internal Holds a description of the QofObject.
+
+Used when converting QOF objects from another application. The incoming,
+\b unknown, objects need to be stored prior to conversion. This allows 
+many-to-many conversions where an invoice can receive data from an incoming
+expense AND datebook and use data from an incoming contacts object to lookup
+the customer for the invoice.
+*/
+typedef struct qsf_object_set
+{
+	GHashTable *parameters;
+	QofIdType object_type;
+	int object_count;
+}qsf_objects;
+
+#define QSF_QOF_VERSION QOF_OBJECT_VERSION /**< QOF Version check.
+
+Make sure the same version of QOF is in use in both applications.
+*/
+#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
+
+The map namespace is not included as maps are not currently written out by QOF.
+*/
+#define QSF_DATE_LENGTH 31 /**< Max length of QSF_XSD_TIME */
+#define QSF_BOOK_TAG	"book" /**< First level child: book tag - the ::QofBook. */
+#define QSF_BOOK_GUID	"book-guid" /**< QOF GUID tag for the QofBook described by this QSF object file */
+#define QSF_BOOK_COUNT	"count" /**< Sequential counter of each book in this file */
+#define QSF_OBJECT_TAG	"object" /**< Second level child: object tag */
+#define QSF_OBJECT_TYPE	"type" /**< QSF parameter name for object type specifiers */
+#define QSF_OBJECT_COUNT	"count" /**< Sequential counter for each QSF object in this file */
+#define QSF_XML_VERSION	"1.0"  /**< The current XML version. */
+#define MAP_ROOT_TAG	"qsf-map" /**< Top level root tag for QSF Maps */
+#define MAP_DEFINITION_TAG	"definition" /**< Second level container for defined objects 
+
+Attributes: qof_version - Taken from the QOF_OBJECT_VERSION macro in QOF,
+At the time of QSF development, QOF_OBJECT_VERSION is defined as 3. All
+QSF maps and QSF objects must use the same qof_version which in turn must
+match the QOF_OBJECT_VERSION for the QOF library in use by the calling process.
+
+No text content allowed.
+*/
+#define MAP_DEFINE_TAG	"define" /**< defines each object supported by this QSF map 
+
+Attributes: e_type Copied directly from the QofObject definition.
+Content: The full QofObject description for the defined QOF object.
+*/
+#define MAP_DEFAULT_TAG	"default"  /**< User editable defaults for data not available within the available QSF objects.
+
+Some defaults will relate to how to format descriptive dates, whether discount should be considered,
+which account to use for certain QSF data from applications that don't use accounts.
+
+Some defaults are pre-defined and cannot be over-written:
+- qsf_time_now
+- qsf_time_string
+
+Attributes (All are mandatory): 
+
+\a name The text name for this default. Certain pre-defined defaults exist but user- or map-defined defaults can have any unique text name. Spaces are \b NOT allowed, use undersccores instead. The value of name must not duplicate any existing default, define, object or parameter unless the special type, enum, is used.
+
+\a type QOF_TYPE - must be one of the recognised QOF data types for the
+qof_version in use or the special type, enum.
+
+\a value Text representation of the required value. For numeric, use the format
+[0-9]?/[0-9]?
+
+\attention Using boolean defaults
+A boolean default is not output in the QSF directly, instead the value is
+used in the calculations to modify certain values. If the boolean default
+is set to true, the if statement containing the boolean name will be evaluated. If the boolean
+default is set to false, the corresponding else will be evaluted. Make sure your
+calculations contain an appropriate else statement so that the boolean value
+can be adjusted without invalidating the map!
+
+\attention Using enumerators
+- enum types are the only defaults that are allowed to use the same name value more than once. 
+- enum types are used to increase the readability of a QSF map.
+- The enum name acts to group the enum values together - in a similar fashion to radio buttons in HTML forms. 
+- enum types are used only where the QOF object itself uses an enum type. 
+
+e.g. the tax_included enum type allows maps to use the full name of the enum value GNC_TAXINCLUDED_YES, instead of the cryptic digit value, 1.
+
+*/
+#define MAP_OBJECT_TAG	"object" /**< Contains all the calculations to make one object from others.
+
+Note that creating an object for the import application can involve using data from more than one
+QSF object, as well as defaults and lookups in the import application itself. Conditionals, simple
+arithmetic and date/time formatting options are also available.
+*/
+#define MAP_CALCULATE_TAG	"calculate" /**< One calculation for every parameter that needs to be set.
+
+QSF follows the same rule as qof_book_merge. Only if a getter and a setter function are defined for
+a parameter is it available to QSF. If a ::QofAccessFunc and ::QofSetterFunc are both defined
+for any QofObject parameter, that parameter \b MUST be calculated in any map that defines that object.
+*/
+#define MAP_QOF_VERSION	"qof_version" /**< This is the QOF_OBJECT_VERSION >From QOF.
+
+QSF maps may need to be updated if QOF itself is upgraded. This setting is coded into QOF and 
+maps for one version cannot necessarily be used by other versions. At the first release of QSF,
+QOF_OBJECT_VERSION = 3.
+*/
+#define MAP_NAME_ATTR	"name" /**< The name of the default setting.
+
+Use this name to refer to the value of this default in the map calculations.
+
+Make sure that the type of this default matches the type of the parameter being set by the 
+parent calculation!
+*/
+#define MAP_TYPE_ATTR	"type" /**< QSF will NOT convert between QOF types.
+
+QSF will allow a conditional to use a parameter of one type to determine the value from a parameter of
+another type, but the final value assigned \b MUST be of the same type as the parent calculation.
+*/
+#define MAP_VALUE_ATTR	"value" /**< The value of the tag, used in defaults and calculations.
+
+The value of a default is a string representation of the value to be inserted into
+the calculation where the default is used.
+
+The value of a calculation is the name of the parameter that will be set by that calculation.
+*/
+#define MAP_E_TYPE	"e_type" /**< Validates the objects defined in the map 
+
+The e_type will be used to match incoming QSF objects with the relevant QSF map.
+The value of the e_type must be the value of the e_type for that object in the
+originating QOF application. The define tag must contain the value of the description
+of the same object in the same originating QOF application.
+*/
+#define MAP_ENUM_TYPE "enum"
+#define QSF_XSD_TIME	"%Y-%m-%dT%H:%M:%SZ" /**< xsd:dateTime format in coordinated universal time, UTC.
+
+You can reproduce the string from the GNU/Linux command line using the date utility: 
+
+date -u +%Y-%m-%dT%H:%M:%SZ
+
+2004-12-12T23:39:11Z
+
+The datestring must be timezone independent and include all specified fields.
+
+Remember to use gmtime() NOT localtime()!. From the command line, use the -u switch with the 
+date command: date -u
+
+To generate a timestamp based on a real time, use the qsf_time_now and qsf_time_string defaults.
+
+qsf_time_now : Format: QOF_TYPE_DATE. The current time taken from the moment the default
+is read into a QSF object at runtime.
+
+qsf_time_string : Format: QOF_TYPE_STRING. The current timestamp taken from the moment the
+default is read into a QSF object at runtime. This form is used when the output parameter 
+needs a formatted date string, not an actual date object. The format is determined by the 
+optional format attribute of the set tag which takes the same operators as the GNU C Library 
+for strftime() and output may therefore be dependent on the locale of the calling process - 
+\b take \b care. Default value is %F, used when qsf_time_string is set without the format
+attribute.
+
+Both defaults use UTC.
+
+*/
+#define QSF_XML_BOOLEAN_TEST "true" /**< needs to be lowercase for XML validation */
+
+/** \brief A specific boolean default for this map.
+*/
+#define QSF_BOOLEAN_DEFAULT "boolean"
+
+#define QSF_CONDITIONAL "if"  /**< child of calculate.
+
+Conditionals can reference objects as if within the original application. In operation,
+the map is overlaid across both sets of defined objects, an import object in the source 
+application and an output object for the destination object. The current import and output 
+QSF objects are therefore always available to the map. 
+Conditionals can reference parameter as well as object values.
+*/
+#define QSF_CONDITIONAL_SET "set" /**< Assignment statement
+
+Map assignments can use the native values within the output object. The output object
+must support setting the relevant parameter using the value exactly as given in the map 
+because the relevant set() function will be called using this value. This may reduce the 
+readability of the map but the relevant application could also be modified to support a more 
+readable set function.
+*/
+#define QSF_CONDITIONAL_ELSE "else" /**< Alternative
+
+if(){} else{} is also supported. Nesting of conditionals causes problems for validating the
+final map against any sensible XML Schema and a map that doesn't validate will be rejected. 
+When editing conditionals in a QSF map, ALWAYS validate the map using xmllint. If necessary, 
+define a variable at the foot of the definitions block, using a similar syntax to a default, 
+then use that variable in another conditional
+
+\a variable \a name="my_rate" \a type="numeric" \a value="0/1"
+
+The syntax for xmllint is:
+
+\a xmllint \a --schema \a &lt;schema file&gt; \a &lt;qsf-file&gt;
+
+Use the qsf-object.xsd.xml schema for objects and qsf-map.xsd.xml for map files.
+
+e.g. xmllint --schema qsf-object.xsd.xml --noout qof-qsf.xml
+
+*/
+#define QSF_OPTION "option" /**< enum operator
+
+Not implemented yet - may need to change once work starts.
+Theoretically, option will specify when an enumerator value is in use -
+it is quite possible that it will be unnecessary.
+*/
+
+#define QSF_FORMATTING_OPTION "format" /**< How to format dates/times
+
+When the QSF map uses a date/time value as a \b string, the formatting
+can be adjusted to personal preference. \a format will only be evaluated
+if the calculated parameter is a QOF_TYPE_STRING - any format attributes
+on other data types will be ignored.
+ */
+
+#define QSF_OBJECT_SCHEMA "qsf-object.xsd.xml" /**< Name of the QSF Object Schema. */
+#define QSF_MAP_SCHEMA "qsf-map.xsd.xml" /**< Name of the QSF Map Schema. */
+/** \brief QSF Parameters
+
+This struct is a catch-all for all parameters required
+for various stages of the process. There are lots of elements
+here that will finally be removed.
+*/
+typedef struct qsf_metadata
+{
+	qsf_type file_type; /**< what type of file is being handled */
+	qsf_objects *object_set; /**< current object set for qsf_object_list. */
+	int count; /**< sequential counter for each object in the book */
+	GList *qsf_object_list; /**< list of qsf_objects */
+	GSList *qsf_sequence; /**< Parameter list sorted into QSF order */
+	GHashTable *qsf_parameter_hash; /**< Hashtable of parameters for each object */
+	GHashTable *qsf_calculate_hash, *qsf_default_hash, *qsf_define_hash;
+	GSList *supported_types; /**< The partial list of QOF types currently supported, in QSF order. */
+	xmlDocPtr input_doc, output_doc; /**< Pointers to the input and output xml document(s). */
+	/** \todo Review the list of xml nodes in qsf_param and rationalise. */
+	xmlNodePtr child_node, cur_node, param_node, output_node, output_root, book_node, lister;
+	xmlNsPtr qsf_ns, map_ns;/**< Separate namespaces for QSF objects and QSF maps. */
+	const char *qof_type; /**< Holds details of the QOF_TYPE */
+	QofIdType qof_obj_type;	/**< current QofObject type (e_type) for the parameters. */
+	QofEntity *qsf_ent; /**< Current entity in the book. */
+	QofBackend *be; /**< the current QofBackend for this operation. */
+	QofBook *book;	/**< the current QofBook.
+
+		Theoretically, QSF can handle multiple QofBooks - currently limited to 1.
+	*/
+	int boolean_calculation_done; /**< simple trip once this boolean is complete. */
+	char *filepath; /**< Path to the QSF file. */
+}qsf_param;
+
+void qsf_free_params(qsf_param *params);
+
+
+/** \brief Validation metadata
+
+The validation is a separate parse with separate data.
+This may change but it currently saves workload.
+
+\todo Examine ways of making the Validation metadata
+into a sub-set of the main code, not an island on it's own.
+*/
+typedef struct qsf_validates
+{
+	QofBackendError error_state;
+	const char *object_path;
+	const char *map_path;
+	GHashTable *validation_table;
+	int valid_object_count;
+	int map_calculated_count;
+	int qof_registered_count;
+}qsf_validator;
+
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+	as well as in a block.
+*/
+int
+qsf_compare_tag_strings(const xmlChar *node_name, char *tag_name);
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+	as well as in a block.
+*/
+int
+qsf_strings_equal(const xmlChar *node_name, char *tag_name);
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+	as well as in a block.
+*/
+int
+qsf_is_element(xmlNodePtr a, xmlNsPtr ns, char *c);
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+	as well as in a block.
+*/
+int
+qsf_check_tag(qsf_param *params, char *qof_type);
+
+/** \brief Checks all incoming objects for QOF registration.
+
+Sums all existing objects in the QSF and counts the number of those
+objects that are also registered with QOF in the host application.
+*/
+void
+qsf_object_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid);
+
+void
+qsf_map_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid);
+
+void
+qsf_map_top_node_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params);
+
+void
+qsf_map_object_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params);
+
+xmlNodePtr
+qsf_add_object_tag(qsf_param *params, int count);
+
+/** \brief Compares an xmlDoc in memory against the schema file.
+
+ at params	schema_dir  set at compile time to $prefix/share/qsf/
+ at params schema_filename Either the QSF Object Schema or the QSF Map Schema.
+ at params doc 	The xmlDoc read from the file using libxml2.
+
+Ensure that you call the right schema_filename for the doc in question!
+
+Incorrect validation will result in output to the terminal window.
+
+ at return TRUE if the doc validates against the assigned schema, otherwise FALSE.
+*/
+gboolean
+qsf_is_valid(const char *schema_dir, const char* schema_filename, xmlDocPtr doc);
+
+/** \brief Validate a QSF file and identify a suitable QSF map
+
+ at param	params	Pointer to qsf_param context
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the 
+corresponding function in other code.
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to find out if a suitable QSF
+map exists. Map files are accepted if all objects described in the QSF object
+file are defined in the QSF map.
+
+ at return TRUE if the file validates and a QSF map can be found,
+otherwise FALSE.
+*/
+gboolean is_qsf_object_be(qsf_param *params);
+
+/** \brief Validate a QSF file and determine type.
+
+ at param	params	Pointer to qsf_param context
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to see if it is registered
+with QOF within the QOF environment of the calling process.
+
+Files that pass the test can be imported into the QOF appliction without the need
+for a QSF map.
+
+ at return TRUE if the file validates and all objects pass,
+otherwise FALSE.
+*/
+gboolean is_our_qsf_object_be(qsf_param *params);
+
+/** \brief Validate a QSF map file.
+
+ at param	params	Pointer to qsf_param context
+
+The file is validated aginst the QSF map schema, qsf-map.xsd.xsml. This
+function is called by ::is_qsf_object. If called directly, the map file
+is validated and closed with a QofBackend error. QSF maps do not contain
+user data and are used to import QSF object files.
+
+ at return TRUE if the map validates, otherwise FALSE.
+*/
+gboolean is_qsf_map_be(qsf_param *params);
+
+/** \brief Validate a QSF file and a selected QSF map
+
+ at param	map_path	Absolute or relative path to the selected QSF map file
+ at param	params	Pointer to qsf_param context
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to find out if the supplied QSF
+map is suitable. Map files are accepted if all objects described in the QSF object
+file are defined in the QSF map.
+
+ at return TRUE if the file validates and the supplied QSF map is usable,
+otherwise FALSE.
+*/
+gboolean is_qsf_object_with_map_be(char *map_path, qsf_param *params);
+
+/** \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.
+
+Checks available QSF maps for match. Only succeeds if a suitable map exists.
+
+*/
+gboolean
+load_qsf_object(QofBook *book, const char *fullpath, qsf_param *params);
+
+/**	\brief QOF processing routine.
+
+To replace the qsf_test_main routine with genuine calls to QofSession and 
+qof_book_merge. Accepts QSF_GNC_OBJECT.
+
+*/
+gboolean
+load_our_qsf_object(QofBook *book, const char *fullpath, qsf_param *params);
+
+/** \brief Export a QofBook as QSF
+
+ 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.
+*/
+xmlDocPtr
+qofbook_to_qsf(QofBook *book);
+
+void qsf_book_node_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params);
+
+void qsf_object_commitCB(gpointer key, gpointer value, gpointer data);
+
+void qsf_param_init(qsf_param *params);
+
+
+/** \brief map callback
+
+Investigate ways to get the map callback to do both
+the map and the validation tasks.
+**/
+typedef void (* qsf_nodeCB)(xmlNodePtr, xmlNsPtr, qsf_param*);
+
+/** \brief validator callback
+
+\todo The need for separate metadata means a separate callback typedef
+	is needed for the validator, but this should be fixed to only need one.
+*/
+typedef void (* qsf_validCB)(xmlNodePtr, xmlNsPtr, qsf_validator*);
+
+
+/** \brief One iterator, two typedefs
+
+\todo resolve the two callbacks in ::qsf_node_iterate into one.
+*/
+struct qsf_node_iterate {
+	qsf_nodeCB *fcn;
+	qsf_validCB *v_fcn;
+	xmlNsPtr ns;
+};
+
+/** \brief Validate a QSF file and identify a suitable QSF map
+
+ at param	path	Absolute or relative path to the file to be validated.
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the 
+corresponding function in other code.
+	
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to find out if a suitable QSF
+map exists. Map files are accepted if all objects described in the QSF object
+file are defined in the QSF map.
+
+ at return TRUE if the file validates and a QSF map can be found,
+otherwise FALSE.
+*/
+gboolean is_qsf_object(const char *path);
+
+/** \brief Validate a QSF file and determine type.
+
+ at param	path	Absolute or relative path to the file to be validated
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the 
+corresponding function in other code.
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to see if it is registered
+with QOF within the QOF environment of the calling process.
+
+Files that pass the test can be imported into the QOF appliction without the need
+for a QSF map.
+
+ at return TRUE if the file validates and all objects pass,
+otherwise FALSE.
+*/
+gboolean is_our_qsf_object(const char *path);
+
+/** \brief Validate a QSF map file.
+
+ at param	path	Absolute or relative path to the file to be validated
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the 
+corresponding function in other code.
+
+The file is validated aginst the QSF map schema, qsf-map.xsd.xsml. This
+function is called by ::is_qsf_object. If called directly, the map file
+is validated and closed with a QofBackend error. QSF maps do not contain
+user data and are used to import QSF object files.
+
+ at return TRUE if the map validates, otherwise FALSE.
+*/
+gboolean is_qsf_map(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.
+
+ at return NULL on error, otherwise a pointer to the QofBook. Use
+	the qof_book_merge API to merge the new data into the current
+	QofBook. 
+*/
+void
+qsf_file_type (QofBackend *be, QofBook *book);
+
+void qsf_provider_init(void);
+
+void
+qsf_valid_foreach(xmlNodePtr parent, qsf_validCB cb,
+	struct qsf_node_iterate *iter, qsf_validator *valid);
+
+void
+qsf_node_foreach(xmlNodePtr parent, qsf_nodeCB cb,
+	struct qsf_node_iterate *iter, qsf_param *params);
+
+/** \brief Loads the QSF into a QofSession, ready to merge.
+
+Loads a QSF object file containing only GnuCash objects
+into a second QofSession.
+
+ at param first_session A QofSession pointer to the original session. This
+	will become the target of the subsequent qof_book_merge.
+ at param path	Absolute or relative path to the file to be loaded
+
+ at return ERR_BACKEND_NO_ERR == 0 on success, otherwise the QofBackendError
+	set by the QSFBackend.
+	
+\todo Build the qof_book_merge code onto this function if session loads
+	properly.
+*/
+QofBackendError qof_session_load_our_qsf_object(QofSession *first_session, const char *path);
+
+/** \brief Placeholder so far.
+
+
+\todo Determine the map to use and convert the QOF objects
+
+Much of the map code is written but there is still work to do.
+*/
+QofBackendError qof_session_load_qsf_object(QofSession *first_session, const char *path);
+	
+void qsf_destroy_backend (QofBackend *be);
+
+/** \brief Backend routine to open a file to write
+
+Calls write_qsf_from_book to convert the QofBook to QSF XML.
+*/
+void qsf_write_file(QofBackend *be, QofBook *book);
+
+/** \brief Create a new QSF backend.
+*/
+QofBackend* qsf_backend_new(void);
+	
+/** @} */
+/** @} */
+
+#endif /* QSF_XML_H */
--- /dev/null
+++ src/backend/qsf/pilot-qsf-GnuCashInvoice.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- this map does NOT post invoices automatically -->
+<!-- maps use the same sequence of parameter types as other QSF -->
+<!-- Trans:desc can be set by expenses or datebook -->
+<qsf-map
+xmlns="urn:qof-qsf-map">
+<definition qof_version="3">
+  <define e_type="qof-expenses">Pilot-link QOF expenses</define>
+  <define e_type="qof-datebook">Pilot-link QOF datebook</define>
+  <define e_type="qof-address">Pilot-link QOF address</define>
+  <define e_type="gncInvoice">Invoice</define>
+  <define e_type="Trans">Transaction</define>
+  <define e_type="gncEntry">Order/Invoice/Bill Entry</define>
+  <default name="mileage_rate" type="numeric" value="28/100"/>
+  <default name="use_weekday_descriptor" type="boolean" value="true"/>
+  <default name="use_discount" type="boolean" value="false"/>
+  <default name="tax_included" type="boolean" value="false"/>
+  <default name="post_account" type="string" value="Assets:Current Assets:CD account"/>
+  <default name="expenses_account" type="string" value="Income:Other Income"/>
+  <default name="datebook_account" type="string" value="Income:Locum Income"/>
+  <default name="tax_included" type="enum" value="1">GNC_TAXINCLUDED_YES</default>
+  <default name="tax_included" type="enum" value="2">GNC_TAXINCLUDED_NO</default>
+  <default name="tax_included" type="enum" value="3">GNC_TAXINCLUDED_USEGLOBAL</default>
+  <default name="amount_type" type="enum" value="1">GNC_AMT_TYPE_VALUE</default>
+  <default name="amount_type" type="enum" value="2">GNC_AMT_TYPE_PERCENT</default>
+</definition>
+<object type="gncEntry">
+  <calculate type="string" value="desc">
+    <if boolean="use_weekday_descriptor">
+      <set format="%A">expense_date</set>
+    </if>
+    <else type="qof_expenses">
+      <set>expense_vendor</set>
+    </else>
+  </calculate>
+  <calculate type="string" value="action">
+    <if type="qof-expenses">
+      <set>Material</set>
+    </if>
+    <else type="qof-datebook">
+      <set>Hours</set>
+    </else>
+  </calculate>
+  <calculate type="string" value="notes">
+    <set object="qof-expenses">expense_note</set>
+  </calculate>
+  <calculate type="guid" value="bill-to">
+    <if type="qof-expenses">
+      <set option="qsf_lookup_string">expenses_account</set>
+    </if>
+    <else>
+      <set option="qsf_lookup_string">datebook_account</set>
+    </else>
+  </calculate>
+  <calculate type="boolean" value="invoice-taxable"/>
+  <calculate type="boolean" value="bill-taxable"/>
+  <calculate type="boolean" value="billable?"/>
+  <calculate type="boolean" value="bill-tax-included"/>
+  <calculate type="boolean" value="invoice-tax-included">
+    <set>tax_included</set>
+  </calculate>
+  <calculate type="numeric" value="iprice">
+    <if type="string" value="expense_type">
+      <equals type="string" value="etMileage">
+        <set>mileage_rate</set>
+      </equals>
+    </if>
+  </calculate>
+  <calculate type="numeric" value="bprice"/>
+  <calculate type="numeric" value="qty">
+    <if string="expense_type">
+      <equals type="string" value="etMileage">
+        <set object="qof-expenses">expense_amount</set>
+      </equals>
+    </if>
+    <else type="string" value="expense_type">
+      <set>0/1</set>
+    </else>
+  </calculate>
+  <calculate type="numeric" value="invoice-discount">
+    <set>0/1</set>
+  </calculate>
+  <calculate type="date" value="date-entered">
+    <set>qsf_time_now</set>
+  </calculate>
+  <calculate type="date" value="date">
+    <set>qsf_enquiry_date</set>
+  </calculate>
+  <calculate type="gint32" value="discount-type">
+    <set option="amount_type_enum">GNC_AMT_TYPE_PERCENT</set>
+  </calculate>
+  <calculate type="gint32" value="discount-method">
+    <set>2</set>
+  </calculate>
+  <calculate type="gint32" value="bill-payment-type"/>
+</object>
+<object type="Trans">
+  <calculate type="string" value="num"/>
+  <calculate type="string" value="desc"/>
+  <calculate type="date" value="date-entered"/>
+  <calculate type="date" value="date-posted"/>
+  <calculate type="date" value="date-due"/>
+  <calculate type="string" value="notes"/>
+</object>
+<object type="gncInvoice">
+  <calculate type="string" value="id"/>
+  <calculate type="string" value="billing_id"/>
+  <calculate type="string" value="notes"/>
+  <calculate type="guid" value="invoice_owner"/>
+  <calculate type="guid" value="account"/>
+  <calculate type="guid" value="posted_txn"/>
+  <calculate type="guid" value="posted_lot"/>
+  <calculate type="guid" value="terms"/>
+  <calculate type="guid" value="bill-to"/>
+  <calculate type="boolean" value="active">
+    <set>true</set>
+  </calculate>
+  <calculate type="date" value="date_opened">
+      <set>qsf_enquiry_date</set>
+  </calculate>
+  <calculate type="date" value="date_posted"/>
+</object>
+</qsf-map>
--- /dev/null
+++ src/backend/qsf/qsf-xml.c
@@ -0,0 +1,764 @@
+/***************************************************************************
+ *            qsf-xml.c
+ *
+ *  Fri Nov 26 19:29:47 2004
+ *  Copyright  2004-2005  Neil Williams  <linux at codehelp.co.uk>
+ *
+ ****************************************************************************/
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/** @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 "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);
+	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)
+{
+	return xmlStrcmp(node_name, (const xmlChar *)tag_name);
+}
+
+int
+qsf_strings_equal(const xmlChar *node_name, char *tag_name)
+{
+	if(0 == qsf_compare_tag_strings(node_name, tag_name)) { return 1; }
+	return 0;
+}
+
+int
+qsf_is_element(xmlNodePtr a, xmlNsPtr ns, char *c)
+{
+	g_return_val_if_fail(a != NULL, 0);
+	g_return_val_if_fail(ns != NULL, 0);
+	g_return_val_if_fail(c != NULL, 0);
+	if ((a->ns == ns) && (a->type == XML_ELEMENT_NODE) &&
+		qsf_strings_equal(a->name, c)) { return 1; }
+	return 0;
+}
+
+int
+qsf_check_tag(qsf_param *params, char *qof_type)
+{
+	return qsf_is_element(params->child_node, params->qsf_ns, qof_type);
+}
+
+gboolean
+qsf_is_valid(const char *schema_dir, const char* schema_filename, xmlDocPtr doc)
+{
+	xmlSchemaParserCtxtPtr qsf_schema_file;
+	xmlSchemaPtr qsf_schema;
+	xmlSchemaValidCtxtPtr qsf_context;
+	gchar *schema_path;
+	gint result;
+
+	if(doc == NULL) return FALSE;
+	schema_path = g_strdup_printf("%s/%s", schema_dir, schema_filename);
+	qsf_schema_file = xmlSchemaNewParserCtxt(schema_path);
+	qsf_schema = xmlSchemaParse(qsf_schema_file);
+	qsf_context = xmlSchemaNewValidCtxt(qsf_schema);
+	result = xmlSchemaValidateDoc(qsf_context, doc);
+	xmlSchemaFreeParserCtxt(qsf_schema_file);
+	xmlSchemaFreeValidCtxt(qsf_context);
+	xmlSchemaFree(qsf_schema);
+	if(result == 0) { return TRUE; }
+	return FALSE;
+}
+
+void
+qsf_valid_foreach(xmlNodePtr parent, qsf_validCB cb,
+	struct qsf_node_iterate *iter, qsf_validator *valid)
+{
+	xmlNodePtr cur_node;
+
+	iter->v_fcn = &cb;
+	for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+	{
+		cb(cur_node, iter->ns, valid);
+	}
+}
+
+void
+qsf_node_foreach(xmlNodePtr parent, qsf_nodeCB cb,
+	struct qsf_node_iterate *iter, qsf_param *params)
+{
+	xmlNodePtr cur_node;
+
+	iter->fcn = &cb;
+	for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+	{
+		cb(cur_node, iter->ns, params);
+	}
+}
+
+void
+qsf_object_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid)
+{
+	xmlNodePtr cur_node;
+	xmlChar *object_declaration;
+
+	for(cur_node = child->children; cur_node != NULL;
+		cur_node = cur_node->next)
+	{
+		if(qsf_is_element(cur_node, ns, QSF_OBJECT_TAG)) {
+			object_declaration = xmlGetProp(cur_node, QSF_OBJECT_TYPE);
+			g_hash_table_insert(valid->validation_table, object_declaration, xmlNodeGetContent(cur_node));
+			if(TRUE == qof_class_is_registered((QofIdTypeConst) object_declaration))
+			{
+				valid->qof_registered_count++;
+			}
+		}
+	}
+}
+
+/** \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;
+	struct qsf_node_iterate iter;
+	xmlNodePtr object_root;
+	qsf_validator valid;
+	gint table_count;
+
+	g_return_val_if_fail((path != NULL),FALSE);
+	if(path == NULL) { return FALSE; }
+	doc = xmlParseFile(path);
+	if(doc == NULL)  { return FALSE; }
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) 
+	{
+		return FALSE;
+	}
+	object_root = xmlDocGetRootElement(doc);
+	valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+	valid.qof_registered_count = 0;
+	iter.ns = object_root->ns;
+	qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+	table_count = g_hash_table_size(valid.validation_table);
+	if(table_count == valid.qof_registered_count)
+	{
+		g_hash_table_destroy(valid.validation_table);
+		return TRUE;
+	}
+	g_hash_table_destroy(valid.validation_table);
+	return FALSE;
+}
+
+gboolean is_qsf_object(const char *path)
+{
+	xmlDocPtr doc;
+
+	g_return_val_if_fail((path != NULL),FALSE);
+	if(path == NULL) { return FALSE; }
+	doc = xmlParseFile(path);
+	if(doc == NULL) { return FALSE; }
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) { return FALSE; }
+	/** \todo implement a way of finding more than one map */
+	/** \todo set the map xmlDocPtr in params for later processing. */
+	return TRUE;
+}
+
+gboolean is_our_qsf_object_be(qsf_param *params)
+{
+	xmlDocPtr doc;
+	struct qsf_node_iterate iter;
+	xmlNodePtr object_root;
+	qsf_validator valid;
+	gint table_count;
+	char *path;
+
+	g_return_val_if_fail((params != NULL),FALSE);
+	path = g_strdup(params->filepath);
+	if(path == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+		return FALSE;
+	}
+	if(params->file_type != QSF_UNDEF) { 
+		return FALSE; 
+	}
+	doc = xmlParseFile(path);
+	if(doc == NULL)  {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) 
+	{
+		qof_backend_set_error(params->be, ERR_QSF_INVALID_OBJ);
+		return FALSE;
+	}
+	params->file_type = IS_QSF_OBJ;
+	object_root = xmlDocGetRootElement(doc);
+	valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+	valid.qof_registered_count = 0;
+	iter.ns = object_root->ns;
+	qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+	table_count = g_hash_table_size(valid.validation_table);
+	if(table_count == valid.qof_registered_count)
+	{
+		g_hash_table_destroy(valid.validation_table);
+		qof_backend_set_error(params->be, ERR_BACKEND_NO_ERR);
+		return TRUE;
+	}
+	g_hash_table_destroy(valid.validation_table);
+	qof_backend_set_error(params->be, ERR_QSF_NO_MAP);
+	return FALSE;
+}
+
+gboolean is_qsf_object_be(qsf_param *params)
+{
+	xmlDocPtr doc;
+	char *path;
+
+	g_return_val_if_fail((params != NULL),FALSE);
+	path = g_strdup(params->filepath);
+	if(path == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+		return FALSE;
+	}
+	/* skip validation if is_our_qsf_object has already been called. */
+	if(ERR_QSF_INVALID_OBJ == qof_backend_get_error(params->be)) { return FALSE; }
+	if(params->file_type == QSF_UNDEF)
+	{
+		doc = xmlParseFile(path);
+		if(doc == NULL) {
+			qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+			return FALSE;
+		}
+		if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc))
+		{
+			qof_backend_set_error(params->be, ERR_QSF_INVALID_OBJ);
+			return FALSE;
+		}
+	}
+	/** \todo implement a way of finding more than one map */
+	/** \todo set the map xmlDocPtr in params for later processing. */
+	return is_qsf_object_with_map_be("pilot-qsf-GnuCashInvoice.xml", params);
+}
+
+static void
+qsf_supported_data_types(gpointer type, gpointer user_data)
+{
+	qsf_param *params;
+
+	g_return_if_fail(user_data != NULL);
+	g_return_if_fail(type != NULL);
+	params = (qsf_param*) user_data;
+	if(qsf_is_element(params->param_node, params->qsf_ns, (char*)type))
+	{
+		g_hash_table_insert(params->qsf_parameter_hash,
+			xmlGetProp(params->param_node, QSF_OBJECT_TYPE), params->param_node);
+	}
+}
+
+static void
+qsf_parameter_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params)
+{
+
+	params->param_node = child;
+	g_slist_foreach(params->supported_types, qsf_supported_data_types, params);
+}
+
+
+/** \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.
+*/
+static void
+qsf_object_node_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params)
+{
+	struct qsf_node_iterate iter;
+	qsf_objects *object_set;
+	char *tail, *object_count_s;
+	int c;
+
+	g_return_if_fail(child != NULL);
+	g_return_if_fail(qsf_ns != NULL);
+	params->qsf_ns = qsf_ns;
+	if(qsf_is_element(child, qsf_ns, QSF_OBJECT_TAG)) {
+		params->qsf_parameter_hash = NULL;
+		object_set = g_new(qsf_objects, 1);
+		params->object_set = object_set;
+		object_set->parameters = g_hash_table_new(g_str_hash, g_str_equal);
+		object_set->object_type = g_strdup(xmlGetProp(child, QSF_OBJECT_TYPE));
+		object_count_s = g_strdup(xmlGetProp(child, QSF_OBJECT_COUNT));
+		c = (int)strtol(object_count_s, &tail, 0);
+		g_free(object_count_s);
+		params->qsf_object_list = g_list_prepend(params->qsf_object_list, object_set);
+		iter.ns = qsf_ns;
+		params->qsf_parameter_hash = object_set->parameters;
+		qsf_node_foreach(child, qsf_parameter_handler, &iter, params);
+	}
+}
+
+/** \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)
+{
+	char *book_count_s, *tail;
+	int book_count;
+	xmlNodePtr child_node;
+	struct qsf_node_iterate iter;
+	gchar *buffer;
+	GUID book_guid;
+
+	if(qsf_is_element(child, ns, QSF_BOOK_TAG)) {
+		book_count_s = xmlGetProp(child,QSF_BOOK_COUNT);
+		if(book_count_s) {
+			book_count = (int)strtol(book_count_s, &tail, 0);
+			/* More than one book not currently supported. */
+			g_return_if_fail(book_count == 1);
+		}
+		iter.ns = ns;
+		qsf_node_foreach(child, qsf_object_node_handler, &iter, params);
+	}
+	for(child_node = child->children; child_node != NULL;
+		child_node = child_node->next)
+		{
+		if(qsf_is_element(child_node, ns, QSF_BOOK_GUID)) {
+			buffer = g_strdup(xmlNodeGetContent(child_node));
+			g_return_if_fail(TRUE == string_to_guid(buffer, &book_guid));
+			qof_entity_set_guid((QofEntity*)params->book, &book_guid);
+			g_free(buffer);
+		}
+		if(qsf_is_element(child_node, ns, QSF_OBJECT_TAG)) {
+			iter.ns = ns;
+			qsf_node_foreach(child_node, qsf_object_node_handler, &iter, params);
+		}
+	}
+}
+
+/** \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);
+			}
+		}
+*/
+}
--- /dev/null
+++ src/backend/qsf/Makefile.am
@@ -0,0 +1,42 @@
+SUBDIRS = . 
+
+pkglib_LTLIBRARIES = libqof-backend-qsf.la
+
+AM_CFLAGS = \
+  -I.. -I../.. \
+  -I${top_srcdir}/src/backend \
+  -I${top_srcdir}/src/engine \
+  -I${top_srcdir}/src/gnc-module \
+  ${LIBXML2_CFLAGS} \
+  ${GLIB_CFLAGS}
+
+libqof_backend_qsf_la_SOURCES = \
+	qsf-backend.c \
+	qsf-xml-map.c \
+        qsf-xml.c
+
+noinst_HEADERS = \
+  qsf-xml.h
+
+LIBADD = \
+   ${GLIB_LIBS} \
+   ${LIBXML2_LIBS}
+
+qsfschemadir = $(QSF_SCHEMA_DIR)
+qsfschema_DATA = \
+        qsf-object.xsd.xml \
+        qsf-map.xsd.xml \
+        pilot-qsf-GnuCashInvoice.xml
+
+EXTRA_DIST = \
+	$(qsfschema_DATA) \
+	qsf-dir.h.in
+
+qsf-dir.h: qsf-dir.h.in
+	rm -f $@.tmp
+	sed < $< > $@.tmp \
+	        -e 's:@-QSF_SCHEMA_DIR-@:${QSF_SCHEMA_DIR}:g'
+	mv $@.tmp $@
+
+BUILT_SOURCES = qsf-dir.h
+
--- /dev/null
+++ src/backend/qsf/qsf-dir.h.in
@@ -0,0 +1,25 @@
+/***************************************************************************
+ *            qsf-xml.h.in
+ *
+ *  Mon Dec 20 20:27:48 2004
+ *  Copyright  2004  Neil Williams
+ *  linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define QSF_SCHEMA_DIR "@-QSF_SCHEMA_DIR-@"
--- /dev/null
+++ src/backend/qsf/qsf-map.xsd.xml
@@ -0,0 +1,114 @@
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+targetNamespace="urn:qof-qsf-map" xmlns="urn:qof-qsf-map">
+<xsd:annotation>
+    <xsd:documentation xml:lang="en">
+	Query Object Framework Serialization Format (QSF)
+	Copyright 2004 Neil Williams linux at codehelp.co.uk
+	QSF is part of QOF.
+	QOF is free software; you can redistribute it and/or modify it
+	under the terms of the GNU General Public License as published by the
+	Free Software Foundation; either version 2 of the License, or (at your
+	option) any later version.
+	This program is distributed in the hope that it will be useful, but
+	WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+	General Public License for more details.
+	You should have received a copy of the GNU General Public License along
+	with this program; if not, write to the Free Software Foundation, Inc., 59
+	Temple	Place, Suite 330, Boston, MA 02111-1307 USA
+    </xsd:documentation>
+  </xsd:annotation>
+<xsd:element name="qsf-map" 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: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:sequence>
+ <xsd:attribute name="qof_version" type="xsd:positiveInteger"/>
+</xsd:complexType>
+<xsd:complexType name="qsfdefine">
+ <xsd:simpleContent>
+   <xsd:extension base="xsd:string">
+     <xsd:attribute name="e_type" type="xsd:string"/>
+   </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="qsfdefault">
+ <xsd:simpleContent>
+   <xsd:extension base="xsd:string">
+     <xsd:attribute name="name" type="xsd:string"/>
+     <xsd:attribute name="type" type="xsd:string"/>
+     <xsd:attribute name="value" type="xsd:string"/>
+   </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="qsfvariable">
+ <xsd:simpleContent>
+   <xsd:extension base="xsd:string">
+     <xsd:attribute name="name" type="xsd:string"/>
+     <xsd:attribute name="type" type="xsd:string"/>
+     <xsd:attribute name="value" type="xsd:string"/>
+   </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="mapobject">
+ <xsd:sequence>
+   <xsd:element name="calculate" 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:sequence>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+</xsd:complexType>
+<xsd:complexType name="qsf_set">
+ <xsd:simpleContent>
+   <xsd:extension base="xsd:string">
+     <xsd:attribute name="option" type="xsd:string" use="optional"/>
+     <xsd:attribute name="format" type="xsd:string" use="optional"/>
+     <xsd:attribute name="object" type="xsd:string" use="optional"/>
+   </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="qsf_if">
+ <xsd:sequence>
+   <xsd:element name="set" type="qsf_set" minOccurs="0" maxOccurs="1"/>
+   <xsd:element name="equals" type="qsf_equal" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="boolean" type="xsd:string" use="optional"/>
+ <xsd:attribute name="type" type="xsd:string" use="optional"/>
+ <xsd:attribute name="value" type="xsd:string" use="optional"/>
+ <xsd:attribute name="string" type="xsd:string" use="optional"/>
+</xsd:complexType>
+<xsd:complexType name="qsf_else">
+ <xsd:sequence>
+   <xsd:element name="set" type="qsf_set" minOccurs="0" maxOccurs="1"/>
+   <xsd:element name="equals" 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:sequence>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+</xsd:complexType>
+
+</xsd:schema>
--- /dev/null
+++ src/backend/qsf/qsf-backend.c
@@ -0,0 +1,428 @@
+/***************************************************************************
+ *            qsf-backend.c
+ *
+ *  Sat Jan  1 15:07:14 2005
+ *  Copyright  2005  Neil Williams
+ *  linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+ /** @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"
+
+//static short module = MOD_BACKEND;
+
+/** \brief QSF wrapper for QofBackend
+
+This makes a qsf_param struct available to the backend.
+*/
+struct QSFBackend_s 
+{
+	QofBackend be;
+	qsf_param *params;
+	char *fullpath;
+};
+
+typedef struct QSFBackend_s QSFBackend;
+
+/** \brief Backend init routine.
+
+*/
+void
+qsf_param_init(qsf_param *params)
+{
+	Timespec *qsf_ts;
+	gchar qsf_time_string[QSF_DATE_LENGTH];
+	gchar qsf_enquiry_date[QSF_DATE_LENGTH];
+	gchar qsf_time_match[QSF_DATE_LENGTH];
+	gchar qsf_time_now[QSF_DATE_LENGTH];
+	time_t qsf_time_now_t;
+	gchar *qsf_time_precision;
+
+	g_return_if_fail(params != NULL);
+	params->count = 1;
+	params->supported_types = NULL;
+	params->file_type = QSF_UNDEF;
+	params->qsf_ns = NULL;
+	params->output_doc = NULL;
+	params->output_node = NULL;
+	params->lister = NULL;
+	params->map_ns = NULL;
+	params->qsf_object_list = NULL;
+	params->qsf_parameter_hash = g_hash_table_new(g_str_hash, g_str_equal);
+	params->qsf_default_hash = g_hash_table_new(g_str_hash, g_str_equal);
+	params->qsf_define_hash = g_hash_table_new(g_str_hash, g_str_equal);
+	params->qsf_calculate_hash = g_hash_table_new(g_str_hash, g_str_equal);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_STRING);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_GUID);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_BOOLEAN);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_NUMERIC);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_DATE);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_INT32);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_INT64);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_DOUBLE);
+	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_CHAR);
+/*	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_KVP);*/
+	qsf_time_precision = "%j";
+	qsf_time_now_t = time(NULL);
+	qsf_ts = g_new(Timespec, 1);
+	timespecFromTime_t(qsf_ts, qsf_time_now_t);
+	strftime(qsf_enquiry_date, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(&qsf_time_now_t));
+	strftime(qsf_time_match, QSF_DATE_LENGTH, qsf_time_precision, gmtime(&qsf_time_now_t));
+	strftime(qsf_time_string, QSF_DATE_LENGTH, "%F", gmtime(&qsf_time_now_t));
+	strftime(qsf_time_now, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(&qsf_time_now_t));
+	g_hash_table_insert(params->qsf_default_hash, "qsf_enquiry_date", qsf_enquiry_date);
+	g_hash_table_insert(params->qsf_default_hash, "qsf_time_now", &qsf_time_now_t);
+	g_hash_table_insert(params->qsf_default_hash, "qsf_time_string", qsf_time_string);
+}
+
+/** \brief GnuCash does LOTS of filesystem work, QSF is going to leave most of it to libxml2. :-)
+
+Just strip the file: from the start of the book_path URL. Locks and file
+creation are not implemented.
+*/
+static void
+qsf_session_begin(QofBackend *be, QofSession *session, const char *book_path,
+                   gboolean ignore_lock, gboolean create_if_nonexistent)
+{
+	QSFBackend *qsf_be;
+	char *p, *path;
+	
+	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);
+	
+	p = strchr (book_path, ':');
+	if (p) {
+		path = g_strdup (book_path);
+		if (!g_strncasecmp(path, "file:", 5)) {
+			p = g_new(char, strlen(path) - 5 + 1);
+			strcpy(p, path + 5);
+		}
+		qsf_be->fullpath = g_strdup(p);
+		g_free (path);
+	}
+	else {
+		qsf_be->fullpath = g_strdup(book_path);
+	}
+	qof_backend_set_error(be, ERR_BACKEND_NO_ERR);
+}
+
+/** \brief Clean up the backend and the libxml2 parser. */
+static void
+qsf_session_end( QofBackend *be)
+{
+	QSFBackend *qsf_be;
+	
+	qsf_be = (QSFBackend*)be;
+	g_return_if_fail(qsf_be != NULL);
+
+	qsf_free_params(qsf_be->params);
+	g_free(qsf_be->fullpath);
+	qsf_be->fullpath = NULL;
+	xmlCleanupParser();
+}
+
+void qsf_destroy_backend (QofBackend *be)
+{
+	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)
+{
+	QofSession *qsf_session;
+	
+	first_session = qof_session_get_current_session();
+	qsf_session = qof_session_new();
+	qof_session_begin(qsf_session, path, FALSE, FALSE);
+	qof_session_load(qsf_session, NULL);
+	return qof_session_get_error(qsf_session);
+}
+
+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)
+{
+	QSFBackend *qsf_be;
+	qsf_param *params;
+	char *path;
+	gboolean result;
+
+	g_return_if_fail(be != NULL);
+	g_return_if_fail(book != NULL);
+	qsf_be = (QSFBackend*) be;
+	g_return_if_fail(qsf_be != NULL);
+	g_return_if_fail(qsf_be->fullpath != NULL);
+	g_return_if_fail(qsf_be->params != NULL);
+	params = qsf_be->params;
+	params->book = book;
+	path = g_strdup(qsf_be->fullpath);
+	params->filepath = g_strdup(path);
+	/* remove any previous error from the backend stack.*/
+	qof_backend_get_error(be);
+	result = is_our_qsf_object_be(params);
+	if(result == TRUE) {
+		params->file_type = OUR_QSF_OBJ;
+		result = load_our_qsf_object(book, path, params);
+		if(FALSE == result) {
+			qof_backend_set_error(be, ERR_FILEIO_PARSE_ERROR);
+		}
+		return;
+	} 
+	else if(is_qsf_object_be(params)) {
+		qsf_be->params->file_type = IS_QSF_OBJ;
+		if(FALSE == load_qsf_object(book, qsf_be->params->filepath, qsf_be->params)) {
+			qof_backend_set_error(be, ERR_FILEIO_PARSE_ERROR);
+		}
+	}
+	else if(is_qsf_map_be(params)) {
+		qsf_be->params->file_type = IS_QSF_MAP;
+		qof_backend_set_error(be, ERR_QSF_MAP_NOT_OBJ);
+	}
+}
+
+/** \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);
+
+1. object handler -> create new QofEntity 
+2. parameter handler -> param_setfcn	
+*/
+static gboolean qsfdoc_to_qofbook(xmlDocPtr doc, qsf_param *params)
+{
+	QofInstance *inst;
+	struct qsf_node_iterate iter;
+	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 */
+	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);
+		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);
+	}
+	return TRUE;
+}
+
+/** \brief Writes a QofBook to an open filehandle.
+
+ at param	out Pointer to a FILE filehandle.
+ at param	book Pointer to a QofBook to write as QSF XML.
+
+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
+write_qsf_from_book(FILE *out, 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(out, qsf_doc, 1);
+	fprintf(out, "\n");
+	xmlFreeDoc(qsf_doc);
+}
+
+/** \brief QofBackend routine to load from file - needs a map.
+*/
+gboolean
+load_qsf_object(QofBook *book, const char *fullpath, qsf_param *params)
+{
+	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);
+		return FALSE;
+	}
+	qsf_root = NULL;
+	qsf_root = xmlDocGetRootElement(params->input_doc);
+	params->qsf_ns = qsf_root->ns;
+	params->book = book;
+	/** \todo Create a QofBook from the QSF document <b>using a QSF map</b>.
+	
+	May seem strange, but I think we can do this by using the map handlers to
+	create the output_doc in memory as OUR_QSF_OBJ, then pass to the same routine!
+	*/
+
+	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)
+{
+	xmlNodePtr qsf_root;
+	
+	params->input_doc = xmlParseFile(fullpath);
+	if (params->input_doc == NULL) {
+		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+		return FALSE;
+	}
+	qsf_root = NULL;
+	qsf_root = xmlDocGetRootElement(params->input_doc);
+	params->qsf_ns = qsf_root->ns;
+	return qsfdoc_to_qofbook(params->input_doc, params);
+}
+
+QofBackend*
+qsf_backend_new(void)
+{
+	QSFBackend *qsf_be;
+	QofBackend *be;
+	
+	qsf_be = g_new0(QSFBackend, 1);
+	be = (QofBackend*) qsf_be;
+	qof_backend_init(be);
+	qsf_be->params = g_new(qsf_param, 1);
+	qsf_be->params->be = be;
+	qsf_param_init(qsf_be->params);
+	qsf_be->be.session_begin = qsf_session_begin;
+
+	/** \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->compile_query = NULL;
+	be->free_query = NULL;
+	be->run_query = NULL;
+	be->counter = NULL;
+	
+	/* Is the QSF backend to be multi-user?? */
+	be->events_pending = NULL;
+	be->process_events = NULL;
+	
+	be->sync = qsf_write_file;
+	
+	qsf_be->fullpath = NULL;
+	
+	return be;
+}
+
+/** \brief The QOF method of loading each backend.
+
+QSF does not use a GnuCash module, it is loaded using the QOF
+method - QofBackendProvider.
+*/
+static void 
+qsf_provider_free (QofBackendProvider *prov)
+{
+	prov->provider_name = NULL;
+	prov->access_method = NULL;
+	g_free (prov);
+}
+
+/** \brief Describe this backend to the application. */
+void
+qsf_provider_init(void)
+{
+	QofBackendProvider *prov;
+	prov = g_new0 (QofBackendProvider, 1);
+	prov->provider_name = "QSF Backend Version 0.1";
+	prov->access_method = "file";
+	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.7
retrieving revision 1.25.4.8
diff -Lsrc/app-file/gnc-file.c -Lsrc/app-file/gnc-file.c -u -r1.25.4.7 -r1.25.4.8
--- src/app-file/gnc-file.c
+++ src/app-file/gnc-file.c
@@ -176,6 +176,56 @@
       gnc_error_dialog (parent, fmt, newfile);
       break;
 
+	/* QSF additions */
+	case ERR_QSF_INVALID_OBJ: {
+		fmt = _("Invalid QSF Object file!\n"
+			"The QSF object file\n%s\n failed to validate"
+			" against the QSF object schema.\nThe XML structure of the file"
+			" is either not well-formed or contains illegal data.");
+		gnc_error_dialog(parent, fmt, newfile);
+		break; 
+	}
+	case ERR_QSF_INVALID_MAP: {
+		fmt = _("Invalid QSF Map file!\n"
+			"The QSF map file\n%s\n failed to validate "
+			" against the QSF map schema.\nThe XML structure of the file"
+			" is either not well-formed or contains illegal data.");
+		gnc_error_dialog(parent, fmt, newfile);
+		break; 
+	}
+	case ERR_QSF_BAD_QOF_VERSION: {
+		fmt = _("The QSF Map file\n%s\nwas written for a different version of QOF\n"
+			"It may need to be modified to work with your current QOF installation.");
+		gnc_error_dialog(parent, fmt, newfile);
+		break; 
+	}
+	case ERR_QSF_BAD_MAP: {
+		fmt = _("The selected QSF map\n%s\ncontains unusable data."
+			"  This is usually because not all the required parameters for "
+			" the defined objects have calculations described in the map.");
+		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);
+		break; 
+	}
+	case ERR_QSF_WRONG_MAP: {
+		fmt = _("Wrong QSF map selected.\n"
+			"The selected map,\n%s\n validates but was written"
+			"for different QOF objects.\n The list of objects defined in "
+			"this map does not include all the objects described in"
+			"the current QSF object file.");
+	  gnc_error_dialog(parent, fmt, newfile);
+	  break; 
+	}
+	case ERR_QSF_MAP_NOT_OBJ: {
+	  fmt = _("The selected file %s is a QSF map and cannot be "
+			"opened as a QSF object.");
+	  gnc_error_dialog(parent, fmt, newfile);
+	  break; 
+	}
     case ERR_FILEIO_FILE_BAD_READ:
       fmt = _("There was an error reading the file.\n"
               "Do you want to continue?");
Index: qofsession.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/qofsession.c,v
retrieving revision 1.2.4.7
retrieving revision 1.2.4.8
diff -Lsrc/engine/qofsession.c -Lsrc/engine/qofsession.c -u -r1.2.4.7 -r1.2.4.8
--- src/engine/qofsession.c
+++ src/engine/qofsession.c
@@ -310,6 +310,29 @@
 }
 
 /* ====================================================================== */
+/* 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 *dl_hand = dlopen (libso, RTLD_LAZY);
+	if (NULL == dl_hand)
+	{
+		const char * err_str = dlerror();
+		PERR("Can't load %s backend, %s\n", libso, err_str);
+		return;
+	}
+	void (*initfn) (void)  = dlsym (dl_hand, loadfn);
+	if (initfn)
+	{
+		 (*initfn)();
+	}
+	else
+	{
+		const char * err_str = dlerror();
+		PERR("Can't find %s:%s, %s\n", libso, loadfn, err_str);
+	}
+}
 
 #ifdef GNUCASH_MAJOR_VERSION 
 
@@ -331,8 +354,12 @@
 static void
 qof_session_load_backend(QofSession * session, char * backend_name)
 {
+  GSList *p;
+  GList *node;
+  QofBook *book;
   GNCModule  mod = 0;
   QofBackend    *(* be_new_func)(void);
+  char 	*access_method;
   char       * mod_name = g_strdup_printf("gnucash/backend/%s", backend_name);
 
   /* FIXME : reinstate better error messages with gnc_module errors */
@@ -344,6 +371,7 @@
    *    XXX this is fexed below, in the non-gnucash version. Cut
    *    over at some point.
    */
+
   mod = gnc_module_load(mod_name, 0);
 
   if (mod) 
@@ -352,12 +380,11 @@
 
     if(be_new_func) 
     {
-      GList *node;
       session->backend = be_new_func();
 
       for (node=session->books; node; node=node->next)
       {
-         QofBook *book = node->data;
+         book = node->data;
          qof_book_set_backend (book, session->backend);
       }
     }
@@ -369,41 +396,49 @@
   }
   else
   {
-    qof_session_int_backend_load_error(session,
-                                       " failed to load '%s' backend", 
-                                       backend_name);
+	/* QSF is built for the QOF version, use that if no module is found.
+	  This allows the GnuCash version to be called with an access method,
+	  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;
 
-  g_free(mod_name);
-  LEAVE (" ");
-}
+		/* Does this provider handle the desired access method? */
+		if (0 == strcasecmp (access_method, prov->access_method))
+		{
+			if (NULL == prov->backend_new) continue;
 
-#else /* GNUCASH */
+			/* Use the providers creation callback */
+        	session->backend = (*(prov->backend_new))();
 
-/* 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 *dl_hand = dlopen (libso, RTLD_LAZY);
-	if (NULL == dl_hand)
+			/* Tell the books about the backend that they'll be using. */
+			for (node=session->books; node; node=node->next)
 	{
-		const char * err_str = dlerror();
-		PERR("Can't load %s backend, %s\n", libso, err_str);
+				book = node->data;
+				qof_book_set_backend (book, session->backend);
+			}
 		return;
 	}
-	void (*initfn) (void)  = dlsym (dl_hand, loadfn);
-	if (initfn)
-	{
-		 (*initfn)();
 	}
-	else
-	{
-		const char * err_str = dlerror();
-		PERR("Can't find %s:%s, %s\n", libso, loadfn, err_str);
+	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);
 	}
+  g_free(mod_name);
+  LEAVE (" ");
 }
 
+#else /* GNUCASH */
+
 static void
 qof_session_load_backend(QofSession * session, char * access_method)
 {
@@ -411,10 +446,11 @@
 	ENTER (" ");
 
 	/* If the provider list is null, try to register the 'well-known'
-	 *  backends. Right now, there's only one. */
+	 *  backends. Right now, there are only two. */
 	if (NULL == provider_list)
 	{
 		load_backend_library ("libqof_backend_dwi.so", "dwiend_provider_init");
+        load_backend_library ("libqsf-backend-file.so", "qsf_provider_init" );
 	}
 
 	for (p = provider_list; p; p=p->next)
Index: qofbackend.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/qofbackend.h,v
retrieving revision 1.3.4.2
retrieving revision 1.3.4.3
diff -Lsrc/engine/qofbackend.h -Lsrc/engine/qofbackend.h -u -r1.3.4.2 -r1.3.4.3
--- src/engine/qofbackend.h
+++ src/engine/qofbackend.h
@@ -66,6 +66,31 @@
                                another user has deleted the object */
   ERR_BACKEND_MISC,         /**< undetermined error */
   
+  /* QSF add-ons */
+  ERR_QSF_INVALID_OBJ, 		/**< The QSF object failed to validate against the QSF object schema */
+  ERR_QSF_INVALID_MAP, 		/**< The QSF map failed to validate against the QSF map schema */
+  ERR_QSF_BAD_QOF_VERSION,	/**< QSF map or object doesn't match the current QOF_OBJECT_VERSION. */
+  ERR_QSF_BAD_MAP,			/**< The selected map validates but is unusable.
+  
+  This is usually because not all the required parameters for the defined objects 
+  have calculations described in the map.
+  */
+  ERR_QSF_NO_MAP,		/**< The QSF object file was loaded without a map
+  
+  The QSF Object file requires a map but it was not provided.
+  */
+  ERR_QSF_WRONG_MAP,		/**< The selected map validates but is for different objects.
+  
+  The list of objects defined in this map does not include all the objects described in
+  the current QSF object file.
+  */
+  ERR_QSF_MAP_NOT_OBJ,		/**< Selected file is a QSF map and cannot be opened as a QSF object */
+  ERR_QSF_OVERFLOW,			/**< EOVERFLOW - generated by strtol or strtoll.
+  
+  When converting XML strings into numbers, an overflow has been detected. The XML file
+  contains invalid data in a field that is meant to hold a signed long integer or signed long long
+  integer.
+  */
   /* fileio errors */
   ERR_FILEIO_FILE_BAD_READ = 1000,  /**< read failed or file prematurely truncated */
   ERR_FILEIO_FILE_EMPTY,     /**< file exists, is readable, but is empty */


More information about the gnucash-changes mailing list