/***************************************************************************
 *            qsf-backend.c
 *
 *  Sat Jan  1 15:07:14 2005
 *  Copyright  2005, 2006  Neil Williams
 *  linux@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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <glib.h>
#include <glib/gstdio.h>
#include "qof.h"
#include "qofbackend-p.h"
#include "qof-backend-qsf.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"
#include <errno.h>
#include <sys/stat.h>

#ifndef HAVE_STRPTIME
#include "strptime.h"
#endif

#define QSF_TYPE_BINARY "binary"
#define QSF_TYPE_GLIST  "glist"
#define QSF_TYPE_FRAME  "frame"

static QofLogModule log_module = QOF_MOD_QSF;
static void qsf_object_commitCB(gpointer key, gpointer value, gpointer data);

struct QSFBackend_s
{
	QofBackend be;
	qsf_param *params;
	gchar *fullpath;
};

typedef struct QSFBackend_s QSFBackend;

static void option_cb (QofBackendOption *option, gpointer data)
{
	qsf_param *params;

	params = (qsf_param*)data;
	g_return_if_fail(params);
	if(0 == safe_strcmp(QSF_COMPRESS, option->option_name)) {
		params->use_gz_level = (*(gint64*)option->value);
	        DEBUG (" gz=%" G_GINT64_FORMAT,params->use_gz_level);
	}
	if (0 == safe_strcmp(QSF_MAP_FILES, option->option_name)) {
		params->map_files = g_list_copy((GList*)option->value);
	}
	if (0 == safe_strcmp(QSF_ENCODING, option->option_name)) {
		params->encoding = g_strdup(option->value);
		DEBUG (" encoding=%s", params->encoding);
	}
}

static void
qsf_load_config(QofBackend *be, KvpFrame *config)
{
	QSFBackend *qsf_be;
	qsf_param  *params;

	ENTER (" ");
	qsf_be = (QSFBackend*)be;
	g_return_if_fail(qsf_be->params);
	params = qsf_be->params;
	qof_backend_option_foreach(config, option_cb, params);
	LEAVE (" ");
}

static KvpFrame*
qsf_get_config(QofBackend *be)
{
	QofBackendOption *option;
	QSFBackend *qsf_be;
	qsf_param *params;

	if(!be) { return NULL; }
	ENTER (" ");
	qsf_be = (QSFBackend*)be;
	g_return_val_if_fail(qsf_be->params, NULL);
	params = qsf_be->params;
	qof_backend_prepare_frame(be);
	option = g_new0(QofBackendOption, 1);
	option->option_name = QSF_COMPRESS;
	option->description = _("Level of compression to use: 0 for none, 9 for highest.");
	option->tooltip = _("QOF can compress QSF XML files using gzip. "
		"Note that compression is not used when outputting to STDOUT.");
	option->type = KVP_TYPE_GINT64;
	option->value = (gpointer)&params->use_gz_level;
	qof_backend_prepare_option(be, option);
	g_free(option);
	option = g_new0(QofBackendOption, 1);
	option->option_name = QSF_MAP_FILES;
	option->description = _("List of QSF map files to use for this session.");
	option->tooltip = _("QOF can convert objects within QSF XML files "
		"using a map of the changes required.");
	option->type = KVP_TYPE_GLIST;
	option->value = (gpointer)params->map_files;
	qof_backend_prepare_option(be, option);
	g_free(option);
	option = g_new0(QofBackendOption, 1);
	option->option_name = QSF_ENCODING;
	option->description = _("String encoding to use when writing the XML file.");
	option->tooltip = _("QSF defaults to UTF-8. Other encodings are supported by "
			"passing the string encoding in this option.");
	option->type = KVP_TYPE_STRING;
	option->value = (gpointer)params->encoding;
	qof_backend_prepare_option(be, option);
	g_free(option);
	LEAVE (" ");
	return qof_backend_complete_frame(be);
}

GList**
qsf_map_prepare_list(GList **maps)
{
	*maps = g_list_prepend(*maps, "pilot-qsf-GnuCashInvoice.xml");
	*maps = g_list_prepend(*maps, "pilot-qsf-gncCustomer.xml");
	return maps;
}

static void
qsf_param_init(qsf_param *params)
{
	Timespec *qsf_ts;
	gchar qsf_time_string[QSF_DATE_LENGTH];
	gchar qsf_enquiry_date[QSF_DATE_LENGTH];
	gchar qsf_time_match[QSF_DATE_LENGTH];
	gchar qsf_time_now[QSF_DATE_LENGTH];
	time_t qsf_time_now_t;
	gchar *qsf_time_precision;

	g_return_if_fail(params != NULL);
	params->count = 0;
	params->use_gz_level = 0;
	params->supported_types = NULL;
	params->file_type = QSF_UNDEF;
	params->qsf_ns = NULL;
	params->output_doc = NULL;
	params->output_node = NULL;
	params->lister = NULL;
	params->full_kvp_path = NULL;
	params->map_ns = NULL;
	params->map_files = NULL;
	params->map_path = NULL;
	params->encoding = "UTF-8";
	params->qsf_object_list = NULL;
	params->qsf_parameter_hash = g_hash_table_new(g_str_hash, g_str_equal);
	params->qsf_default_hash = g_hash_table_new(g_str_hash, g_str_equal);
	params->qsf_define_hash = g_hash_table_new(g_str_hash, g_str_equal);
	params->qsf_calculate_hash = g_hash_table_new(g_str_hash, g_str_equal);
	params->referenceList = NULL;
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_STRING);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_GUID);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_BOOLEAN);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_NUMERIC);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_DATE);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_INT32);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_INT64);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_DOUBLE);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_CHAR);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_KVP);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_COLLECT);
	params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_CHOICE);
	qsf_time_precision = "%j";
	qsf_time_now_t = time(NULL);
	qsf_ts = g_new(Timespec, 1);
	timespecFromTime_t(qsf_ts, qsf_time_now_t);
	strftime(qsf_enquiry_date, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(&qsf_time_now_t));
	strftime(qsf_time_match, QSF_DATE_LENGTH, qsf_time_precision, gmtime(&qsf_time_now_t));
	strftime(qsf_time_string, QSF_DATE_LENGTH, "%F", gmtime(&qsf_time_now_t));
	strftime(qsf_time_now, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(&qsf_time_now_t));
	g_hash_table_insert(params->qsf_default_hash, "qsf_enquiry_date", qsf_enquiry_date);
	g_hash_table_insert(params->qsf_default_hash, "qsf_time_now", &qsf_time_now_t);
	g_hash_table_insert(params->qsf_default_hash, "qsf_time_string", qsf_time_string);
	/* default map files */
	params->map_files = *qsf_map_prepare_list(&params->map_files);
}

static gboolean 
qsf_determine_file_type(const gchar *path)
{
	struct stat sbuf;

	if (!path) { return TRUE; }
	if (0 == safe_strcmp(path, QOF_STDOUT)) { return TRUE; }
	if (g_stat(path, &sbuf) <0)  { return FALSE; }
	if (sbuf.st_size == 0)       { return TRUE; }
	if(is_our_qsf_object(path))  { return TRUE; }
	else if(is_qsf_object(path)) { return TRUE; }
	else if(is_qsf_map(path))    { return TRUE; }
	return FALSE;
}

/* GnuCash does LOTS of filesystem work, QSF is going to leave most of it to libxml2. :-)
Just strip the file: from the start of the book_path URL. Locks are not implemented.
*/
static void
qsf_session_begin(QofBackend *be, QofSession *session, const gchar *book_path,
                   gboolean ignore_lock, gboolean create_if_nonexistent)
{
	QSFBackend *qsf_be;
	gchar *p, *path;

	PINFO (" ignore_lock=%d create_if_nonexistent=%d", ignore_lock, create_if_nonexistent);
	g_return_if_fail(be != NULL);
	qsf_be = (QSFBackend*)be;
	g_return_if_fail(qsf_be->params != NULL);
	qsf_be->fullpath = NULL;
	if(book_path == NULL)
	{
		/* use stdout */
		qof_backend_set_error(be, ERR_BACKEND_NO_ERR);
		return;
	}
	p = strchr (book_path, ':');
	if (p) {
		path = g_strdup (book_path);
		if (!g_ascii_strncasecmp(path, "file:", 5)) {
			p = g_new(gchar, 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);
	}
	if(create_if_nonexistent)
	{
        FILE *f;

        f = g_fopen(qsf_be->fullpath, "a+");
        if(f) {fclose(f); }
		else
		{
			qof_backend_set_error(be, ERR_BACKEND_READONLY);
			return;
		}
	}
	qof_backend_set_error(be, ERR_BACKEND_NO_ERR);
}

static void 
qsf_free_params(qsf_param *params)
{
	g_hash_table_destroy(params->qsf_calculate_hash);
	g_hash_table_destroy(params->qsf_default_hash);
	if(params->referenceList) {
		g_list_free(params->referenceList);
	}
	g_slist_free(params->supported_types);
	if(params->map_ns) { xmlFreeNs(params->map_ns); }
}

static void
qsf_session_end( QofBackend *be)
{
	QSFBackend *qsf_be;

	qsf_be = (QSFBackend*)be;
	g_return_if_fail(qsf_be != NULL);
	qsf_free_params(qsf_be->params);
	g_free(qsf_be->fullpath);
	qsf_be->fullpath = NULL;
	xmlCleanupParser();
}

static void
qsf_destroy_backend (QofBackend *be)
{
	g_free(be);
}

static void
ent_ref_cb (QofEntity* ent, gpointer user_data)
{
	qsf_param *params;
	QofEntityReference *ref;
	void (*reference_setter) (QofEntity*, QofEntity*);
	QofEntity *reference;
	QofCollection *coll;
	QofIdType type;

	params = (qsf_param*)user_data;
	g_return_if_fail(params);
	while(params->referenceList)
	{
		ref = (QofEntityReference*)params->referenceList->data;
		if(qof_object_is_choice(ent->e_type)) { type = ref->choice_type; }
		else { type = ref->type; }
		coll = qof_book_get_collection(params->book, type);
		reference = qof_collection_lookup_entity(coll, ref->ref_guid);
		reference_setter = (void(*)(QofEntity*, QofEntity*))ref->param->param_setfcn;
		if(reference_setter != NULL)
		{
			qof_begin_edit((QofInstance*)ent);
			qof_begin_edit((QofInstance*)reference);
			reference_setter(ent, reference);
			qof_commit_edit((QofInstance*)ent);
			qof_commit_edit((QofInstance*)reference);
		}
		params->referenceList = g_list_next(params->referenceList);
	}
}

static void
insert_ref_cb(QofObject *obj, gpointer user_data)
{
	qsf_param *params;

	params = (qsf_param*)user_data;
	g_return_if_fail(params);
	qof_object_foreach(obj->e_type, params->book, ent_ref_cb, params);
}

/*================================================
	Load QofEntity into QofBook from XML in memory
==================================================*/

static gboolean
qsfdoc_to_qofbook(xmlDocPtr doc, qsf_param *params)
{
	QofInstance *inst;
	struct qsf_node_iterate iter;
	QofBook *book;
	GList *object_list;
	xmlNodePtr qsf_root;
	xmlNsPtr qsf_ns;

	g_return_val_if_fail(params != NULL, FALSE);
	g_return_val_if_fail(params->input_doc != NULL, FALSE);
	g_return_val_if_fail(params->book != NULL, FALSE);
	g_return_val_if_fail(params->file_type == OUR_QSF_OBJ, FALSE);
	qsf_root = xmlDocGetRootElement(params->input_doc);
	if(!qsf_root) { return FALSE; }
	qsf_ns = qsf_root->ns;
	iter.ns = qsf_ns;
	book = params->book;
	params->referenceList = (GList*)qof_book_get_data(book, ENTITYREFERENCE);
	qsf_node_foreach(qsf_root, qsf_book_node_handler, &iter, params);
	object_list = g_list_copy(params->qsf_object_list);
	while(object_list != NULL)
	{
		params->object_set = object_list->data;
		object_list = g_list_next(object_list);
		params->qsf_parameter_hash = params->object_set->parameters;
		if(!qof_class_is_registered(params->object_set->object_type)) { continue; }
		inst = (QofInstance*)qof_object_new_instance(params->object_set->object_type, book);
		g_return_val_if_fail(inst != NULL, FALSE);
		params->qsf_ent = &inst->entity;
		qof_begin_edit(inst);
		g_hash_table_foreach(params->qsf_parameter_hash, qsf_object_commitCB, params);
		qof_commit_edit(inst);
	}
	qof_object_foreach_type(insert_ref_cb, params);
	qof_book_set_data(book, ENTITYREFERENCE, params->referenceList);
	return TRUE;
}

/* QofBackend routine to load from file - needs a map.
*/
static gboolean
load_qsf_object(QofBook *book, const gchar *fullpath, qsf_param *params)
{
	xmlNodePtr qsf_root, map_root;
	xmlDocPtr mapDoc, foreign_doc;
	gchar *map_path, *map_file;

	map_file = params->map_path;
	mapDoc = NULL;
	/* use selected map */
	if(!map_file) {
		qof_backend_set_error(params->be, ERR_QSF_NO_MAP);
		return FALSE; 
	}
	foreign_doc = xmlParseFile(fullpath);
	if (foreign_doc == NULL) {
		qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
		return FALSE;
	}
	qsf_root = NULL;
	qsf_root = xmlDocGetRootElement(foreign_doc);
	params->qsf_ns = qsf_root->ns;
	params->book = book;
	map_path = g_strdup_printf("%s/%s", QSF_SCHEMA_DIR, map_file);
	if(!map_path) {
		qof_backend_set_error(params->be, ERR_QSF_NO_MAP);
		return FALSE; 
	}
	mapDoc = xmlParseFile(map_path);
	if(!mapDoc) {
		qof_backend_set_error(params->be, ERR_QSF_NO_MAP);
		return FALSE;
	}
	map_root = xmlDocGetRootElement(mapDoc);
	params->map_ns = map_root->ns;
	params->input_doc = qsf_object_convert(mapDoc, qsf_root, params);
	qsfdoc_to_qofbook(params->input_doc, params);
	return TRUE;
}

static gboolean
load_our_qsf_object(QofBook *book, const gchar *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);
}

/* Determine the type of QSF and load it into the QofBook

- is_our_qsf_object, OUR_QSF_OBJ, QSF object file using only QOF objects known
	to the calling process.	No map is required.
- is_qsf_object, IS_QSF_OBJ, QSF object file that may or may not have a QSF map
	to convert external objects. This temporary type will be set to HAVE_QSF_MAP 
	if a suitable map exists, or an error value returned: ERR_QSF_NO_MAP, 
	ERR_QSF_BAD_MAP or ERR_QSF_WRONG_MAP. This allows the calling process to inform 
	the user that the QSF itself is valid but a suitable map cannot be found.
- is_qsf_map, IS_QSF_MAP, QSF map file. In the backend, this generates 
	ERR_QSF_MAP_NOT_OBJ but	it can be used internally when processing maps to 
	match a QSF object.

returns NULL on error, otherwise a pointer to the QofBook. Use
	the qof_book_merge API to merge the new data into the current
	QofBook. 
*/
static void
qsf_file_type(QofBackend *be, QofBook *book)
{
	QSFBackend *qsf_be;
	qsf_param *params;
	FILE *f;
	gchar *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);
	f = g_fopen(path, "r");
	if(!f) { qof_backend_set_error(be, ERR_FILEIO_READ_ERROR); }
	fclose(f);
	params->filepath = g_strdup(path);
	qof_backend_get_error(be);
	result = is_our_qsf_object_be(params);
	if(result) {
		params->file_type = OUR_QSF_OBJ;
		result = load_our_qsf_object(book, path, params);
		if(!result) { qof_backend_set_error(be, ERR_FILEIO_PARSE_ERROR); }
		return;
	}
	else if(is_qsf_object_be(params)) {
		params->file_type = IS_QSF_OBJ;
		result = load_qsf_object(book, path, params);
		if(!result) { qof_backend_set_error(be, ERR_FILEIO_PARSE_ERROR); }
		return;
	}
	if(result == FALSE) {
		if(is_qsf_map_be(params)) {
		params->file_type = IS_QSF_MAP;
		qof_backend_set_error(be, ERR_QSF_MAP_NOT_OBJ);
		}
	}
}

static void
qsf_object_sequence(QofParam *qof_param, gpointer data)
{
	qsf_param *params;
	GSList *checklist, *result;

	g_return_if_fail(data != NULL);
	params = (qsf_param*) data;
	result = NULL;
	checklist = NULL;
	params->knowntype = FALSE;
	checklist = g_slist_copy(params->supported_types);
	for(result = checklist; result != NULL; result = result->next)
	{
		if(0 == safe_strcmp((QofIdType)result->data, qof_param->param_type))
		{
			params->knowntype = TRUE;
		}
	}
	g_slist_free(checklist);
	if(0 == safe_strcmp(qof_param->param_type, params->qof_type))
	{
		params->qsf_sequence = g_slist_append(params->qsf_sequence, qof_param);
		params->knowntype = TRUE;
	}
	/* handle params->qof_type = QOF_TYPE_GUID and qof_param->param_type != known type */
	if(0 == safe_strcmp(params->qof_type, QOF_TYPE_GUID)
		&& (params->knowntype == FALSE))
	{
		params->qsf_sequence = g_slist_append(params->qsf_sequence, qof_param);
		params->knowntype = TRUE;
	}
}

/* receives each entry from supported_types in sequence
	type = qof data type from supported list
	user_data = params. Holds object type
*/
static void
qsf_supported_parameters(gpointer type, gpointer user_data)
{
	qsf_param *params;

	g_return_if_fail(user_data != NULL);
	params = (qsf_param*) user_data;
	params->qof_type = (QofIdType)type;
	params->knowntype = FALSE;
	qof_class_param_foreach(params->qof_obj_type, qsf_object_sequence, params);
}

static KvpValueType
qsf_to_kvp_helper(const char *type_string)
{
	if(0 == safe_strcmp(QOF_TYPE_INT64,   type_string)) { return KVP_TYPE_GINT64;   }
	if(0 == safe_strcmp(QOF_TYPE_DOUBLE,  type_string)) { return KVP_TYPE_DOUBLE;   }
	if(0 == safe_strcmp(QOF_TYPE_NUMERIC, type_string)) { return KVP_TYPE_NUMERIC;  }
	if(0 == safe_strcmp(QOF_TYPE_STRING,  type_string)) { return KVP_TYPE_STRING;   }
	if(0 == safe_strcmp(QOF_TYPE_GUID,    type_string)) { return KVP_TYPE_GUID;     }
	if(0 == safe_strcmp(QOF_TYPE_DATE,    type_string)) { return KVP_TYPE_TIMESPEC; }
	if(0 == safe_strcmp(QSF_TYPE_BINARY,  type_string)) { return KVP_TYPE_BINARY;   }
	if(0 == safe_strcmp(QSF_TYPE_GLIST,   type_string)) { return KVP_TYPE_GLIST;    }
	if(0 == safe_strcmp(QSF_TYPE_FRAME,   type_string)) { return KVP_TYPE_FRAME;    }
	return 0;
}

static QofIdTypeConst
kvp_value_to_qof_type_helper(KvpValueType n)
{
	switch(n)
	{
		case KVP_TYPE_GINT64   : { return QOF_TYPE_INT64;   break; }
		case KVP_TYPE_DOUBLE   : { return QOF_TYPE_DOUBLE;  break; }
		case KVP_TYPE_NUMERIC  : { return QOF_TYPE_NUMERIC; break; }
		case KVP_TYPE_STRING   : { return QOF_TYPE_STRING;  break; }
		case KVP_TYPE_GUID     : { return QOF_TYPE_GUID;    break; }
		case KVP_TYPE_TIMESPEC : { return QOF_TYPE_DATE;    break; }
		case KVP_TYPE_BINARY   : { return QSF_TYPE_BINARY;  break; }
		case KVP_TYPE_GLIST    : { return QSF_TYPE_GLIST;   break; }
		case KVP_TYPE_FRAME    : { return QSF_TYPE_FRAME;   break; }
		default : { return NULL; }
	}
}


static void
qsf_from_kvp_helper(const gchar *path, KvpValue *content, gpointer data)
{
	qsf_param *params;
	QofParam *qof_param;
	xmlNodePtr node;
	KvpValueType n;
	gchar *full_path;

	params = (qsf_param*)data;
	qof_param = params->qof_param;
	full_path = NULL;
	g_return_if_fail(params && path && content);
	ENTER (" ");
	n = kvp_value_get_type(content);
	switch(n)
	{
		case KVP_TYPE_GINT64   :
		case KVP_TYPE_DOUBLE   :
		case KVP_TYPE_NUMERIC  :
		case KVP_TYPE_STRING   :
		case KVP_TYPE_GUID     :
		case KVP_TYPE_TIMESPEC :
		case KVP_TYPE_BINARY   :
		case KVP_TYPE_GLIST    :
		{
			node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
				BAD_CAST qof_param->param_type));
			xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
			full_path = g_strconcat(params->full_kvp_path, "/", path, NULL);
			xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST full_path);
			xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, 
				BAD_CAST kvp_value_to_qof_type_helper(n));
			PINFO (" set %s", kvp_value_to_qof_type_helper(n));
			break;
		}
		case KVP_TYPE_FRAME:
		{
			if(!params->full_kvp_path) { params->full_kvp_path = g_strdup(path); }
			else { 
				params->full_kvp_path = g_strconcat(params->full_kvp_path, 
					"/", path, NULL);
			}
			PINFO (" full=%s, path=%s ", params->full_kvp_path, path);
			kvp_frame_for_each_slot(kvp_value_get_frame(content), 
				qsf_from_kvp_helper, params);
			g_free(params->full_kvp_path);
			params->full_kvp_path = NULL;
			break;
		}
		default:
		{
			PERR (" unsupported value = %d", kvp_value_get_type(content));
			break;
		}
	}
	LEAVE (" ");
}

static void
qsf_from_coll_cb (QofEntity *ent, gpointer user_data)
{
	qsf_param *params;
	QofParam *qof_param;
	xmlNodePtr node;
	gchar qsf_guid[GUID_ENCODING_LENGTH + 1];

	params = (qsf_param*)user_data;
	if(!ent || !params) { return; }
	qof_param = params->qof_param;
	guid_to_string_buff(qof_entity_get_guid(ent), qsf_guid);
	node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
		BAD_CAST qof_param->param_type));
	xmlNodeAddContent(node, BAD_CAST qsf_guid);
	xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
}

/******* reference handling ***********/

static gint
qof_reference_list_cb(gconstpointer a, gconstpointer b)
{
	const QofEntityReference *aa;
	const QofEntityReference *bb;

	aa = (QofEntityReference*) a;
	bb = (QofEntityReference*) b;
	if(aa == NULL) { return 1; }
	g_return_val_if_fail((bb != NULL), 1);
	g_return_val_if_fail((aa->type != NULL), 1);
	if((0 == guid_compare(bb->ent_guid, aa->ent_guid))
		&&(0 == safe_strcmp(bb->type, aa->type))
		&&(0 == safe_strcmp(bb->param->param_name, aa->param->param_name)))
	{
		return 0;
	}
	return 1;
}

static QofEntityReference*
qof_reference_lookup(GList *referenceList, QofEntityReference *find)
{
	GList *single_ref;
	QofEntityReference *ent_ref;

	if(referenceList == NULL) { return NULL; }
	g_return_val_if_fail(find != NULL, NULL);
	single_ref = NULL;
	ent_ref = NULL;
	single_ref = g_list_find_custom(referenceList, find, qof_reference_list_cb);
	if(single_ref == NULL) { return ent_ref; }
	ent_ref = (QofEntityReference*)single_ref->data;
	g_list_free(single_ref);
	return ent_ref;
}

static void
reference_list_lookup(gpointer data, gpointer user_data)
{
	QofEntity *ent;
	QofParam *ref_param;
	QofEntityReference *reference, *starter;
	qsf_param  *params;
	const GUID *guid;
	xmlNodePtr node, object_node;
	xmlNsPtr ns;
	GList *copy_list;
	gchar qsf_guid[GUID_ENCODING_LENGTH + 1], *ref_name;

	params = (qsf_param*)user_data;
	ref_param = (QofParam*)data;
	object_node = params->output_node;
	ent = params->qsf_ent;
	ns = params->qsf_ns;
	starter = g_new(QofEntityReference, 1);
	starter->ent_guid = qof_entity_get_guid(ent);
	starter->type = g_strdup(ent->e_type);
	starter->param = ref_param;
	starter->ref_guid = NULL;
	copy_list = g_list_copy(params->referenceList);
	reference = qof_reference_lookup(copy_list, starter);
	g_free(starter);
	if(reference != NULL) {
		if((ref_param->param_getfcn == NULL)||(ref_param->param_setfcn == NULL))
		{
			return;
		}
		ref_name = g_strdup(reference->param->param_name);
		node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST QOF_TYPE_GUID));
		guid_to_string_buff(reference->ref_guid, qsf_guid);
		xmlNodeAddContent(node, BAD_CAST qsf_guid);
		xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST ref_name);
		g_free(ref_name);
	}
	else {
		ent = (QofEntity*)ref_param->param_getfcn(ent, ref_param);
		if(!ent) { return; }
		if((0 == safe_strcmp(ref_param->param_type, QOF_TYPE_COLLECT)) ||
			(0 == safe_strcmp(ref_param->param_type, QOF_TYPE_CHOICE)))
		{ return; }
		node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST QOF_TYPE_GUID));
		guid = qof_entity_get_guid(ent);
		guid_to_string_buff(guid, qsf_guid);
		xmlNodeAddContent(node, BAD_CAST qsf_guid);
		xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST ref_param->param_name);
	}
}

/*=====================================
	Convert QofEntity to QSF XML node
qof_param holds the parameter sequence.
=======================================*/
static void
qsf_entity_foreach(QofEntity *ent, gpointer data)
{
	qsf_param  *params;
	GSList     *param_list, *supported;
	GList      *ref;
	xmlNodePtr node, object_node;
	xmlNsPtr   ns;
	gchar      *string_buffer;
	QofParam   *qof_param;
	QofEntity  *choice_ent;
	KvpFrame   *qsf_kvp;
	QofCollection *qsf_coll;
	gint        param_count;
	gboolean   own_guid;
	const GUID *cm_guid;
	gchar       cm_sa[GUID_ENCODING_LENGTH + 1];

	g_return_if_fail(data != NULL);
	params = (qsf_param*)data;
	param_count = ++params->count;
	ns = params->qsf_ns;
	qsf_kvp = kvp_frame_new();
	own_guid = FALSE;
	choice_ent = NULL;
	object_node = xmlNewChild(params->book_node, params->qsf_ns,
		BAD_CAST QSF_OBJECT_TAG, NULL);
	xmlNewProp(object_node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST ent->e_type);
	string_buffer = g_strdup_printf("%i", param_count);
	xmlNewProp(object_node, BAD_CAST QSF_OBJECT_COUNT, BAD_CAST string_buffer);
	g_free(string_buffer);
	param_list = g_slist_copy(params->qsf_sequence);
	while(param_list != NULL) {
		qof_param = (QofParam*)param_list->data;
		g_return_if_fail(qof_param != NULL);
		if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_GUID))
		{
			if(!own_guid)
			{
				cm_guid = qof_entity_get_guid(ent);
				node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST QOF_TYPE_GUID));
				guid_to_string_buff(cm_guid, cm_sa);
				string_buffer = g_strdup(cm_sa);
				xmlNodeAddContent(node, BAD_CAST string_buffer);
				xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE , BAD_CAST QOF_PARAM_GUID);
				g_free(string_buffer);
				own_guid = TRUE;
			}
			params->qsf_ent = ent;
			params->output_node = object_node;
			ref = qof_class_get_referenceList(ent->e_type);
			if(ref != NULL) {
				g_list_foreach(ref, reference_list_lookup, params);
			}
		}
		if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_COLLECT))
		{
			qsf_coll = qof_param->param_getfcn(ent, qof_param);
			if(qsf_coll) {
				params->qof_param = qof_param;
				params->output_node = object_node;
				if(qof_collection_count(qsf_coll) > 0) {
					qof_collection_foreach(qsf_coll, qsf_from_coll_cb, params);
				}
			}
			param_list = g_slist_next(param_list);
			continue;
		}
		if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_CHOICE))
		{
			/** \todo use the reference list here. */
			choice_ent = (QofEntity*)qof_param->param_getfcn(ent, qof_param);
			if(!choice_ent) {
				param_list = g_slist_next(param_list);
				continue;
			}
			node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST qof_param->param_type));
			cm_guid = qof_entity_get_guid(choice_ent);
			guid_to_string_buff(cm_guid, cm_sa);
			string_buffer = g_strdup(cm_sa);
			xmlNodeAddContent(node, BAD_CAST string_buffer);
			xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
			xmlNewProp(node, BAD_CAST "name", BAD_CAST choice_ent->e_type);
			g_free(string_buffer);
			param_list = g_slist_next(param_list);
			continue;
		}
		if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_KVP))
		{
			qsf_kvp = (KvpFrame*)qof_param->param_getfcn(ent,qof_param);
			if(kvp_frame_is_empty(qsf_kvp))	{ LEAVE(" "); return; }
			params->qof_param = qof_param;
			params->output_node = object_node;
			kvp_frame_for_each_slot(qsf_kvp, qsf_from_kvp_helper, params);
		}
		if((qof_param->param_setfcn != NULL) && (qof_param->param_getfcn != NULL))
		{
			for( supported = g_slist_copy(params->supported_types);
				supported != NULL; supported = g_slist_next(supported))
			{
				if(0 == safe_strcmp((const gchar*)supported->data, (const gchar*)qof_param->param_type))
				{
					node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST qof_param->param_type));
					string_buffer = g_strdup(qof_book_merge_param_as_string(qof_param, ent));
					xmlNodeAddContent(node, BAD_CAST string_buffer);
					xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
					g_free(string_buffer);
				}
			}
		}
		param_list = g_slist_next(param_list);
	}
}

static void
qsf_foreach_obj_type(QofObject *qsf_obj, gpointer data)
{
	qsf_param *params;
	QofBook *book;
	GSList *support;

	g_return_if_fail(data != NULL);
	params = (qsf_param*) data;
	/* Skip unsupported objects */
	if((qsf_obj->create == NULL)||(qsf_obj->foreach == NULL)){
		PINFO (" qsf_obj QOF support failed %s", qsf_obj->e_type);
		return;
	}
	params->qof_obj_type = qsf_obj->e_type;
	params->qsf_sequence = NULL;
	book = params->book;
	support = g_slist_copy(params->supported_types);
	g_slist_foreach(support,qsf_supported_parameters, params);
	qof_object_foreach(qsf_obj->e_type, book, qsf_entity_foreach, params);
}

/*=====================================================
	Take a QofBook and prepare a QSF XML doc in memory
=======================================================*/
/*	QSF only uses one QofBook per file - count may be removed later. */
static xmlDocPtr
qofbook_to_qsf(QofBook *book, qsf_param *params)
{
	xmlNodePtr top_node, node;
	xmlDocPtr doc;
	gchar buffer[GUID_ENCODING_LENGTH + 1];
	const GUID *book_guid;

	g_return_val_if_fail(book != NULL, NULL);
	params->book = book;
	params->referenceList = 
		g_list_copy((GList*)qof_book_get_data(book, ENTITYREFERENCE));
	doc = xmlNewDoc(BAD_CAST QSF_XML_VERSION);
	top_node = xmlNewNode(NULL, BAD_CAST QSF_ROOT_TAG);
	xmlDocSetRootElement(doc, top_node);
	xmlSetNs(top_node, xmlNewNs(top_node, BAD_CAST QSF_DEFAULT_NS, NULL));
	params->qsf_ns = top_node->ns;
	node = xmlNewChild(top_node, params->qsf_ns, BAD_CAST QSF_BOOK_TAG, NULL);
	params->book_node = node;
	xmlNewProp(node, BAD_CAST QSF_BOOK_COUNT, BAD_CAST "1");
	book_guid = qof_book_get_guid(book);
	guid_to_string_buff(book_guid, buffer);
	xmlNewChild(params->book_node, params->qsf_ns, 
		BAD_CAST QSF_BOOK_GUID, BAD_CAST buffer);
	params->output_doc = doc;
	params->book_node = node;
	qof_object_foreach_type(qsf_foreach_obj_type, params);
	return params->output_doc;
}

static void
write_qsf_from_book(const char *path, QofBook *book, qsf_param *params)
{
	xmlDocPtr qsf_doc;
	gint write_result;
	QofBackend *be;

	be = qof_book_get_backend(book);
	qsf_doc = qofbook_to_qsf(book, params);
	write_result = 0;
	DEBUG (" use_gz_level=%" G_GINT64_FORMAT " encoding=%s", 
		params->use_gz_level, params->encoding);
	if((params->use_gz_level > 0) && (params->use_gz_level <= 9)) 
	{
		xmlSetDocCompressMode(qsf_doc, params->use_gz_level); 
	}
	g_return_if_fail(qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, qsf_doc) == TRUE);
	write_result = xmlSaveFormatFileEnc(path, qsf_doc, params->encoding, 1);
	if(write_result < 0) 
	{
		qof_backend_set_error(be, ERR_FILEIO_WRITE_ERROR);
		return;
	}
	xmlFreeDoc(qsf_doc);
}

static void
write_qsf_to_stdout(QofBook *book, qsf_param *params)
{
	xmlDocPtr qsf_doc;

	qsf_doc = qofbook_to_qsf(book, params);
	g_return_if_fail(qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, qsf_doc) == TRUE);
	DEBUG (" use_gz_level=%" G_GINT64_FORMAT " encoding=%s", 
		params->use_gz_level, params->encoding);
	xmlSaveFormatFileEnc("-", qsf_doc, params->encoding, 1);
	fprintf(stdout, "\n");
	xmlFreeDoc(qsf_doc);
}

static void
qsf_write_file(QofBackend *be, QofBook *book)
{
	QSFBackend *qsf_be;
	qsf_param *params;
	char *path;

	qsf_be = (QSFBackend*)be;
	params = qsf_be->params;
	/* if fullpath is blank, book_id was set to QOF_STDOUT */
	if (!qsf_be->fullpath || (*qsf_be->fullpath == '\0')) {
		write_qsf_to_stdout(book, params);
		return;
	}
	path = strdup(qsf_be->fullpath);
	write_qsf_from_book(path, book, params);
	g_free(path);
}

KvpValue*
string_to_kvp_value(const gchar *content, KvpValueType type)
{
	gchar        *tail;
	gint64      cm_i64;
	double      cm_double;
	gnc_numeric cm_numeric;
	GUID        *cm_guid;
	struct tm   kvp_time;
	time_t      kvp_time_t;
	Timespec    cm_date;

	switch(type) {
	  case KVP_TYPE_GINT64:
		errno = 0;
		cm_i64 = strtoll(content, &tail, 0);
		if(errno == 0) {
			return kvp_value_new_gint64(cm_i64);
		}
		break;
	  case KVP_TYPE_DOUBLE:
  		errno = 0;
		cm_double = strtod(content, &tail);
		if(errno == 0) {
			return kvp_value_new_double(cm_double);
		}
		break;
	  case KVP_TYPE_NUMERIC:
		string_to_gnc_numeric(content, &cm_numeric);
		return kvp_value_new_gnc_numeric(cm_numeric);
		break;
	  case KVP_TYPE_STRING:
		return kvp_value_new_string(content);
		break;
	  case KVP_TYPE_GUID:
		cm_guid = g_new(GUID, 1);
		if(TRUE == string_to_guid(content, cm_guid))
		{
			return kvp_value_new_guid(cm_guid);
		}
		break;
	  case KVP_TYPE_TIMESPEC:
		strptime(content, QSF_XSD_TIME, &kvp_time);
		kvp_time_t = mktime(&kvp_time);
		timespecFromTime_t(&cm_date, kvp_time_t);
		return kvp_value_new_timespec(cm_date);
		break;
	  case KVP_TYPE_BINARY:
//		return kvp_value_new_binary(value->value.binary.data,
//									value->value.binary.datasize);
		break;
	  case KVP_TYPE_GLIST:
//		return kvp_value_new_glist(value->value.list);
		break;
	  case KVP_TYPE_FRAME:
//		return kvp_value_new_frame(value->value.frame);
		break;
	}
	return NULL;
}

/* ======================================================
	Commit XML data from file to QofEntity in a QofBook
========================================================= */
void
qsf_object_commitCB(gpointer key, gpointer value, gpointer data)
{
	qsf_param          *params;
	qsf_objects        *object_set;
	xmlNodePtr         node;
	QofEntityReference *reference;
	QofEntity          *qsf_ent;
	QofBook            *targetBook;
	const char         *qof_type, *parameter_name, *timechk;
	QofIdType          obj_type, reference_type;
	struct tm          qsf_time;
	time_t             qsf_time_t;
	gchar              *tail;
	/* cm_ prefix used for variables that hold the data to commit */
	gnc_numeric    cm_numeric;
	double         cm_double;
	gboolean       cm_boolean;
	gint32         cm_i32;
	gint64         cm_i64;
	Timespec       cm_date;
	gchar          cm_char,    (*char_getter)  (xmlNodePtr);
	GUID           *cm_guid;
	KvpFrame       *cm_kvp;
	KvpValue       *cm_value;
	KvpValueType   cm_type;
	QofSetterFunc  cm_setter;
	const QofParam *cm_param;
	void (*string_setter)    (QofEntity*, const gchar*);
	void (*date_setter)      (QofEntity*, Timespec);
	void (*numeric_setter)   (QofEntity*, gnc_numeric);
	void (*double_setter)    (QofEntity*, double);
	void (*boolean_setter)   (QofEntity*, gboolean);
	void (*i32_setter)       (QofEntity*, gint32);
	void (*i64_setter)       (QofEntity*, gint64);
	void (*char_setter)      (QofEntity*, gchar);

	g_return_if_fail(data && value && key);
	params = (qsf_param*)data;
	node = (xmlNodePtr)value;
	parameter_name = (const gchar*)key;
	qof_type = (gchar*)node->name;
	qsf_ent = params->qsf_ent;
	targetBook = params->book;
	memset (&qsf_time, '\0', sizeof(qsf_time));
	cm_date.tv_nsec = 0;
	cm_date.tv_sec = 0;
	obj_type = (gchar*)xmlGetProp(node->parent, BAD_CAST QSF_OBJECT_TYPE);
	if(0 == safe_strcasecmp(obj_type, parameter_name)) { return; }
	cm_setter = qof_class_get_parameter_setter(obj_type, parameter_name);
	cm_param = qof_class_get_parameter(obj_type, parameter_name);
	object_set = params->object_set;
	if(safe_strcmp(qof_type, QOF_TYPE_STRING) == 0)  {
		string_setter = (void(*)(QofEntity*, const gchar*))cm_setter;
		if(string_setter != NULL) { string_setter(qsf_ent, (gchar*)xmlNodeGetContent(node)); }
	}
	if(safe_strcmp(qof_type, QOF_TYPE_DATE) == 0) {
		date_setter = (void(*)(QofEntity*, Timespec))cm_setter;
		timechk = NULL;
		timechk = strptime((char*)xmlNodeGetContent(node), QSF_XSD_TIME, &qsf_time);
		g_return_if_fail(timechk != NULL);
		qsf_time_t = mktime(&qsf_time);
		if(qsf_time_t != -3600)
		{
			timespecFromTime_t(&cm_date, qsf_time_t);
			if(date_setter != NULL) { date_setter(qsf_ent, cm_date); }
		}
	}
	if((safe_strcmp(qof_type, QOF_TYPE_NUMERIC) == 0)  ||
	(safe_strcmp(qof_type, QOF_TYPE_DEBCRED) == 0)) {
		numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_setter;
		string_to_gnc_numeric((char*)xmlNodeGetContent(node), &cm_numeric);
		if(numeric_setter != NULL) { numeric_setter(qsf_ent, cm_numeric); }
	}
	if(safe_strcmp(qof_type, QOF_TYPE_GUID) == 0) {
		cm_guid = g_new(GUID, 1);
		if(TRUE != string_to_guid((char*)xmlNodeGetContent(node), cm_guid))
		{
			qof_backend_set_error(params->be, ERR_QSF_BAD_OBJ_GUID);
			PINFO (" string to guid conversion failed for %s:%s:%s",
				xmlNodeGetContent(node), obj_type, qof_type);
			return;
		}
		reference_type = (char*)xmlGetProp(node, BAD_CAST QSF_OBJECT_TYPE);
		if(0 == safe_strcmp(QOF_PARAM_GUID, reference_type))
		{
			qof_entity_set_guid(qsf_ent, cm_guid);
		}
		else {
			reference = qof_entity_get_reference_from(qsf_ent, cm_param);
			if(reference) {
				params->referenceList = g_list_append(params->referenceList, reference);
			}
		}
	}
	if(safe_strcmp(qof_type, QOF_TYPE_INT32) == 0) {
		errno = 0;
		cm_i32 = (gint32)strtol ((char*)xmlNodeGetContent(node), &tail, 0);
		if(errno == 0) {
			i32_setter = (void(*)(QofEntity*, gint32))cm_setter;
			if(i32_setter != NULL) { i32_setter(qsf_ent, cm_i32); }
		}
		else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
	}
	if(safe_strcmp(qof_type, QOF_TYPE_INT64) == 0) {
		errno = 0;
		cm_i64 = strtoll((gchar*)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((gchar*)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((gchar*)xmlNodeGetContent(node), 
				QSF_XML_BOOLEAN_TEST)) {
			cm_boolean = TRUE;
		}
		else { cm_boolean = FALSE; }
		boolean_setter = (void(*)(QofEntity*, gboolean))cm_setter;
		if(boolean_setter != NULL) { boolean_setter(qsf_ent, cm_boolean); }
	}
	if(safe_strcmp(qof_type, QOF_TYPE_KVP) == 0) {
		cm_type = qsf_to_kvp_helper((gchar*)xmlGetProp(node, BAD_CAST QSF_OBJECT_VALUE));
		if(!cm_type) { return; }
		cm_value = string_to_kvp_value((gchar*)xmlNodeGetContent(node), cm_type);
		cm_kvp = (KvpFrame*)cm_param->param_getfcn(qsf_ent, cm_param);
		cm_kvp = kvp_frame_set_value(cm_kvp, (gchar*)xmlGetProp(node, 
			BAD_CAST QSF_OBJECT_KVP), cm_value);
	}
	if(safe_strcmp(qof_type, QOF_TYPE_COLLECT) == 0) {
		QofCollection *qsf_coll;
		QofIdType type;
		QofEntityReference *reference;
		QofParam *copy_param;
		/* retrieve the *type* of the collection, ignore any contents. */
		qsf_coll = cm_param->param_getfcn(qsf_ent, cm_param);
		type = qof_collection_get_type(qsf_coll);
		cm_guid = g_new(GUID, 1);
		if(TRUE != string_to_guid((gchar*)xmlNodeGetContent(node), cm_guid))
		{
			qof_backend_set_error(params->be, ERR_QSF_BAD_OBJ_GUID);
			PINFO (" string to guid collect failed for %s", xmlNodeGetContent(node));
			return;
		}
		/* create a QofEntityReference with this type and GUID.
		 there is only one entity each time.
		 cm_guid contains the GUID of the reference.
		 type is the type of the reference. */
		reference = g_new0(QofEntityReference, 1);
		reference->type = g_strdup(qsf_ent->e_type);
		reference->ref_guid = cm_guid;
		reference->ent_guid = &qsf_ent->guid;
		copy_param = g_new0(QofParam, 1);
		copy_param->param_name = g_strdup(cm_param->param_name);
		copy_param->param_type = g_strdup(cm_param->param_type);
		reference->param = copy_param;
		params->referenceList = g_list_append(params->referenceList, reference);
	}
	if(safe_strcmp(qof_type, QOF_TYPE_CHAR) == 0) {
		char_getter = (gchar (*)(xmlNodePtr))xmlNodeGetContent;
		cm_char = char_getter(node);
		char_setter = (void(*)(QofEntity*, gchar))cm_setter;
		if(char_setter != NULL) { char_setter(qsf_ent, cm_char); }
	}
}

static QofBackend*
qsf_backend_new(void)
{
	QSFBackend *qsf_be;
	QofBackend *be;

	qsf_be = g_new0(QSFBackend, 1);
	be = (QofBackend*) qsf_be;
	qof_backend_init(be);
	qsf_be->params = g_new(qsf_param, 1);
	qsf_be->params->be = be;
	qsf_param_init(qsf_be->params);
	qsf_be->be.session_begin = qsf_session_begin;

	be->session_end = qsf_session_end;
	be->destroy_backend = qsf_destroy_backend;
	be->load = qsf_file_type;
	be->save_may_clobber_data = NULL;
	/* The QSF backend will always load and save the entire QSF XML file. */
	be->begin = NULL;
	be->commit = NULL;
	be->rollback = NULL;
	/* QSF uses the built-in SQL, not a dedicated SQL server. */
	be->compile_query = NULL;
	be->free_query = NULL;
	be->run_query = NULL;
	be->counter = NULL;
	/* The QSF backend is not multi-user. */
	be->events_pending = NULL;
	be->process_events = NULL;

	be->sync = qsf_write_file;
	/* use for maps, later. */
	be->load_config = qsf_load_config;
	be->get_config = qsf_get_config;

	qsf_be->fullpath = NULL;
	return be;
}

/* The QOF method of loading each backend.
QSF is loaded as a GModule using the QOF method - QofBackendProvider.
*/
static void
qsf_provider_free (QofBackendProvider *prov)
{
	prov->provider_name = NULL;
	prov->access_method = NULL;
	g_free (prov);
}

G_MODULE_EXPORT const gchar *
g_module_check_init(GModule *module)
{
	QofBackendProvider *prov;

	prov = g_new0 (QofBackendProvider, 1);
	prov->provider_name = "QSF Backend Version 0.2";
	prov->access_method = "file";
	prov->partial_book_supported = TRUE;
	prov->backend_new = qsf_backend_new;
	prov->check_data_type = qsf_determine_file_type;
	prov->provider_free = qsf_provider_free;
	qof_backend_register_provider (prov);
	g_module_make_resident (module);
	return NULL;
}
