[Gnucash-changes] r11882 - gnucash/trunk/lib - New locations for
QOF files
Neil Williams
codehelp at cvs.gnucash.org
Mon Nov 7 10:46:12 EST 2005
Author: codehelp
Date: 2005-11-07 10:45:58 -0500 (Mon, 07 Nov 2005)
New Revision: 11882
Added:
gnucash/trunk/lib/libqof/
gnucash/trunk/lib/libqof/Makefile.am
gnucash/trunk/lib/libqof/backend/
gnucash/trunk/lib/libqof/backend/Makefile.am
gnucash/trunk/lib/libqof/backend/file/
gnucash/trunk/lib/libqof/backend/file/Makefile.am
gnucash/trunk/lib/libqof/backend/file/pilot-qsf-GnuCashInvoice.xml
gnucash/trunk/lib/libqof/backend/file/qof-backend-qsf.h
gnucash/trunk/lib/libqof/backend/file/qsf-backend.c
gnucash/trunk/lib/libqof/backend/file/qsf-dir.h.in
gnucash/trunk/lib/libqof/backend/file/qsf-map.xsd.xml
gnucash/trunk/lib/libqof/backend/file/qsf-object.xsd.xml
gnucash/trunk/lib/libqof/backend/file/qsf-xml-map.c
gnucash/trunk/lib/libqof/backend/file/qsf-xml.c
gnucash/trunk/lib/libqof/backend/file/qsf-xml.h
gnucash/trunk/lib/libqof/qof/
gnucash/trunk/lib/libqof/qof/Makefile.am
gnucash/trunk/lib/libqof/qof/gnc-date.c
gnucash/trunk/lib/libqof/qof/gnc-date.h
gnucash/trunk/lib/libqof/qof/gnc-engine-util.c
gnucash/trunk/lib/libqof/qof/gnc-engine-util.h
gnucash/trunk/lib/libqof/qof/gnc-event-p.h
gnucash/trunk/lib/libqof/qof/gnc-event.c
gnucash/trunk/lib/libqof/qof/gnc-event.h
gnucash/trunk/lib/libqof/qof/gnc-numeric.c
gnucash/trunk/lib/libqof/qof/gnc-numeric.h
gnucash/trunk/lib/libqof/qof/gnc-numeric.scm
gnucash/trunk/lib/libqof/qof/gnc-trace.c
gnucash/trunk/lib/libqof/qof/gnc-trace.h
gnucash/trunk/lib/libqof/qof/guid.c
gnucash/trunk/lib/libqof/qof/guid.h
gnucash/trunk/lib/libqof/qof/kvp-util-p.h
gnucash/trunk/lib/libqof/qof/kvp-util.c
gnucash/trunk/lib/libqof/qof/kvp-util.h
gnucash/trunk/lib/libqof/qof/kvp_frame.c
gnucash/trunk/lib/libqof/qof/kvp_frame.h
gnucash/trunk/lib/libqof/qof/md5.c
gnucash/trunk/lib/libqof/qof/md5.h
gnucash/trunk/lib/libqof/qof/qof-be-utils.h
gnucash/trunk/lib/libqof/qof/qof.h
gnucash/trunk/lib/libqof/qof/qof_book_merge.c
gnucash/trunk/lib/libqof/qof/qof_book_merge.h
gnucash/trunk/lib/libqof/qof/qofbackend-p.h
gnucash/trunk/lib/libqof/qof/qofbackend.c
gnucash/trunk/lib/libqof/qof/qofbackend.h
gnucash/trunk/lib/libqof/qof/qofbook-p.h
gnucash/trunk/lib/libqof/qof/qofbook.c
gnucash/trunk/lib/libqof/qof/qofbook.h
gnucash/trunk/lib/libqof/qof/qofchoice.c
gnucash/trunk/lib/libqof/qof/qofchoice.h
gnucash/trunk/lib/libqof/qof/qofclass-p.h
gnucash/trunk/lib/libqof/qof/qofclass.c
gnucash/trunk/lib/libqof/qof/qofclass.h
gnucash/trunk/lib/libqof/qof/qofgobj.c
gnucash/trunk/lib/libqof/qof/qofgobj.h
gnucash/trunk/lib/libqof/qof/qofid-p.h
gnucash/trunk/lib/libqof/qof/qofid.c
gnucash/trunk/lib/libqof/qof/qofid.h
gnucash/trunk/lib/libqof/qof/qofinstance-p.h
gnucash/trunk/lib/libqof/qof/qofinstance.c
gnucash/trunk/lib/libqof/qof/qofinstance.h
gnucash/trunk/lib/libqof/qof/qofla-dir.h.in
gnucash/trunk/lib/libqof/qof/qofmath128.c
gnucash/trunk/lib/libqof/qof/qofmath128.h
gnucash/trunk/lib/libqof/qof/qofobject-p.h
gnucash/trunk/lib/libqof/qof/qofobject.c
gnucash/trunk/lib/libqof/qof/qofobject.h
gnucash/trunk/lib/libqof/qof/qofquery-deserial.c
gnucash/trunk/lib/libqof/qof/qofquery-deserial.h
gnucash/trunk/lib/libqof/qof/qofquery-p.h
gnucash/trunk/lib/libqof/qof/qofquery-serialize.c
gnucash/trunk/lib/libqof/qof/qofquery-serialize.h
gnucash/trunk/lib/libqof/qof/qofquery.c
gnucash/trunk/lib/libqof/qof/qofquery.h
gnucash/trunk/lib/libqof/qof/qofquerycore-p.h
gnucash/trunk/lib/libqof/qof/qofquerycore.c
gnucash/trunk/lib/libqof/qof/qofquerycore.h
gnucash/trunk/lib/libqof/qof/qofsession-p.h
gnucash/trunk/lib/libqof/qof/qofsession.c
gnucash/trunk/lib/libqof/qof/qofsession.h
gnucash/trunk/lib/libqof/qof/qofsql.c
gnucash/trunk/lib/libqof/qof/qofsql.h
Log:
New locations for QOF files
Added: gnucash/trunk/lib/libqof/Makefile.am
===================================================================
--- gnucash/trunk/lib/libqof/Makefile.am 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/Makefile.am 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,2 @@
+SUBDIRS = . qof backend
+
Added: gnucash/trunk/lib/libqof/backend/Makefile.am
===================================================================
--- gnucash/trunk/lib/libqof/backend/Makefile.am 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/Makefile.am 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,2 @@
+SUBDIRS = file
+
Added: gnucash/trunk/lib/libqof/backend/file/Makefile.am
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/Makefile.am 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/Makefile.am 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,43 @@
+SUBDIRS = .
+
+lib_LTLIBRARIES = libqof-backend-qsf.la
+
+AM_CFLAGS = \
+ -I.. -I../.. \
+ -DLOCALE_DIR=\""$(datadir)/locale"\" \
+ ${QOF_CFLAGS} \
+ ${LIBXML2_CFLAGS} \
+ ${GLIB_CFLAGS}
+
+libqof_backend_qsf_la_SOURCES = \
+ qsf-backend.c \
+ qsf-xml-map.c \
+ qsf-xml.c
+
+LIBADD = \
+ ${QOF_LIBS} \
+ ${GLIB_LIBS} \
+ ${LIBXML2_LIBS}
+
+qsfschemadir = $(QSF_SCHEMA_DIR)
+qsfschema_DATA = \
+ qsf-object.xsd.xml \
+ qsf-map.xsd.xml \
+ pilot-qsf-GnuCashInvoice.xml
+
+EXTRA_DIST = \
+ $(qsfschema_DATA) \
+ qsf-dir.h.in \
+ qof-backend-qsf.h \
+ qsf-xml.h
+
+qsf-dir.h: qsf-dir.h.in
+ rm -f $@.tmp
+ sed < $< > $@.tmp \
+ -e 's:@-QSF_SCHEMA_DIR-@:${QSF_SCHEMA_DIR}:g'
+ mv $@.tmp $@
+
+BUILT_SOURCES = qsf-dir.h
+
+CONFIG_CLEAN_FILES = qsf-dir.h
+
Added: gnucash/trunk/lib/libqof/backend/file/pilot-qsf-GnuCashInvoice.xml
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/pilot-qsf-GnuCashInvoice.xml 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/pilot-qsf-GnuCashInvoice.xml 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- this map does NOT post invoices automatically -->
+<!-- maps use the same sequence of parameter types as other QSF -->
+<!-- Trans:desc can be set by expenses or datebook -->
+<qsf-map
+xmlns="http://qof.sourceforge.net/">
+<definition qof_version="3">
+ <define e_type="pilot_expenses">Pilot-link QOF expenses</define>
+ <define e_type="pilot_datebook">Pilot-link QOF datebook</define>
+ <define e_type="pilot_address">Pilot-link QOF address</define>
+ <define e_type="gncInvoice">Invoice</define>
+ <define e_type="Trans">Transaction</define>
+ <define e_type="gncEntry">Order/Invoice/Bill Entry</define>
+ <default name="mileage_rate" type="numeric" value="28/100"/>
+ <default name="use_weekday_descriptor" type="boolean" value="true"/>
+ <default name="use_discount" type="boolean" value="false"/>
+ <default name="tax_included" type="boolean" value="false"/>
+ <default name="post_account" type="string" value="Assets:Current Assets:CD account"/>
+ <default name="expenses_account" type="string" value="Income:Other Income"/>
+ <default name="datebook_account" type="string" value="Income:Locum Income"/>
+ <default name="tax_included" type="enum" value="1">GNC_TAXINCLUDED_YES</default>
+ <default name="tax_included" type="enum" value="2">GNC_TAXINCLUDED_NO</default>
+ <default name="tax_included" type="enum" value="3">GNC_TAXINCLUDED_USEGLOBAL</default>
+ <default name="amount_type" type="enum" value="1">GNC_AMT_TYPE_VALUE</default>
+ <default name="amount_type" type="enum" value="2">GNC_AMT_TYPE_PERCENT</default>
+</definition>
+<object type="gncEntry">
+ <calculate type="string" value="desc">
+ <if boolean="use_weekday_descriptor">
+ <set format="%A">expense_date</set>
+ </if>
+ <else type="qof_expenses">
+ <set>expense_vendor</set>
+ </else>
+ </calculate>
+ <calculate type="string" value="action">
+ <if type="qof-expenses">
+ <set>Material</set>
+ </if>
+ <else type="qof-datebook">
+ <set>Hours</set>
+ </else>
+ </calculate>
+ <calculate type="string" value="notes">
+ <set object="qof-expenses">expense_note</set>
+ </calculate>
+ <calculate type="guid" value="bill-to"/>
+ <calculate type="boolean" value="invoice-taxable"/>
+ <calculate type="boolean" value="bill-taxable"/>
+ <calculate type="boolean" value="billable?"/>
+ <calculate type="boolean" value="bill-tax-included"/>
+ <calculate type="boolean" value="invoice-tax-included">
+ <set>tax_included</set>
+ </calculate>
+ <calculate type="numeric" value="iprice">
+ <if type="string" value="expense_type">
+ <equals type="string" value="etMileage">
+ <set>mileage_rate</set>
+ </equals>
+ </if>
+ </calculate>
+ <calculate type="numeric" value="bprice"/>
+ <calculate type="numeric" value="qty">
+ <if string="expense_type">
+ <equals type="string" value="etMileage">
+ <set object="qof-expenses">expense_amount</set>
+ </equals>
+ </if>
+ <else type="string" value="expense_type">
+ <set>0/1</set>
+ </else>
+ </calculate>
+ <calculate type="numeric" value="invoice-discount">
+ <set>0/1</set>
+ </calculate>
+ <calculate type="date" value="date-entered">
+ <set>qsf_time_now</set>
+ </calculate>
+ <calculate type="date" value="date">
+ <set>qsf_enquiry_date</set>
+ </calculate>
+ <calculate type="gint32" value="discount-type">
+ <set option="amount_type_enum">GNC_AMT_TYPE_PERCENT</set>
+ </calculate>
+ <calculate type="gint32" value="discount-method">
+ <set>2</set>
+ </calculate>
+ <calculate type="gint32" value="bill-payment-type"/>
+</object>
+<object type="Trans">
+ <calculate type="string" value="num"/>
+ <calculate type="string" value="desc"/>
+ <calculate type="date" value="date-entered"/>
+ <calculate type="date" value="date-posted"/>
+ <calculate type="date" value="date-due"/>
+ <calculate type="string" value="notes"/>
+</object>
+<object type="gncInvoice">
+ <calculate type="string" value="id"/>
+ <calculate type="string" value="billing_id"/>
+ <calculate type="string" value="notes"/>
+ <calculate type="guid" value="invoice_owner"/>
+ <calculate type="guid" value="account"/>
+ <calculate type="guid" value="posted_txn"/>
+ <calculate type="guid" value="posted_lot"/>
+ <calculate type="guid" value="terms"/>
+ <calculate type="guid" value="bill-to"/>
+ <calculate type="boolean" value="active">
+ <set>true</set>
+ </calculate>
+ <calculate type="date" value="date_opened">
+ <set>qsf_enquiry_date</set>
+ </calculate>
+ <calculate type="date" value="date_posted"/>
+</object>
+</qsf-map>
Added: gnucash/trunk/lib/libqof/backend/file/qof-backend-qsf.h
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qof-backend-qsf.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qof-backend-qsf.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,150 @@
+/***************************************************************************
+ * qof-backend-qsf.h
+ *
+ * Sat Jun 11 19:34:36 2005
+ * Copyright 2005 Neil Williams
+ * linux at codehelp.co.uk
+ ****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/** @addtogroup Backend
+ @{ */
+/** @addtogroup QSF QOF Serialisation Format
+
+This is the public interface of the qof-backend-qsf library.
+
+QSF - QOF Serialization Format is an XML serialization format
+i.e. it lays out a QOF object in a series of XML tags. The format will
+consist of two component formats:
+
+qof-qsf for the QSF object data and
+
+qsf-map to map sets of QSF objects between QOF applications.
+
+Maps exist to allow complex conversions between objects where object parameters
+need to be calculated, combined or processed using conditionals. Some QSF objects
+can be converted using XSL or other standard tools. For more information on maps,
+see http://code.neil.williamsleesmill.me.uk/map.html
+
+QSF object files will contain user data and are to be exported from QOF applications
+under user control or they can be hand-edited. QSF maps contain application data and
+can be created by application developers from application source code. Tools may be
+created later to generate maps interactively but maps require application support as
+well as an understanding of QOF objects in the import and output applications and are
+intended to remain within the scope of application developers rather than users.
+
+A QSF file written by one QOF application will need an appropriate QSF map before the
+data can be accessed by a different application using QOF. Any QSF objects that are
+not defined in the map will be ignored. QSF files written and accessed by the same
+application can use maps if necessary or can simply import the QSF data as a whole.
+
+Unless specifically mentioned otherwise, all defined strings are case-sensitive.
+
+Full documentation of this format is at:
+
+http://code.neil.williamsleesmill.me.uk/qsf.html
+
+QSF itself is now being built into the QOF library for use with pilot-link to allow
+Palm objects to be described in QOF, written to XML as QSF and imported directly into
+GnuCash and other QOF-compliant applications. As a generic format, it does not depend
+on any pre-defined objects - as the current GnuCash XML format depends on AccountGroup.
+Instead, QSF is a simple container for all QOF objects.
+
+QSF grew from the qof_book_merge code base and uses the qof_book_merge code that is now
+part of QOF. Any QofBook generated by QSF still needs to be merged into the existing
+application data using qof_book_merge. See ::BookMerge.
+
+QSF can be used as an export or offline storage format for QOF applications (although
+long term storage may be best performed using separate (non-XML) methods, depending
+on the application).
+
+QSF is designed to cope with partial QofBooks at the QofObject level. There is no
+requirement for specific objects to always be defined, as long as each QOF object
+specified is fully defined, no orphan or missing parameters are allowed. Part of the
+handling for partial books requires a storage mechanism for references to entities
+that are not within reach of the current book. This requires a little extra coding
+in the QSF QofBackend to contain the reference data so that when the book is
+written out, the reference can be included. When the file is imported back in, a
+little extra code then rebuilds those references during the merge.
+
+Copying entites from an existing QofBook using the qof_entity_copy routines will
+automatically create the reference table. If your QOF objects use references to other
+entities, books that are created manually also need to create a reference table.
+
+Work is continuing on supporting QSF in GnuCash and QOF. QSF is a very open format -
+the majority of the work will be in standardising object types and creating maps that
+convert between objects. Applications that read QSF should ignore any objects that do
+not match the available maps and warn the user about missing data.
+
+Anyone is free to create their own QSF objects, subject to the GNU GPL. It is intended
+that QSF can be used as the flexible, open and free format for QOF data - providing
+all that is lacking from a typical CSV export with all the validation benefits of XML
+and the complex data handling of QOF. The QSF object and map formats remain under the
+GNU GPL licence and QSF is free software.
+
+\todo
+ - Adding more map support, some parts of the map are still not coded. equals,
+ variables and the conditional logic may not be up to the task of the
+ datebook repetition calculations.
+ - Rationalise the API - remove functions that shouldn't be public.
+
+\todo QOF contains numerous g_string_sprintf and g_string_sprintfa calls.
+ These are deprecated and should be renamed to g_string_printf and g_string_append_printf
+ respectively.
+
+QSF is in three sections:
+ - QSF Backend : a QofBackend for file:/ QSF objects and maps.
+ qsf-backend.c
+ - QSF Object : Input, export and validation of QSF object files.
+ qsf-xml.c
+ - QSF Map : Validation, processing and conversion routines.
+ qsf-xml-map.c
+
+ @{ */
+/** @file qof-backend-qsf.h
+ @brief QSF API - Backend, maps and objects.
+ @author Copyright (C) 2004-2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#ifndef _QOF_BACKEND_QSF_H
+#define _QOF_BACKEND_QSF_H
+
+#include "gnc-trace.h"
+#include "qofbackend.h"
+#include "qof-be-utils.h"
+
+#define QOF_MOD_QSF "qof-backend-qsf"
+
+/** \brief Describe this backend to the application.
+
+Sets QSF Backend Version 0.1, access method = file:
+
+This is the QOF backend interface, not a GnuCash module.
+*/
+void qsf_provider_init(void);
+
+/** \brief Create a new QSF backend.
+
+ Initialises the backend and provides access to the
+ functions that will load and save the data.
+*/
+QofBackend* qsf_backend_new(void);
+
+/** @} */
+/** @} */
+
+#endif /* _QOF_BACKEND_QSF_H */
Added: gnucash/trunk/lib/libqof/backend/file/qsf-backend.c
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-backend.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-backend.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1185 @@
+/***************************************************************************
+ * qsf-backend.c
+ *
+ * Sat Jan 1 15:07:14 2005
+ * Copyright 2005 Neil Williams
+ * linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+
+#include "qof-backend-qsf.h"
+#include "qsf-xml.h"
+#include "qsf-dir.h"
+#include <errno.h>
+#include <sys/stat.h>
+
+#define QSF_TYPE_BINARY "binary"
+#define QSF_TYPE_GLIST "glist"
+#define QSF_TYPE_FRAME "frame"
+#define QSF_COMPRESS "compression_level"
+
+static QofLogModule log_module = QOF_MOD_QSF;
+static int use_gz_level = 0;
+
+static void option_cb (QofBackendOption *option, gpointer data)
+{
+ if(0 == safe_strcmp(QSF_COMPRESS, option->option_name)) {
+ use_gz_level = *(gint*)option->value;
+ }
+}
+
+static void
+qsf_load_config(QofBackend *be, KvpFrame *config)
+{
+ qof_backend_option_foreach(config, option_cb, NULL);
+}
+
+static KvpFrame*
+qsf_get_config(QofBackend *be)
+{
+ QofBackendOption *option;
+
+ if(!be) { return NULL; }
+ qof_backend_prepare_frame(be);
+ option = g_new0(QofBackendOption, 1);
+ option->option_name = QSF_COMPRESS;
+ option->description = _("Level of compression to use: 0 for none, 9 for highest.");
+ option->tooltip = _("QOF can compress QSF XML files using gzip. "
+ "Note that compression is not used when outputting to STDOUT.");
+ option->type = KVP_TYPE_GINT64;
+ option->value = (gpointer)&use_gz_level;
+ qof_backend_prepare_option(be, option);
+ g_free(option);
+ return qof_backend_complete_frame(be);
+}
+
+struct QSFBackend_s
+{
+ QofBackend be;
+ qsf_param *params;
+ char *fullpath;
+};
+
+typedef struct QSFBackend_s QSFBackend;
+
+void
+qsf_param_init(qsf_param *params)
+{
+ Timespec *qsf_ts;
+ gchar qsf_time_string[QSF_DATE_LENGTH];
+ gchar qsf_enquiry_date[QSF_DATE_LENGTH];
+ gchar qsf_time_match[QSF_DATE_LENGTH];
+ gchar qsf_time_now[QSF_DATE_LENGTH];
+ time_t qsf_time_now_t;
+ gchar *qsf_time_precision;
+
+ g_return_if_fail(params != NULL);
+ params->count = 0;
+ params->supported_types = NULL;
+ params->file_type = QSF_UNDEF;
+ params->qsf_ns = NULL;
+ params->output_doc = NULL;
+ params->output_node = NULL;
+ params->lister = NULL;
+ params->map_ns = NULL;
+ params->qsf_object_list = NULL;
+ params->qsf_parameter_hash = g_hash_table_new(g_str_hash, g_str_equal);
+ params->qsf_default_hash = g_hash_table_new(g_str_hash, g_str_equal);
+ params->qsf_define_hash = g_hash_table_new(g_str_hash, g_str_equal);
+ params->qsf_calculate_hash = g_hash_table_new(g_str_hash, g_str_equal);
+ params->referenceList = NULL;
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_STRING);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_GUID);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_BOOLEAN);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_NUMERIC);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_DATE);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_INT32);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_INT64);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_DOUBLE);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_CHAR);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_KVP);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_COLLECT);
+ params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_CHOICE);
+ qsf_time_precision = "%j";
+ qsf_time_now_t = time(NULL);
+ qsf_ts = g_new(Timespec, 1);
+ timespecFromTime_t(qsf_ts, qsf_time_now_t);
+ strftime(qsf_enquiry_date, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(&qsf_time_now_t));
+ strftime(qsf_time_match, QSF_DATE_LENGTH, qsf_time_precision, gmtime(&qsf_time_now_t));
+ strftime(qsf_time_string, QSF_DATE_LENGTH, "%F", gmtime(&qsf_time_now_t));
+ strftime(qsf_time_now, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(&qsf_time_now_t));
+ g_hash_table_insert(params->qsf_default_hash, "qsf_enquiry_date", qsf_enquiry_date);
+ g_hash_table_insert(params->qsf_default_hash, "qsf_time_now", &qsf_time_now_t);
+ g_hash_table_insert(params->qsf_default_hash, "qsf_time_string", qsf_time_string);
+}
+
+static gboolean
+qsf_determine_file_type(const char *path)
+{
+ struct stat sbuf;
+
+ PINFO (" %s", path);
+ if (!path) { return TRUE; }
+ if (0 == safe_strcmp(path, QOF_STDOUT)) { return TRUE; }
+ if (stat(path, &sbuf) <0) { return FALSE; }
+ if (sbuf.st_size == 0) { return TRUE; }
+ if(is_our_qsf_object(path)) { return TRUE; }
+ else if(is_qsf_object(path)) { return TRUE; }
+ else if(is_qsf_map(path)) { return TRUE; }
+ return FALSE;
+}
+
+/* GnuCash does LOTS of filesystem work, QSF is going to leave most of it to libxml2. :-)
+Just strip the file: from the start of the book_path URL. Locks and file
+creation are not implemented.
+*/
+static void
+qsf_session_begin(QofBackend *be, QofSession *session, const char *book_path,
+ gboolean ignore_lock, gboolean create_if_nonexistent)
+{
+ QSFBackend *qsf_be;
+ char *p, *path;
+
+ PINFO (" ignore_lock=%d create_if_nonexistent=%d", ignore_lock, create_if_nonexistent);
+ g_return_if_fail(be != NULL);
+ qsf_be = (QSFBackend*)be;
+ g_return_if_fail(qsf_be->params != NULL);
+ qsf_be->fullpath = NULL;
+ if(book_path == NULL)
+ {
+ qof_backend_set_error(be, ERR_BACKEND_NO_ERR);
+ return;
+ }
+ p = strchr (book_path, ':');
+ if (p) {
+ path = g_strdup (book_path);
+ if (!g_strncasecmp(path, "file:", 5)) {
+ p = g_new(char, strlen(path) - 5 + 1);
+ strcpy(p, path + 5);
+ }
+ qsf_be->fullpath = g_strdup(p);
+ g_free (path);
+ }
+ else {
+ qsf_be->fullpath = g_strdup(book_path);
+ }
+ qof_backend_set_error(be, ERR_BACKEND_NO_ERR);
+}
+
+static void
+qsf_session_end( QofBackend *be)
+{
+ QSFBackend *qsf_be;
+
+ qsf_be = (QSFBackend*)be;
+ g_return_if_fail(qsf_be != NULL);
+ qsf_free_params(qsf_be->params);
+ g_free(qsf_be->fullpath);
+ qsf_be->fullpath = NULL;
+ xmlCleanupParser();
+}
+
+static void
+qsf_destroy_backend (QofBackend *be)
+{
+ g_free(be);
+}
+
+QofBackendError
+qof_session_load_our_qsf_object(QofSession *first_session, const char *path)
+{
+ QofSession *qsf_session;
+
+ qsf_session = qof_session_new();
+ qof_session_begin(qsf_session, path, FALSE, FALSE);
+ qof_session_load(qsf_session, NULL);
+ /* FIXME: This needs to return success and set the open not merge error in file_open */
+ return ERR_QSF_OPEN_NOT_MERGE;
+}
+
+QofBackendError
+qof_session_load_qsf_object(QofSession *first_session, const char *path)
+{
+ PINFO ("%s = ERR_QSF_NO_MAP", path);
+ return ERR_QSF_NO_MAP;
+}
+
+void
+qsf_file_type(QofBackend *be, QofBook *book)
+{
+ QSFBackend *qsf_be;
+ qsf_param *params;
+ char *path;
+ gboolean result;
+
+ g_return_if_fail(be != NULL);
+ g_return_if_fail(book != NULL);
+ qsf_be = (QSFBackend*) be;
+ g_return_if_fail(qsf_be != NULL);
+ g_return_if_fail(qsf_be->fullpath != NULL);
+ g_return_if_fail(qsf_be->params != NULL);
+ params = qsf_be->params;
+ params->book = book;
+ path = g_strdup(qsf_be->fullpath);
+ params->filepath = g_strdup(path);
+ qof_backend_get_error(be);
+ result = is_our_qsf_object_be(params);
+ if(result) {
+ params->file_type = OUR_QSF_OBJ;
+ result = load_our_qsf_object(book, path, params);
+ if(!result) { qof_backend_set_error(be, ERR_FILEIO_PARSE_ERROR); }
+ return;
+ }
+ else if(is_qsf_object_be(params)) {
+ params->file_type = IS_QSF_OBJ;
+ result = load_qsf_object(book, path, params);
+ if(!result) { qof_backend_set_error(be, ERR_FILEIO_PARSE_ERROR); }
+ }
+ if(result == FALSE) {
+ if(is_qsf_map_be(params)) {
+ params->file_type = IS_QSF_MAP;
+ qof_backend_set_error(be, ERR_QSF_MAP_NOT_OBJ);
+ }
+ }
+}
+
+static void
+ent_ref_cb (QofEntity* ent, gpointer user_data)
+{
+ qsf_param *params;
+ QofEntityReference *ref;
+ void (*reference_setter) (QofEntity*, QofEntity*);
+ QofEntity *reference;
+ QofCollection *coll;
+ QofIdType type;
+
+ params = (qsf_param*)user_data;
+ g_return_if_fail(params);
+ while(params->referenceList)
+ {
+ ref = (QofEntityReference*)params->referenceList->data;
+ if(qof_object_is_choice(ent->e_type)) { type = ref->choice_type; }
+ else { type = ref->type; }
+ coll = qof_book_get_collection(params->book, type);
+ reference = qof_collection_lookup_entity(coll, ref->ref_guid);
+ reference_setter = (void(*)(QofEntity*, QofEntity*))ref->param->param_setfcn;
+ if(reference_setter != NULL)
+ {
+ qof_begin_edit((QofInstance*)ent);
+ qof_begin_edit((QofInstance*)reference);
+ reference_setter(ent, reference);
+ qof_commit_edit((QofInstance*)ent);
+ qof_commit_edit((QofInstance*)reference);
+ }
+ params->referenceList = g_list_next(params->referenceList);
+ }
+}
+
+static void
+insert_ref_cb(QofObject *obj, gpointer user_data)
+{
+ qsf_param *params;
+
+ params = (qsf_param*)user_data;
+ g_return_if_fail(params);
+ qof_object_foreach(obj->e_type, params->book, ent_ref_cb, params);
+}
+
+/*================================================
+ Load QofEntity into QofBook from XML in memory
+==================================================*/
+
+static gboolean
+qsfdoc_to_qofbook(xmlDocPtr doc, qsf_param *params)
+{
+ QofInstance *inst;
+ struct qsf_node_iterate iter;
+ QofBook *book;
+ GList *object_list;
+ xmlNodePtr qsf_root;
+ xmlNsPtr qsf_ns;
+
+ g_return_val_if_fail(params != NULL, FALSE);
+ g_return_val_if_fail(params->input_doc != NULL, FALSE);
+ g_return_val_if_fail(params->book != NULL, FALSE);
+ g_return_val_if_fail(params->file_type == OUR_QSF_OBJ, FALSE);
+ qsf_root = xmlDocGetRootElement(params->input_doc);
+ if(!qsf_root) { return FALSE; }
+ qsf_ns = qsf_root->ns;
+ iter.ns = qsf_ns;
+ book = params->book;
+ params->referenceList = (GList*)qof_book_get_data(book, ENTITYREFERENCE);
+ qsf_node_foreach(qsf_root, qsf_book_node_handler, &iter, params);
+ object_list = g_list_copy(params->qsf_object_list);
+ while(object_list != NULL)
+ {
+ params->object_set = object_list->data;
+ params->qsf_parameter_hash = params->object_set->parameters;
+ inst = (QofInstance*)qof_object_new_instance(params->object_set->object_type, book);
+ g_return_val_if_fail(inst != NULL, FALSE);
+ params->qsf_ent = &inst->entity;
+ qof_begin_edit(inst);
+ g_hash_table_foreach(params->qsf_parameter_hash, qsf_object_commitCB, params);
+ qof_commit_edit(inst);
+ object_list = g_list_next(object_list);
+ }
+ qof_object_foreach_type(insert_ref_cb, params);
+ qof_book_set_data(book, ENTITYREFERENCE, params->referenceList);
+ return TRUE;
+}
+
+static void
+qsf_object_sequence(QofParam *qof_param, gpointer data)
+{
+ qsf_param *params;
+ GSList *checklist, *result;
+
+ g_return_if_fail(data != NULL);
+ params = (qsf_param*) data;
+ result = NULL;
+ checklist = NULL;
+ params->knowntype = FALSE;
+ checklist = g_slist_copy(params->supported_types);
+ for(result = checklist; result != NULL; result = result->next)
+ {
+ if(0 == safe_strcmp((QofIdType)result->data, qof_param->param_type))
+ {
+ params->knowntype = TRUE;
+ }
+ }
+ g_slist_free(checklist);
+ if(0 == safe_strcmp(qof_param->param_type, params->qof_type))
+ {
+ params->qsf_sequence = g_slist_append(params->qsf_sequence, qof_param);
+ params->knowntype = TRUE;
+ }
+ /* handle params->qof_type = QOF_TYPE_GUID and qof_param->param_type != known type */
+ if(0 == safe_strcmp(params->qof_type, QOF_TYPE_GUID)
+ && (params->knowntype == FALSE))
+ {
+ params->qsf_sequence = g_slist_append(params->qsf_sequence, qof_param);
+ params->knowntype = TRUE;
+ }
+}
+
+/* receives each entry from supported_types in sequence
+ type = qof data type from supported list
+ user_data = params. Holds object type
+*/
+static void
+qsf_supported_parameters(gpointer type, gpointer user_data)
+{
+ qsf_param *params;
+
+ g_return_if_fail(user_data != NULL);
+ params = (qsf_param*) user_data;
+ params->qof_type = (QofIdType)type;
+ params->knowntype = FALSE;
+ qof_class_param_foreach(params->qof_obj_type, qsf_object_sequence, params);
+}
+
+static KvpValueType
+qsf_to_kvp_helper(const char *type_string)
+{
+ if(0 == safe_strcmp(QOF_TYPE_STRING, type_string)) { return KVP_TYPE_STRING; }
+ if(0 == safe_strcmp(QOF_TYPE_GUID, type_string)) { return KVP_TYPE_GUID; }
+ if(0 == safe_strcmp(QOF_TYPE_INT64, type_string)) { return KVP_TYPE_GINT64; }
+ if(0 == safe_strcmp(QOF_TYPE_DOUBLE, type_string)) { return KVP_TYPE_DOUBLE; }
+ if(0 == safe_strcmp(QOF_TYPE_NUMERIC, type_string)) { return KVP_TYPE_NUMERIC; }
+ if(0 == safe_strcmp(QSF_TYPE_BINARY, type_string)) { return KVP_TYPE_BINARY; }
+ if(0 == safe_strcmp(QSF_TYPE_GLIST, type_string)) { return KVP_TYPE_GLIST; }
+ if(0 == safe_strcmp(QSF_TYPE_FRAME, type_string)) { return KVP_TYPE_FRAME; }
+ return 0;
+}
+
+static void
+qsf_from_kvp_helper(const char *path, KvpValue *content, gpointer data)
+{
+ qsf_param *params;
+ QofParam *qof_param;
+ xmlNodePtr node;
+
+ params = (qsf_param*)data;
+ qof_param = params->qof_param;
+ g_return_if_fail(params && path && content);
+ ENTER (" path=%s", path);
+ switch(kvp_value_get_type(content))
+ {
+ case KVP_TYPE_STRING:
+ {
+ node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+ BAD_CAST qof_param->param_type));
+ xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QOF_TYPE_STRING);
+ break;
+ }
+ case KVP_TYPE_GUID:
+ {
+ node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+ BAD_CAST qof_param->param_type));
+ xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QOF_TYPE_GUID);
+ break;
+ }
+ case KVP_TYPE_BINARY:
+ {
+ node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+ BAD_CAST qof_param->param_type));
+ xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QSF_TYPE_BINARY);
+ break;
+ }
+ case KVP_TYPE_GLIST:
+ {
+ node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+ BAD_CAST qof_param->param_type));
+ xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QSF_TYPE_GLIST);
+ break;
+ }
+ case KVP_TYPE_FRAME:
+ {
+ node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+ BAD_CAST qof_param->param_type));
+ xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QSF_TYPE_FRAME);
+ break;
+ }
+ case KVP_TYPE_GINT64:
+ {
+ node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+ BAD_CAST qof_param->param_type));
+ xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QOF_TYPE_INT64);
+ break;
+ }
+ case KVP_TYPE_DOUBLE:
+ {
+ node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+ BAD_CAST qof_param->param_type));
+ xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QOF_TYPE_DOUBLE);
+ break;
+ }
+ case KVP_TYPE_NUMERIC:
+ {
+ node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+ BAD_CAST qof_param->param_type));
+ xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST path);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE, BAD_CAST QOF_TYPE_NUMERIC);
+ break;
+ }
+ default:
+ { break; }
+ }
+ LEAVE (" ");
+}
+
+static void
+qsf_from_coll_cb (QofEntity *ent, gpointer user_data)
+{
+ qsf_param *params;
+ QofParam *qof_param;
+ xmlNodePtr node;
+ gchar qsf_guid[GUID_ENCODING_LENGTH + 1];
+
+ params = (qsf_param*)user_data;
+ if(!ent || !params) { return; }
+ qof_param = params->qof_param;
+ node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
+ BAD_CAST qof_param->param_type));
+ guid_to_string_buff(qof_entity_get_guid(ent), qsf_guid);
+ xmlNodeAddContent(node, BAD_CAST qsf_guid);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+}
+
+/******* reference handling ***********/
+
+static gint
+qof_reference_list_cb(gconstpointer a, gconstpointer b)
+{
+ const QofEntityReference *aa;
+ const QofEntityReference *bb;
+
+ aa = (QofEntityReference*) a;
+ bb = (QofEntityReference*) b;
+ if(aa == NULL) { return 1; }
+ g_return_val_if_fail((bb != NULL), 1);
+ g_return_val_if_fail((aa->type != NULL), 1);
+ if((0 == guid_compare(bb->ent_guid, aa->ent_guid))
+ &&(0 == safe_strcmp(bb->type, aa->type))
+ &&(0 == safe_strcmp(bb->param->param_name, aa->param->param_name)))
+ {
+ return 0;
+ }
+ return 1;
+}
+
+static QofEntityReference*
+qof_reference_lookup(GList *referenceList, QofEntityReference *find)
+{
+ GList *single_ref;
+ QofEntityReference *ent_ref;
+
+ if(referenceList == NULL) { return NULL; }
+ g_return_val_if_fail(find != NULL, NULL);
+ single_ref = NULL;
+ ent_ref = NULL;
+ single_ref = g_list_find_custom(referenceList, find, qof_reference_list_cb);
+ if(single_ref == NULL) { return ent_ref; }
+ ent_ref = (QofEntityReference*)single_ref->data;
+ g_list_free(single_ref);
+ return ent_ref;
+}
+
+static void
+reference_list_lookup(gpointer data, gpointer user_data)
+{
+ QofEntity *ent;
+ QofParam *ref_param;
+ QofEntityReference *reference, *starter;
+ qsf_param *params;
+ xmlNodePtr node, object_node;
+ xmlNsPtr ns;
+ GList *copy_list;
+ gchar qsf_guid[GUID_ENCODING_LENGTH + 1], *ref_name;
+
+ params = (qsf_param*)user_data;
+ ref_param = (QofParam*)data;
+ object_node = params->output_node;
+ ent = params->qsf_ent;
+ ns = params->qsf_ns;
+ starter = g_new(QofEntityReference, 1);
+ starter->ent_guid = qof_entity_get_guid(ent);
+ starter->type = g_strdup(ent->e_type);
+ starter->param = ref_param;
+ starter->ref_guid = NULL;
+ copy_list = g_list_copy(params->referenceList);
+ reference = qof_reference_lookup(copy_list, starter);
+ g_free(starter);
+ if(reference != NULL) {
+ if((ref_param->param_getfcn == NULL)||(ref_param->param_setfcn == NULL))
+ {
+ return;
+ }
+ ref_name = g_strdup(reference->param->param_name);
+ node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST QOF_TYPE_GUID));
+ guid_to_string_buff(reference->ref_guid, qsf_guid);
+ xmlNodeAddContent(node, BAD_CAST qsf_guid);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST ref_name);
+ g_free(ref_name);
+ }
+}
+
+/*=====================================
+ Convert QofEntity to QSF XML node
+qof_param holds the parameter sequence.
+=======================================*/
+static void
+qsf_entity_foreach(QofEntity *ent, gpointer data)
+{
+ qsf_param *params;
+ GSList *param_list, *supported;
+ GList *ref;
+ xmlNodePtr node, object_node;
+ xmlNsPtr ns;
+ gchar *string_buffer;
+ GString *buffer;
+ QofParam *qof_param;
+ QofEntity *choice_ent;
+ KvpFrame *qsf_kvp;
+ QofCollection *qsf_coll;
+ int param_count;
+ gboolean own_guid;
+ const GUID *cm_guid;
+ char cm_sa[GUID_ENCODING_LENGTH + 1];
+
+ g_return_if_fail(data != NULL);
+ params = (qsf_param*)data;
+ param_count = ++params->count;
+ ns = params->qsf_ns;
+ qsf_kvp = kvp_frame_new();
+ own_guid = FALSE;
+ choice_ent = NULL;
+ object_node = xmlNewChild(params->book_node, params->qsf_ns,
+ BAD_CAST QSF_OBJECT_TAG, NULL);
+ xmlNewProp(object_node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST ent->e_type);
+ buffer = g_string_new(" ");
+ g_string_printf(buffer, "%i", param_count);
+ xmlNewProp(object_node, BAD_CAST QSF_OBJECT_COUNT, BAD_CAST buffer->str);
+ param_list = g_slist_copy(params->qsf_sequence);
+ while(param_list != NULL) {
+ qof_param = (QofParam*)param_list->data;
+ g_return_if_fail(qof_param != NULL);
+ if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_GUID))
+ {
+ if(!own_guid)
+ {
+ cm_guid = qof_entity_get_guid(ent);
+ node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST QOF_TYPE_GUID));
+ guid_to_string_buff(cm_guid, cm_sa);
+ string_buffer = g_strdup(cm_sa);
+ xmlNodeAddContent(node, BAD_CAST string_buffer);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE , BAD_CAST QOF_PARAM_GUID);
+ own_guid = TRUE;
+ }
+ params->qsf_ent = ent;
+ params->output_node = object_node;
+ ref = qof_class_get_referenceList(ent->e_type);
+ if(ref != NULL) {
+ g_list_foreach(ref, reference_list_lookup, params);
+ }
+ }
+ if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_COLLECT))
+ {
+ qsf_coll = qof_param->param_getfcn(ent, qof_param);
+ params->qof_param = qof_param;
+ params->output_node = object_node;
+ qof_collection_foreach(qsf_coll, qsf_from_coll_cb, params);
+ }
+ if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_CHOICE))
+ {
+ /** \todo use the reference list here. */
+ /* repeats due to use of reference list for GUID and CHOICE */
+ /* fix to allow COLLECT too */
+ choice_ent = (QofEntity*)qof_param->param_getfcn(ent, qof_param);
+ if(!choice_ent) {
+ param_list = g_slist_next(param_list);
+ continue;
+ }
+ node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST qof_param->param_type));
+ cm_guid = qof_entity_get_guid(choice_ent);
+ guid_to_string_buff(cm_guid, cm_sa);
+ string_buffer = g_strdup(cm_sa);
+ xmlNodeAddContent(node, BAD_CAST string_buffer);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+ xmlNewProp(node, BAD_CAST "name", BAD_CAST choice_ent->e_type);
+ param_list = g_slist_next(param_list);
+ continue;
+ }
+ if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_KVP))
+ {
+ qsf_kvp = kvp_frame_copy(qof_param->param_getfcn(ent,qof_param));
+ params->qof_param = qof_param;
+ params->output_node = object_node;
+ kvp_frame_for_each_slot(qsf_kvp, qsf_from_kvp_helper, params);
+ }
+ if((qof_param->param_setfcn != NULL) && (qof_param->param_getfcn != NULL))
+ {
+ for( supported = g_slist_copy(params->supported_types);
+ supported != NULL; supported = g_slist_next(supported))
+ {
+ if(0 == safe_strcmp((const char*)supported->data, (const char*)qof_param->param_type))
+ {
+ node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST qof_param->param_type));
+ string_buffer = g_strdup(qof_book_merge_param_as_string(qof_param, ent));
+ xmlNodeAddContent(node, BAD_CAST string_buffer);
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
+ }
+ }
+ }
+ param_list = g_slist_next(param_list);
+ }
+}
+
+static void
+qsf_foreach_obj_type(QofObject *qsf_obj, gpointer data)
+{
+ qsf_param *params;
+ QofBook *book;
+ GSList *support;
+
+ g_return_if_fail(data != NULL);
+ params = (qsf_param*) data;
+ /* Skip unsupported objects */
+ if((qsf_obj->create == NULL)||(qsf_obj->foreach == NULL)){
+ PINFO (" qsf_obj QOF support failed %s", qsf_obj->e_type);
+ return;
+ }
+ params->qof_obj_type = qsf_obj->e_type;
+ params->qsf_sequence = NULL;
+ book = params->book;
+ support = g_slist_copy(params->supported_types);
+ g_slist_foreach(support,qsf_supported_parameters, params);
+ qof_object_foreach(qsf_obj->e_type, book, qsf_entity_foreach, params);
+}
+
+/*=====================================================
+ Take a QofBook and prepare a QSF XML doc in memory
+=======================================================*/
+/* QSF only uses one QofBook per file - count may be removed later. */
+static xmlDocPtr
+qofbook_to_qsf(QofBook *book, qsf_param *params)
+{
+ xmlNodePtr top_node, node;
+ xmlDocPtr doc;
+ gchar buffer[GUID_ENCODING_LENGTH + 1];
+ const GUID *book_guid;
+
+ g_return_val_if_fail(book != NULL, NULL);
+ params->book = book;
+ params->referenceList = g_list_copy((GList*)qof_book_get_data(book, ENTITYREFERENCE));
+ doc = xmlNewDoc(BAD_CAST QSF_XML_VERSION);
+ top_node = xmlNewNode(NULL, BAD_CAST QSF_ROOT_TAG);
+ xmlDocSetRootElement(doc, top_node);
+ xmlSetNs(top_node, xmlNewNs(top_node, BAD_CAST QSF_DEFAULT_NS, NULL));
+ params->qsf_ns = top_node->ns;
+ node = xmlNewChild(top_node, params->qsf_ns, BAD_CAST QSF_BOOK_TAG, NULL);
+ params->book_node = node;
+ xmlNewProp(node, BAD_CAST QSF_BOOK_COUNT, BAD_CAST "1");
+ book_guid = qof_book_get_guid(book);
+ guid_to_string_buff(book_guid, buffer);
+ xmlNewChild(params->book_node, params->qsf_ns, BAD_CAST QSF_BOOK_GUID, BAD_CAST buffer);
+ params->output_doc = doc;
+ params->book_node = node;
+ qof_object_foreach_type(qsf_foreach_obj_type, params);
+ return params->output_doc;
+}
+
+static void
+write_qsf_from_book(const char *path, QofBook *book, qsf_param *params)
+{
+ xmlDocPtr qsf_doc;
+ gint write_result;
+ QofBackend *be;
+
+ be = qof_book_get_backend(book);
+ qsf_doc = qofbook_to_qsf(book, params);
+ write_result = 0;
+ if((use_gz_level > 0) && (use_gz_level <= 9))
+ {
+ xmlSetDocCompressMode(qsf_doc, use_gz_level);
+ }
+ g_return_if_fail(qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, qsf_doc) == TRUE);
+ write_result = xmlSaveFormatFileEnc(path, qsf_doc, "UTF-8", 1);
+ if(write_result < 0)
+ {
+ qof_backend_set_error(be, ERR_FILEIO_WRITE_ERROR);
+ return;
+ }
+ xmlFreeDoc(qsf_doc);
+}
+
+static void
+write_qsf_to_stdout(QofBook *book, qsf_param *params)
+{
+ xmlDocPtr qsf_doc;
+
+ qsf_doc = qofbook_to_qsf(book, params);
+ g_return_if_fail(qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, qsf_doc) == TRUE);
+ xmlSaveFormatFileEnc("-", qsf_doc, "UTF-8", 1);
+ fprintf(stdout, "\n");
+ xmlFreeDoc(qsf_doc);
+ LEAVE (" ");
+}
+
+void
+qsf_write_file(QofBackend *be, QofBook *book)
+{
+ QSFBackend *qsf_be;
+ qsf_param *params;
+ char *path;
+
+ qsf_be = (QSFBackend*)be;
+ params = qsf_be->params;
+ /* if fullpath is blank, book_id was set to QOF_STDOUT */
+ if (!qsf_be->fullpath || (*qsf_be->fullpath == '\0')) {
+ write_qsf_to_stdout(book, params);
+ return;
+ }
+ path = strdup(qsf_be->fullpath);
+ write_qsf_from_book(path, book, params);
+ g_free(path);
+}
+
+/* QofBackend routine to load from file - needs a map.
+*/
+gboolean
+load_qsf_object(QofBook *book, const char *fullpath, qsf_param *params)
+{
+ xmlNodePtr qsf_root, map_root;
+ xmlDocPtr mapDoc, foreign_doc;
+ gchar *map_path, *map_file;
+
+ map_file = g_strdup("pilot-qsf-GnuCashInvoice.xml");
+ foreign_doc = xmlParseFile(fullpath);
+ if (foreign_doc == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+ return FALSE;
+ }
+ qsf_root = NULL;
+ qsf_root = xmlDocGetRootElement(foreign_doc);
+ params->qsf_ns = qsf_root->ns;
+ params->book = book;
+ map_path = g_strdup_printf("%s/%s", QSF_SCHEMA_DIR, map_file);
+ if(map_path == NULL) { return FALSE; }
+ mapDoc = xmlParseFile(map_path);
+ map_root = xmlDocGetRootElement(mapDoc);
+ params->map_ns = map_root->ns;
+ params->input_doc = qsf_object_convert(mapDoc, qsf_root, params);
+ qsfdoc_to_qofbook(params->input_doc, params);
+ return TRUE;
+}
+
+gboolean
+load_our_qsf_object(QofBook *book, const char *fullpath, qsf_param *params)
+{
+ xmlNodePtr qsf_root;
+
+ params->input_doc = xmlParseFile(fullpath);
+ if (params->input_doc == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+ return FALSE;
+ }
+ qsf_root = NULL;
+ qsf_root = xmlDocGetRootElement(params->input_doc);
+ params->qsf_ns = qsf_root->ns;
+ return qsfdoc_to_qofbook(params->input_doc, params);
+}
+
+KvpValue*
+string_to_kvp_value(const char *content, KvpValueType type)
+{
+ char *tail;
+ gint64 cm_i64;
+ double cm_double;
+ gnc_numeric cm_numeric;
+ GUID *cm_guid;
+ struct tm kvp_time;
+ time_t kvp_time_t;
+ Timespec cm_date;
+
+ switch(type) {
+ case KVP_TYPE_GINT64:
+ errno = 0;
+ cm_i64 = strtoll(content, &tail, 0);
+ if(errno == 0) {
+ return kvp_value_new_gint64(cm_i64);
+ }
+ break;
+ case KVP_TYPE_DOUBLE:
+ errno = 0;
+ cm_double = strtod(content, &tail);
+ if(errno == 0) {
+ return kvp_value_new_double(cm_double);
+ }
+ break;
+ case KVP_TYPE_NUMERIC:
+ string_to_gnc_numeric(content, &cm_numeric);
+ return kvp_value_new_gnc_numeric(cm_numeric);
+ break;
+ case KVP_TYPE_STRING:
+ return kvp_value_new_string(content);
+ break;
+ case KVP_TYPE_GUID:
+ cm_guid = g_new(GUID, 1);
+ if(TRUE == string_to_guid(content, cm_guid))
+ {
+ return kvp_value_new_guid(cm_guid);
+ }
+ break;
+ case KVP_TYPE_TIMESPEC:
+ strptime(content, QSF_XSD_TIME, &kvp_time);
+ kvp_time_t = mktime(&kvp_time);
+ timespecFromTime_t(&cm_date, kvp_time_t);
+ return kvp_value_new_timespec(cm_date);
+ break;
+ case KVP_TYPE_BINARY:
+// return kvp_value_new_binary(value->value.binary.data,
+// value->value.binary.datasize);
+ break;
+ case KVP_TYPE_GLIST:
+// return kvp_value_new_glist(value->value.list);
+ break;
+ case KVP_TYPE_FRAME:
+// return kvp_value_new_frame(value->value.frame);
+ break;
+ }
+ return NULL;
+}
+
+/*======================================================
+ Commit XML data from file to QofEntity in a QofBook
+========================================================*/
+void
+qsf_object_commitCB(gpointer key, gpointer value, gpointer data)
+{
+ qsf_param *params;
+ qsf_objects *object_set;
+ xmlNodePtr node;
+ QofEntityReference *reference;
+ QofEntity *qsf_ent;
+ QofBook *targetBook;
+ const char *qof_type, *parameter_name, *timechk;
+ QofIdType obj_type, reference_type;
+ struct tm qsf_time;
+ time_t qsf_time_t;
+ char *tail;
+ /* cm_ prefix used for variables that hold the data to commit */
+ gnc_numeric cm_numeric;
+ double cm_double;
+ gboolean cm_boolean;
+ gint32 cm_i32;
+ gint64 cm_i64;
+ Timespec cm_date;
+ char cm_char, (*char_getter) (xmlNodePtr);
+ GUID *cm_guid;
+ KvpFrame *cm_kvp;
+ KvpValue *cm_value;
+ KvpValueType cm_type;
+ QofSetterFunc cm_setter;
+ const QofParam *cm_param;
+ void (*string_setter) (QofEntity*, const char*);
+ void (*date_setter) (QofEntity*, Timespec);
+ void (*numeric_setter) (QofEntity*, gnc_numeric);
+ void (*double_setter) (QofEntity*, double);
+ void (*boolean_setter) (QofEntity*, gboolean);
+ void (*i32_setter) (QofEntity*, gint32);
+ void (*i64_setter) (QofEntity*, gint64);
+ void (*char_setter) (QofEntity*, char);
+ void (*kvp_frame_setter) (QofEntity*, KvpFrame*);
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(value != NULL);
+ params = (qsf_param*)data;
+ node = (xmlNodePtr)value;
+ parameter_name = (const char*)key;
+ qof_type = (char*)node->name;
+ qsf_ent = params->qsf_ent;
+ targetBook = params->book;
+ memset (&qsf_time, '\0', sizeof(qsf_time));
+ cm_date.tv_nsec = 0;
+ cm_date.tv_sec = 0;
+ obj_type = (char*)xmlGetProp(node->parent, BAD_CAST QSF_OBJECT_TYPE);
+ if(0 == safe_strcasecmp(obj_type, parameter_name)) { return; }
+ cm_setter = qof_class_get_parameter_setter(obj_type, parameter_name);
+ cm_param = qof_class_get_parameter(obj_type, parameter_name);
+ object_set = params->object_set;
+ if(safe_strcmp(qof_type, QOF_TYPE_STRING) == 0) {
+ string_setter = (void(*)(QofEntity*, const char*))cm_setter;
+ if(string_setter != NULL) { string_setter(qsf_ent, (char*)xmlNodeGetContent(node)); }
+ }
+ if(safe_strcmp(qof_type, QOF_TYPE_DATE) == 0) {
+ date_setter = (void(*)(QofEntity*, Timespec))cm_setter;
+ timechk = NULL;
+ timechk = strptime((char*)xmlNodeGetContent(node), QSF_XSD_TIME, &qsf_time);
+ g_return_if_fail(timechk != NULL);
+ qsf_time_t = mktime(&qsf_time);
+ timespecFromTime_t(&cm_date, qsf_time_t);
+ if(date_setter != NULL) { date_setter(qsf_ent, cm_date); }
+ }
+ if((safe_strcmp(qof_type, QOF_TYPE_NUMERIC) == 0) ||
+ (safe_strcmp(qof_type, QOF_TYPE_DEBCRED) == 0)) {
+ numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_setter;
+ string_to_gnc_numeric((char*)xmlNodeGetContent(node), &cm_numeric);
+ if(numeric_setter != NULL) { numeric_setter(qsf_ent, cm_numeric); }
+ }
+ if(safe_strcmp(qof_type, QOF_TYPE_GUID) == 0) {
+ cm_guid = g_new(GUID, 1);
+ if(TRUE != string_to_guid((char*)xmlNodeGetContent(node), cm_guid))
+ {
+ qof_backend_set_error(params->be, ERR_QSF_BAD_OBJ_GUID);
+ PINFO (" string to guid conversion failed for %s:%s:%s",
+ xmlNodeGetContent(node), obj_type, qof_type);
+ return;
+ }
+ reference_type = (char*)xmlGetProp(node, BAD_CAST QSF_OBJECT_TYPE);
+ if(0 == safe_strcmp(QOF_PARAM_GUID, reference_type))
+ {
+ qof_entity_set_guid(qsf_ent, cm_guid);
+ }
+ else {
+ reference = qof_entity_get_reference_from(qsf_ent, cm_param);
+ if(reference) {
+ params->referenceList = g_list_append(params->referenceList, reference);
+ }
+ }
+ }
+ if(safe_strcmp(qof_type, QOF_TYPE_INT32) == 0) {
+ errno = 0;
+ cm_i32 = (gint32)strtol ((char*)xmlNodeGetContent(node), &tail, 0);
+ if(errno == 0) {
+ i32_setter = (void(*)(QofEntity*, gint32))cm_setter;
+ if(i32_setter != NULL) { i32_setter(qsf_ent, cm_i32); }
+ }
+ else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
+ }
+ if(safe_strcmp(qof_type, QOF_TYPE_INT64) == 0) {
+ errno = 0;
+ cm_i64 = strtoll((char*)xmlNodeGetContent(node), &tail, 0);
+ if(errno == 0) {
+ i64_setter = (void(*)(QofEntity*, gint64))cm_setter;
+ if(i64_setter != NULL) { i64_setter(qsf_ent, cm_i64); }
+ }
+ else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
+ }
+ if(safe_strcmp(qof_type, QOF_TYPE_DOUBLE) == 0) {
+ errno = 0;
+ cm_double = strtod((char*)xmlNodeGetContent(node), &tail);
+ if(errno == 0) {
+ double_setter = (void(*)(QofEntity*, double))cm_setter;
+ if(double_setter != NULL) { double_setter(qsf_ent, cm_double); }
+ }
+ }
+ if(safe_strcmp(qof_type, QOF_TYPE_BOOLEAN) == 0){
+ if(0 == safe_strcasecmp((char*)xmlNodeGetContent(node), QSF_XML_BOOLEAN_TEST)) {
+ cm_boolean = TRUE;
+ }
+ else { cm_boolean = FALSE; }
+ boolean_setter = (void(*)(QofEntity*, gboolean))cm_setter;
+ if(boolean_setter != NULL) { boolean_setter(qsf_ent, cm_boolean); }
+ }
+ if(safe_strcmp(qof_type, QOF_TYPE_KVP) == 0) {
+ cm_type = qsf_to_kvp_helper((char*)xmlGetProp(node, BAD_CAST QSF_OBJECT_VALUE));
+ if(!cm_type) { return; }
+ cm_value = string_to_kvp_value((char*)xmlNodeGetContent(node), cm_type);
+ cm_kvp = kvp_frame_copy(cm_param->param_getfcn(qsf_ent, cm_param));
+ cm_kvp = kvp_frame_set_value(cm_kvp, (char*)xmlGetProp(node, BAD_CAST QSF_OBJECT_KVP), cm_value);
+ kvp_frame_setter = (void(*)(QofEntity*, KvpFrame*))cm_setter;
+ if(kvp_frame_setter != NULL) { kvp_frame_setter(qsf_ent, cm_kvp); }
+ }
+ if(safe_strcmp(qof_type, QOF_TYPE_COLLECT) == 0) {
+ QofCollection *qsf_coll;
+ QofIdType type;
+ QofEntityReference *reference;
+ QofParam *copy_param;
+ /* retrieve the *type* of the collection, ignore any contents. */
+ qsf_coll = cm_param->param_getfcn(qsf_ent, cm_param);
+ type = qof_collection_get_type(qsf_coll);
+ cm_guid = g_new(GUID, 1);
+ if(TRUE != string_to_guid((char*)xmlNodeGetContent(node), cm_guid))
+ {
+ qof_backend_set_error(params->be, ERR_QSF_BAD_OBJ_GUID);
+ PINFO (" string to guid collect failed for %s", xmlNodeGetContent(node));
+ return;
+ }
+ // create a QofEntityReference with this type and GUID.
+ // there is only one entity each time.
+ // cm_guid contains the GUID of the reference.
+ // type is the type of the reference.
+ reference = g_new0(QofEntityReference, 1);
+ reference->type = g_strdup(qsf_ent->e_type);
+ reference->ref_guid = cm_guid;
+ reference->ent_guid = &qsf_ent->guid;
+ copy_param = g_new0(QofParam, 1);
+ copy_param->param_name = g_strdup(cm_param->param_name);
+ copy_param->param_type = g_strdup(cm_param->param_type);
+ reference->param = copy_param;
+ params->referenceList = g_list_append(params->referenceList, reference);
+ }
+ if(safe_strcmp(qof_type, QOF_TYPE_CHAR) == 0) {
+ char_getter = (char (*)(xmlNodePtr))xmlNodeGetContent;
+ cm_char = char_getter(node);
+ char_setter = (void(*)(QofEntity*, char))cm_setter;
+ if(char_setter != NULL) { char_setter(qsf_ent, cm_char); }
+ }
+}
+
+QofBackend*
+qsf_backend_new(void)
+{
+ QSFBackend *qsf_be;
+ QofBackend *be;
+
+ qsf_be = g_new0(QSFBackend, 1);
+ be = (QofBackend*) qsf_be;
+ qof_backend_init(be);
+ qsf_be->params = g_new(qsf_param, 1);
+ qsf_be->params->be = be;
+ qsf_param_init(qsf_be->params);
+ qsf_be->be.session_begin = qsf_session_begin;
+
+ be->session_end = qsf_session_end;
+ be->destroy_backend = qsf_destroy_backend;
+ be->load = qsf_file_type;
+ be->save_may_clobber_data = NULL;
+ /* The QSF backend will always load and save the entire QSF XML file. */
+ be->begin = NULL;
+ be->commit = NULL;
+ be->rollback = NULL;
+ /* QSF uses the built-in SQL, not a dedicated SQL server. */
+ be->compile_query = NULL;
+ be->free_query = NULL;
+ be->run_query = NULL;
+ be->counter = NULL;
+ /* The QSF backend is not multi-user. */
+ be->events_pending = NULL;
+ be->process_events = NULL;
+
+ be->sync = qsf_write_file;
+ /* use for maps, later. */
+ be->load_config = qsf_load_config;
+ be->get_config = qsf_get_config;
+
+ qsf_be->fullpath = NULL;
+ return be;
+}
+
+/** \brief The QOF method of loading each backend.
+
+QSF does not use a GnuCash module, it is loaded using the QOF
+method - QofBackendProvider.
+*/
+static void
+qsf_provider_free (QofBackendProvider *prov)
+{
+ prov->provider_name = NULL;
+ prov->access_method = NULL;
+ g_free (prov);
+}
+
+/* although we call gettext here, none of the
+QofBackendProvider strings are translatable. */
+void
+qsf_provider_init(void)
+{
+ QofBackendProvider *prov;
+
+ #ifdef ENABLE_NLS
+ setlocale (LC_ALL, "");
+ bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+ #endif
+ prov = g_new0 (QofBackendProvider, 1);
+ prov->provider_name = "QSF Backend Version 0.1";
+ prov->access_method = "file";
+ prov->partial_book_supported = TRUE;
+ prov->backend_new = qsf_backend_new;
+ prov->check_data_type = qsf_determine_file_type;
+ prov->provider_free = qsf_provider_free;
+ qof_backend_register_provider (prov);
+}
Added: gnucash/trunk/lib/libqof/backend/file/qsf-dir.h.in
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-dir.h.in 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-dir.h.in 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,26 @@
+/***************************************************************************
+ * qsf-xml.h.in
+ *
+ * Mon Dec 20 20:27:48 2004
+ * Copyright 2004 Neil Williams
+ * linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define QSF_SCHEMA_DIR "@-QSF_SCHEMA_DIR-@"
+
Added: gnucash/trunk/lib/libqof/backend/file/qsf-map.xsd.xml
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-map.xsd.xml 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-map.xsd.xml 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,116 @@
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+targetNamespace="http://qof.sourceforge.net/"
+xmlns:qsf-map="http://qof.sourceforge.net/">
+<xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Query Object Framework Serialization Format (QSF)
+ Copyright 2004 Neil Williams linux at codehelp.co.uk
+ QSF is part of QOF.
+ QOF is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc., 59
+ Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ </xsd:documentation>
+ </xsd:annotation>
+<xsd:element name="qsf-map" qsf-map:type="map-type"/>
+<xsd:complexType name="map-type">
+ <xsd:sequence>
+ <xsd:element name="definition" qsf-map:type="qsfdefinition"/>
+ <xsd:element name="object" qsf-map:type="mapobject" minOccurs="1" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+<xsd:complexType name="qsfdefinition">
+ <xsd:sequence>
+ <xsd:element name="define" qsf-map:type="qsfdefine" minOccurs="2" maxOccurs="unbounded"/>
+ <xsd:element name="default" qsf-map:type="qsfdefault" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="variable" qsf-map:type="qsfvariable" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="qof_version" type="xsd:positiveInteger"/>
+</xsd:complexType>
+<xsd:complexType name="qsfdefine">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="e_type" type="xsd:string"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="qsfdefault">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="name" type="xsd:string"/>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="qsfvariable">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="name" type="xsd:string"/>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="mapobject">
+ <xsd:sequence>
+ <xsd:element name="calculate" qsf-map:type="qsfcalculate" minOccurs="1" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+</xsd:complexType>
+<xsd:complexType name="qsfcalculate">
+ <xsd:sequence>
+ <xsd:element name="set" qsf-map:type="qsf_set" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="if" qsf-map:type="qsf_if" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="equals" qsf-map:type="qsf_equal" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="else" qsf-map:type="qsf_else" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+</xsd:complexType>
+<xsd:complexType name="qsf_set">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="option" type="xsd:string" use="optional"/>
+ <xsd:attribute name="format" type="xsd:string" use="optional"/>
+ <xsd:attribute name="object" type="xsd:string" use="optional"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+</xsd:complexType>
+<xsd:complexType name="qsf_if">
+ <xsd:sequence>
+ <xsd:element name="set" qsf-map:type="qsf_set" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="equals" qsf-map:type="qsf_equal" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="boolean" type="xsd:string" use="optional"/>
+ <xsd:attribute name="type" type="xsd:string" use="optional"/>
+ <xsd:attribute name="value" type="xsd:string" use="optional"/>
+ <xsd:attribute name="string" type="xsd:string" use="optional"/>
+</xsd:complexType>
+<xsd:complexType name="qsf_else">
+ <xsd:sequence>
+ <xsd:element name="set" qsf-map:type="qsf_set" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="equals" qsf-map:type="qsf_equal" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+</xsd:complexType>
+<xsd:complexType name="qsf_equal">
+ <xsd:sequence>
+ <xsd:element name="set" qsf-map:type="qsf_set" minOccurs="0" maxOccurs="1"/>
+ <xsd:element name="equals" qsf-map:type="qsf_equal" minOccurs="0" maxOccurs="1"/>
+ </xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string"/>
+ <xsd:attribute name="value" type="xsd:string"/>
+</xsd:complexType>
+
+</xsd:schema>
+
Added: gnucash/trunk/lib/libqof/backend/file/qsf-object.xsd.xml
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-object.xsd.xml 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-object.xsd.xml 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,154 @@
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+targetNamespace="http://qof.sourceforge.net/"
+xmlns:qof-qsf="http://qof.sourceforge.net/"
+elementFormDefault="qualified"
+attributeFormDefault="unqualified" xml:lang="en-GB">
+ <xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Query Object Framework Serialization Format (QSF)
+ Copyright 2004-5 Neil Williams linux at codehelp.co.uk
+ QSF is part of QOF.
+ QOF is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc., 59
+ Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ </xsd:documentation>
+ </xsd:annotation>
+<xsd:element name="qof-qsf" type="qof-qsf:qsftype"/>
+<xsd:complexType name="qsftype">
+ <xsd:sequence>
+ <xsd:element name="book" type="qof-qsf:qofbook" minOccurs="1" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+<xsd:complexType name="qofbook">
+ <xsd:sequence>
+ <xsd:element name="book-guid" type="xsd:string" minOccurs="1" maxOccurs="1"/>
+ <xsd:element name="object" type="qof-qsf:qsfobject" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="count" type="xsd:positiveInteger" use="optional"/>
+</xsd:complexType>
+<xsd:complexType name="qsfobject">
+ <xsd:sequence>
+ <xsd:element name="string" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="guid" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:hexBinary">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="boolean" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:boolean">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="numeric" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="date" minOccurs="0" maxOccurs="unbounded" >
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:dateTime">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="gint32" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:int">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="gint64" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:long">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="double" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:double">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="character" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="kvp" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ <xsd:attribute name="path" type="xsd:string" use="required"/>
+ <xsd:attribute name="value" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="collection" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:hexBinary">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="choice" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:hexBinary">
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+</xsd:sequence>
+ <xsd:attribute name="type" type="xsd:string" use="required"/>
+ <xsd:attribute name="count" type="xsd:positiveInteger" use="optional"/>
+</xsd:complexType>
+</xsd:schema>
Added: gnucash/trunk/lib/libqof/backend/file/qsf-xml-map.c
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-xml-map.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-xml-map.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,711 @@
+/***************************************************************************
+ * qsf-xml-map.c
+ *
+ * Sat Jan 1 07:31:55 2005
+ * Copyright 2005 Neil Williams
+ * linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+
+#include <libxml/xmlversion.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlschemas.h>
+#include "qof-backend-qsf.h"
+#include "qsf-xml.h"
+#include "qsf-dir.h"
+
+static void
+qsf_date_default_handler(const char *default_name, GHashTable *qsf_default_hash,
+ xmlNodePtr parent_tag, xmlNodePtr import_node, xmlNsPtr ns)
+{
+ xmlNodePtr output_parent;
+ time_t *qsf_time;
+ char date_as_string[QSF_DATE_LENGTH];
+
+ output_parent = xmlAddChild(parent_tag, xmlNewNode(ns,
+ xmlGetProp(import_node, BAD_CAST QSF_OBJECT_TYPE)));
+ xmlNewProp(output_parent, BAD_CAST QSF_OBJECT_TYPE,
+ xmlGetProp(import_node, BAD_CAST MAP_VALUE_ATTR));
+ qsf_time = (time_t*)g_hash_table_lookup(qsf_default_hash, default_name);
+ strftime(date_as_string, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(qsf_time));
+ xmlNodeAddContent(output_parent, BAD_CAST date_as_string);
+}
+
+static void
+qsf_string_default_handler(const char *default_name, GHashTable *qsf_default_hash,
+ xmlNodePtr parent_tag, xmlNodePtr import_node, xmlNsPtr ns)
+{
+ xmlNodePtr node;
+ xmlChar *output;
+
+ node = xmlAddChild(parent_tag,
+ xmlNewNode(ns, xmlGetProp(import_node, BAD_CAST QSF_OBJECT_TYPE)));
+ xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE,
+ xmlGetProp(import_node, BAD_CAST MAP_VALUE_ATTR));
+ output = (xmlChar *)g_hash_table_lookup(qsf_default_hash, default_name);
+ xmlNodeAddContent(node, output);
+}
+
+void
+qsf_map_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid)
+{
+ xmlChar *qof_version, *match;
+ GString *buff;
+ xmlNodePtr child_node;
+
+ if (qsf_is_element(child, ns, MAP_DEFINITION_TAG)) {
+ qof_version = xmlGetProp(child, BAD_CAST MAP_QOF_VERSION);
+ buff = g_string_new(" ");
+ g_string_printf(buff, "%i", QSF_QOF_VERSION);
+ if(xmlStrcmp(qof_version, BAD_CAST buff->str) != 0)
+ {
+ valid->error_state = ERR_QSF_BAD_QOF_VERSION;
+ return;
+ }
+ for(child_node = child->children; child_node != NULL;
+ child_node = child_node->next)
+ {
+ if (qsf_is_element(child_node, ns, MAP_DEFINE_TAG)) {
+ g_hash_table_insert(valid->validation_table,
+ xmlGetProp(child_node, BAD_CAST MAP_E_TYPE),
+ xmlNodeGetContent(child_node));
+ }
+ }
+ }
+ if(qsf_is_element(child, ns, MAP_OBJECT_TAG)) {
+ match = NULL;
+ match = BAD_CAST g_hash_table_lookup( valid->validation_table,
+ xmlGetProp(child, BAD_CAST MAP_TYPE_ATTR));
+ if(match) {
+ valid->map_calculated_count++;
+ }
+ }
+}
+
+gboolean is_qsf_object_with_map_be(char *map_file, qsf_param *params)
+{
+ xmlDocPtr doc, map_doc;
+ int valid_count;
+ struct qsf_node_iterate iter;
+ xmlNodePtr map_root, object_root;
+ xmlNsPtr map_ns;
+ qsf_validator valid;
+ char *path;
+ gchar *map_path;
+
+ g_return_val_if_fail((params != NULL),FALSE);
+ path = g_strdup(params->filepath);
+ map_path = g_strdup_printf("%s/%s", QSF_SCHEMA_DIR, map_file);
+ if(path == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+ return FALSE;
+ }
+ doc = xmlParseFile(path);
+ if(doc == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+ return FALSE;
+ }
+ if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) {
+ qof_backend_set_error(params->be, ERR_QSF_INVALID_OBJ);
+ return FALSE;
+ }
+ object_root = xmlDocGetRootElement(doc);
+ if(map_path == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+ return FALSE;
+ }
+ valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+ map_doc = xmlParseFile(map_path);
+ if(map_doc == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+ return FALSE;
+ }
+ if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, map_doc)) {
+ qof_backend_set_error(params->be, ERR_QSF_INVALID_MAP);
+ return FALSE;
+ }
+ map_root = xmlDocGetRootElement(map_doc);
+ valid.map_calculated_count = 0;
+ valid.valid_object_count = 0;
+ valid.qof_registered_count = 0;
+ valid.error_state = ERR_BACKEND_NO_ERR;
+ map_ns = map_root->ns;
+ iter.ns = object_root->ns;
+ qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+ iter.ns = map_ns;
+ qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+ if (valid.error_state != ERR_BACKEND_NO_ERR) {
+ qof_backend_set_error(params->be, valid.error_state);
+ g_hash_table_destroy(valid.validation_table);
+ return FALSE;
+ }
+ valid_count = 0 - g_hash_table_size(valid.validation_table);
+ valid_count += valid.map_calculated_count;
+ valid_count += valid.valid_object_count;
+ g_hash_table_destroy(valid.validation_table);
+ if(valid_count == 0) {
+ qof_backend_get_error(params->be);
+ return TRUE;
+ }
+ qof_backend_set_error(params->be, ERR_QSF_WRONG_MAP);
+ /* the object is OK, only the map is wrong. */
+ return TRUE;
+}
+
+gboolean is_qsf_object_with_map(const char *path, char *map_file)
+{
+ xmlDocPtr doc, map_doc;
+ int valid_count;
+ struct qsf_node_iterate iter;
+ xmlNodePtr map_root, object_root;
+ xmlNsPtr map_ns;
+ qsf_validator valid;
+ gchar *map_path;
+
+ map_path = g_strdup_printf("%s/%s", QSF_SCHEMA_DIR, map_file);
+ if(path == NULL) {
+ return FALSE;
+ }
+ doc = xmlParseFile(path);
+ if(doc == NULL) {
+ return FALSE;
+ }
+ if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) {
+ return FALSE;
+ }
+ object_root = xmlDocGetRootElement(doc);
+ if(map_path == NULL) {
+ return FALSE;
+ }
+ valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+ map_doc = xmlParseFile(map_path);
+ if(map_doc == NULL) {
+ return FALSE;
+ }
+ if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, map_doc)) {
+ return FALSE;
+ }
+ map_root = xmlDocGetRootElement(map_doc);
+ valid.map_calculated_count = 0;
+ valid.valid_object_count = 0;
+ valid.error_state = ERR_BACKEND_NO_ERR;
+ map_ns = map_root->ns;
+ iter.ns = map_ns;
+ qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+ iter.ns = object_root->ns;
+ qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+ if (valid.error_state != ERR_BACKEND_NO_ERR) {
+ g_hash_table_destroy(valid.validation_table);
+ return FALSE;
+ }
+ valid_count = 0 - g_hash_table_size(valid.validation_table);
+ valid_count += valid.map_calculated_count;
+ valid_count += valid.valid_object_count;
+ g_hash_table_destroy(valid.validation_table);
+ if(valid_count == 0) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+gboolean is_qsf_map_be(qsf_param *params)
+{
+ xmlDocPtr doc;
+ struct qsf_node_iterate iter;
+ qsf_validator valid;
+ xmlNodePtr map_root;
+ xmlNsPtr map_ns;
+ char *path;
+
+ g_return_val_if_fail((params != NULL),FALSE);
+ qof_backend_get_error(params->be);
+ path = g_strdup(params->filepath);
+ if(path == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+ return FALSE;
+ }
+ doc = xmlParseFile(path);
+ if(doc == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+ return FALSE;
+ }
+ if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, doc)) {
+ qof_backend_set_error(params->be, ERR_QSF_INVALID_MAP);
+ return FALSE;
+ }
+ map_root = xmlDocGetRootElement(doc);
+ map_ns = map_root->ns;
+ iter.ns = map_ns;
+ valid.error_state = ERR_BACKEND_NO_ERR;
+ qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+ if (valid.error_state != ERR_BACKEND_NO_ERR) {
+ qof_backend_set_error(params->be, valid.error_state);
+ g_hash_table_destroy(valid.validation_table);
+ return FALSE;
+ }
+ qof_backend_get_error(params->be);
+ g_hash_table_destroy(valid.validation_table);
+ return TRUE;
+}
+
+gboolean is_qsf_map(const char *path)
+{
+ xmlDocPtr doc;
+ struct qsf_node_iterate iter;
+ qsf_validator valid;
+ xmlNodePtr map_root;
+ xmlNsPtr map_ns;
+
+ g_return_val_if_fail((path != NULL),FALSE);
+ if(path == NULL) { return FALSE; }
+ doc = xmlParseFile(path);
+ if(doc == NULL) { return FALSE; }
+ if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_MAP_SCHEMA, doc)) {
+ return FALSE;
+ }
+ map_root = xmlDocGetRootElement(doc);
+ map_ns = map_root->ns;
+ iter.ns = map_ns;
+ valid.error_state = ERR_BACKEND_NO_ERR;
+ qsf_valid_foreach(map_root, qsf_map_validation_handler, &iter, &valid);
+ if (valid.error_state != ERR_BACKEND_NO_ERR) {
+ g_hash_table_destroy(valid.validation_table);
+ return FALSE;
+ }
+ g_hash_table_destroy(valid.validation_table);
+ return TRUE;
+}
+
+
+static void
+qsf_map_default_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params )
+{
+ xmlChar *qsf_enum;
+
+ g_return_if_fail(params->qsf_define_hash != NULL);
+ if (qsf_is_element(child, ns, MAP_DEFINE_TAG)) {
+ if(NULL == g_hash_table_lookup(params->qsf_define_hash,
+ xmlGetProp(child, BAD_CAST MAP_E_TYPE)))
+ {
+ g_hash_table_insert(params->qsf_define_hash,
+ xmlGetProp(child, BAD_CAST MAP_E_TYPE), params->child_node);
+ }
+ else {
+ qof_backend_set_error(params->be, ERR_QSF_BAD_MAP);
+ return;
+ }
+ }
+ if(qsf_is_element(child, ns, MAP_DEFAULT_TAG)) {
+ if(qsf_strings_equal(xmlGetProp(child, BAD_CAST MAP_TYPE_ATTR), MAP_ENUM_TYPE))
+ {
+ qsf_enum = xmlNodeGetContent(child);
+ /** Use content to discriminate enums in QOF */
+ /** \todo FIXME: the default enum value is not used
+ implemented properly or fully handled.
+ */
+ if(NULL == g_hash_table_lookup(params->qsf_default_hash,
+ xmlNodeGetContent(child)))
+ {
+ g_hash_table_insert(params->qsf_default_hash,
+ xmlNodeGetContent(child), child);
+ }
+ else
+ {
+ qof_backend_set_error(params->be, ERR_QSF_BAD_MAP);
+ return;
+ }
+ }
+ /** Non-enum defaults */
+ else {
+ if(NULL == g_hash_table_lookup(params->qsf_default_hash,
+ xmlGetProp(child, BAD_CAST MAP_NAME_ATTR)))
+ {
+ g_hash_table_insert(params->qsf_default_hash,
+ xmlGetProp(child, BAD_CAST MAP_NAME_ATTR), child);
+ }
+ else
+/* if(0 != xmlHashAddEntry(params->default_map,
+ xmlGetProp(child_node, MAP_NAME_ATTR), child_node))*/
+ {
+ qof_backend_set_error(params->be, ERR_QSF_BAD_MAP);
+ return;
+ }
+ }
+ }
+}
+
+void
+qsf_map_top_node_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params)
+{
+ xmlChar *qof_version;
+ GString *buff;
+ struct qsf_node_iterate iter;
+
+ if(!params->qsf_define_hash) return;
+ if(!params->qsf_default_hash) return;
+ if(qsf_is_element(child, ns, MAP_DEFINITION_TAG)) {
+ qof_version = xmlGetProp(child, BAD_CAST MAP_QOF_VERSION);
+ buff = g_string_new(" ");
+ g_string_printf(buff, "%i", QSF_QOF_VERSION);
+ if(xmlStrcmp(qof_version, BAD_CAST buff->str) != 0) {
+ qof_backend_set_error(params->be, ERR_QSF_BAD_QOF_VERSION);
+ return;
+ }
+ iter.ns = ns;
+ qsf_node_foreach(child, qsf_map_default_handler, &iter, params);
+ }
+}
+
+static char*
+qsf_else_set_value(xmlNodePtr parent, GHashTable *default_hash,
+ char *content, xmlNsPtr map_ns)
+{
+ xmlNodePtr cur_node;
+
+ content = NULL;
+ for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+ {
+ if(qsf_is_element(cur_node, map_ns, QSF_CONDITIONAL_SET)) {
+ content = (char*)xmlNodeGetContent(cur_node);
+ return content;
+ }
+ }
+ return NULL;
+}
+
+/* Handles the set tag in the map.
+This function will be overhauled once inside QOF
+QOF hook required for "Lookup in the receiving application"
+*/
+static char*
+qsf_set_handler(xmlNodePtr parent, GHashTable *default_hash,
+ char *content, qsf_param *params)
+{
+ xmlNodePtr cur_node, lookup_node;
+
+ content = NULL;
+ for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+ {
+ if(qsf_is_element(cur_node, params->map_ns, QSF_CONDITIONAL_SET))
+ {
+ content = (char*)xmlGetProp(cur_node, BAD_CAST QSF_OPTION);
+ if(qsf_strings_equal(xmlGetProp(cur_node, BAD_CAST QSF_OPTION), "qsf_lookup_string"))
+ {
+ lookup_node = (xmlNodePtr) g_hash_table_lookup(default_hash,
+ xmlNodeGetContent(cur_node));
+ content = (char*)xmlGetProp(lookup_node, BAD_CAST MAP_VALUE_ATTR);
+ /** \todo FIXME: do the lookup. type is defined by output object. */
+ /* Find by name, get GUID, return GUID as string. */
+ g_message("Lookup %s in the receiving application\n", content );
+ return content;
+ }
+ if(content)
+ {
+ lookup_node = (xmlNodePtr) g_hash_table_lookup(default_hash,
+ xmlNodeGetContent(cur_node));
+ content = (char*)xmlGetProp(lookup_node, BAD_CAST "value");
+ return content;
+ }
+ content = (char*)xmlGetProp(parent, BAD_CAST "boolean");
+ if(!content) {
+ /** \todo Check qsf_parameter_hash arguments */
+ lookup_node = (xmlNodePtr) g_hash_table_lookup(params->qsf_parameter_hash,
+ xmlGetProp(parent->parent, BAD_CAST MAP_TYPE_ATTR));
+ if(lookup_node) { return (char*)xmlNodeGetContent(lookup_node); }
+ return (char*)xmlNodeGetContent(cur_node);
+ }
+ }
+ }
+ return NULL;
+}
+
+static void
+qsf_calculate_else(xmlNodePtr param_node, xmlNodePtr child, qsf_param *params)
+{
+ xmlNodePtr export_node;
+ xmlChar *output_content, *object_data;
+
+ if(qsf_is_element(param_node, params->map_ns, QSF_CONDITIONAL_ELSE)) {
+ if(params->boolean_calculation_done == 0) {
+ output_content = object_data = NULL;
+ output_content = BAD_CAST qsf_set_handler(param_node,
+ params->qsf_default_hash, (char*)output_content, params);
+ if(output_content == NULL) {
+ output_content = xmlGetProp(param_node, BAD_CAST MAP_TYPE_ATTR);
+ object_data = BAD_CAST qsf_else_set_value(param_node, params->qsf_default_hash,
+ (char*)output_content, params->map_ns);
+ output_content = BAD_CAST xmlGetProp( (xmlNodePtr) g_hash_table_lookup(
+ params->qsf_default_hash, object_data), BAD_CAST MAP_VALUE_ATTR);
+ }
+ if(object_data != NULL) {
+ export_node =(xmlNodePtr) g_hash_table_lookup(
+ params->qsf_parameter_hash,
+ xmlGetProp(params->child_node, BAD_CAST QSF_OBJECT_TYPE));
+ object_data = xmlNodeGetContent(export_node);
+ }
+ if(output_content != NULL) { object_data = output_content; }
+ export_node = xmlAddChild(params->lister, xmlNewNode(params->qsf_ns,
+ xmlGetProp(child, BAD_CAST QSF_OBJECT_TYPE)));
+ xmlNewProp(export_node, BAD_CAST QSF_OBJECT_TYPE,
+ xmlGetProp(child, BAD_CAST MAP_VALUE_ATTR));
+ xmlNodeAddContent(export_node, object_data);
+ params->boolean_calculation_done = 1;
+ }
+ }
+}
+
+static void
+qsf_set_format_value(xmlChar *format, char *qsf_time_now_as_string,
+ xmlNodePtr cur_node, qsf_param *params)
+{
+ int result;
+ xmlChar *content;
+ time_t *output;
+ struct tm *tmp;
+ time_t tester;
+ xmlNodePtr kl;
+ regex_t reg;
+
+ /** Comments retained - this behaves a little strangely */
+
+ result = 0;
+ if(format == NULL) { return; }
+ content = xmlNodeGetContent(cur_node);
+ output = (time_t*) g_hash_table_lookup(params->qsf_default_hash, content);
+ if(!output) {
+ /** No default time set, use the object time */
+ /** fill the time structs with temp data */
+ tester = time(NULL);
+ tmp = gmtime(&tester);
+ /** Lookup the object value to read */
+ /** \todo qsf_parameter_hash check correct arguments */
+ kl = (xmlNodePtr) g_hash_table_lookup(params->qsf_parameter_hash, content);
+ if(!kl) {
+ printf("no suitable date set.\n");
+ return;
+ }
+ /** Read the object value as a dateTime */
+ strptime((char*)xmlNodeGetContent(kl), QSF_XSD_TIME, tmp);
+ if(!tmp) {
+ printf("empty date field in QSF object.\n");
+ return;
+ }
+ tester = mktime(tmp);
+ output = &tester;
+ }
+ result = regcomp(®, "%[a-zA-Z]", REG_EXTENDED|REG_NOSUB);
+ result = regexec(®, (char*)format,(size_t)0,NULL,0);
+ if(result == REG_NOMATCH) { format = BAD_CAST "%F"; }
+ regfree(®);
+ /** QSF_DATE_LENGTH preset for all internal and QSF_XSD_TIME string formats.
+ */
+ strftime(qsf_time_now_as_string, QSF_DATE_LENGTH, (char*)format, gmtime(output));
+}
+
+static void
+qsf_boolean_set_value(xmlNodePtr parent, qsf_param *params,
+ char *content, xmlNsPtr map_ns)
+{
+ xmlNodePtr cur_node;
+ xmlChar *boolean_name;
+
+ boolean_name = NULL;
+ for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next) {
+ if(qsf_is_element(cur_node, map_ns, QSF_CONDITIONAL_SET)) {
+ boolean_name = xmlGetProp(cur_node, BAD_CAST QSF_FORMATTING_OPTION);
+ qsf_set_format_value(boolean_name, content, cur_node, params);
+ }
+ }
+}
+
+static void
+qsf_calculate_conditional(xmlNodePtr param_node, xmlNodePtr child, qsf_param *params)
+{
+ xmlNodePtr export_node;
+ xmlChar *output_content;
+
+ output_content = NULL;
+ if(qsf_is_element(param_node, params->map_ns, QSF_CONDITIONAL)) {
+ printf("param_node=%s\n", param_node->name);
+ if(params->boolean_calculation_done == 0) {
+ /* set handler */
+ output_content = BAD_CAST qsf_set_handler(param_node, params->qsf_default_hash,
+ (char*)output_content, params);
+ /* If the 'if' contains a boolean that has a default value */
+ if(output_content == NULL) {
+ if(NULL != xmlGetProp(param_node, BAD_CAST QSF_BOOLEAN_DEFAULT)) {
+ output_content = xmlGetProp( (xmlNodePtr) g_hash_table_lookup(
+ params->qsf_default_hash, xmlGetProp(param_node,
+ BAD_CAST QSF_BOOLEAN_DEFAULT) ), BAD_CAST MAP_VALUE_ATTR);
+ }
+ /* Is the default set to true? */
+ if( 0 == qsf_compare_tag_strings(output_content, QSF_XML_BOOLEAN_TEST))
+ {
+ qsf_boolean_set_value(param_node, params, (char*)output_content, params->map_ns);
+ export_node = xmlAddChild(params->lister, xmlNewNode(params->qsf_ns,
+ xmlGetProp(child, BAD_CAST QSF_OBJECT_TYPE)));
+ xmlNewProp(export_node, BAD_CAST QSF_OBJECT_TYPE,
+ xmlGetProp(child, BAD_CAST MAP_VALUE_ATTR));
+ xmlNodeAddContent(export_node, output_content);
+ params->boolean_calculation_done = 1;
+ }
+ }
+ }
+ }
+}
+
+static xmlNodePtr
+qsf_add_object_tag(qsf_param *params, int count)
+{
+ xmlNodePtr extra_node;
+ GString *str;
+ xmlChar *property;
+
+ str = g_string_new (" ");
+ g_string_printf(str, "%i", count);
+ extra_node = NULL;
+ extra_node = xmlAddChild(params->output_node,
+ xmlNewNode(params->qsf_ns, BAD_CAST QSF_OBJECT_TAG));
+ xmlNewProp(extra_node, BAD_CAST QSF_OBJECT_TYPE,
+ xmlGetProp(params->cur_node, BAD_CAST QSF_OBJECT_TYPE));
+ property = xmlCharStrdup(str->str);
+ xmlNewProp(extra_node, BAD_CAST QSF_OBJECT_COUNT, property);
+ return extra_node;
+}
+
+void
+qsf_map_object_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params)
+{
+ xmlNodePtr param_node, export_node;
+ xmlNsPtr map_ns, qsf_ns;
+ xmlChar *output_content;
+ int result, count;
+
+ map_ns = ns;
+ qsf_ns = params->qsf_ns;
+ param_node = NULL;
+ result = 0;
+ count = params->count;
+ if(child == NULL) { return; }
+ if(ns == NULL) { return; }
+ params->boolean_calculation_done = 0;
+
+ if(qsf_is_element(child, map_ns, MAP_CALCULATE_TAG)) {
+ params->boolean_calculation_done = 0;
+ for(param_node = child->children; param_node != NULL;
+ param_node = param_node->next)
+ {
+ output_content = NULL;
+ export_node = NULL;
+ if(!params->lister) { params->lister = qsf_add_object_tag(params, count); }
+ if(qsf_is_element(param_node, map_ns, "set"))
+ {
+ /* Map the pre-defined defaults */
+ if(0 == qsf_compare_tag_strings(
+ xmlNodeGetContent(param_node), "qsf_enquiry_date"))
+ {
+ qsf_string_default_handler("qsf_enquiry_date",
+ params->qsf_default_hash, params->lister, child, qsf_ns);
+ }
+ if(0 == qsf_compare_tag_strings(
+ xmlNodeGetContent(param_node), "qsf_time_now"))
+ {
+ qsf_date_default_handler("qsf_time_now",
+ params->qsf_default_hash, params->lister, child, qsf_ns);
+ }
+ if(0 == qsf_compare_tag_strings(
+ xmlNodeGetContent(param_node), "qsf_time_string"))
+ {
+ qsf_string_default_handler("qsf_time_string",
+ params->qsf_default_hash, params->lister, child, qsf_ns);
+ }
+ output_content = xmlNodeGetContent(param_node);
+ if(output_content != NULL) {
+ output_content = xmlNodeGetContent((xmlNodePtr) g_hash_table_lookup(
+ params->qsf_parameter_hash, xmlGetProp(child, BAD_CAST MAP_TYPE_ATTR)));
+ if(output_content != NULL) {
+ export_node = xmlAddChild(params->lister, xmlNewNode(qsf_ns,
+ xmlGetProp(child, BAD_CAST QSF_OBJECT_TYPE)));
+ xmlNewProp(export_node, BAD_CAST QSF_OBJECT_TYPE,
+ xmlGetProp(child, BAD_CAST MAP_VALUE_ATTR));
+ xmlNodeAddContent(export_node, output_content);
+ xmlFree(output_content);
+ }
+ }
+ }
+
+ qsf_calculate_conditional( param_node, child, params);
+ qsf_calculate_else(param_node, child, params);
+ }
+
+ /* calculate_map currently not in use */
+ /* ensure uniqueness of the key before re-instating */
+/* result = xmlHashAddEntry2(calculate_map,
+ xmlGetProp(child_node, MAP_TYPE_ATTR),
+ xmlGetProp(child_node, MAP_VALUE_ATTR), child_node);
+ if(result != 0) {
+ printf("add entry to calculate hash failed. %s\t%s\t%s.\n",
+ xmlGetProp(child_node, MAP_TYPE_ATTR),
+ xmlGetProp(child_node, MAP_VALUE_ATTR), child_node->name);
+ return;
+ }
+
+ is_qsf_object_with_map(path, map_path);
+*/
+ }
+}
+xmlDocPtr
+qsf_object_convert(xmlDocPtr mapDoc, xmlNodePtr qsf_root, qsf_param *params)
+{
+ struct qsf_node_iterate iter;
+ xmlDocPtr output_doc;
+ xmlNode *cur_node;
+ xmlNode *map_root, *output_root, *output_node;
+
+ output_doc = xmlNewDoc(BAD_CAST QSF_XML_VERSION);
+ output_root = xmlDocCopyNode(qsf_root,output_doc,2);
+ xmlSetNs(output_root, params->qsf_ns);
+ output_node = NULL;
+
+ qsf_node_foreach(qsf_root, qsf_object_node_handler, &iter, params);
+ map_root = xmlDocGetRootElement(mapDoc);
+
+ iter.ns = params->map_ns;
+ qsf_node_foreach(map_root, qsf_map_top_node_handler, &iter, params);
+
+// iter.ns = qsf_ns;
+// qsf_node_foreach(qsf_root, qsf_map_object_handler, &iter, params);
+ for(cur_node = map_root->children; cur_node != NULL; cur_node = cur_node->next)
+ {
+ params->cur_node = cur_node;
+ params->count = 0;
+ if(qsf_is_element(cur_node, params->map_ns, MAP_OBJECT_TAG))
+ {
+ params->lister = NULL;
+ params->count++;
+ iter.ns = params->map_ns;
+ qsf_node_foreach(cur_node, qsf_map_object_handler, &iter, params);
+ }
+ }
+ params->file_type = OUR_QSF_OBJ;
+ return output_doc;
+}
Added: gnucash/trunk/lib/libqof/backend/file/qsf-xml.c
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-xml.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-xml.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,349 @@
+/***************************************************************************
+ * qsf-xml.c
+ *
+ * Fri Nov 26 19:29:47 2004
+ * Copyright 2004-2005 Neil Williams <linux at codehelp.co.uk>
+ *
+ ****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+
+#include <libxml/xmlversion.h>
+#include "qof-backend-qsf.h"
+#include "qsf-dir.h"
+#include "qsf-xml.h"
+
+static QofLogModule log_module = QOF_MOD_QSF;
+
+void qsf_free_params(qsf_param *params)
+{
+ g_hash_table_destroy(params->qsf_calculate_hash);
+ g_hash_table_destroy(params->qsf_default_hash);
+ if(params->referenceList) {
+ g_list_free(params->referenceList);
+ }
+ g_slist_free(params->supported_types);
+ if(params->map_ns) { xmlFreeNs(params->map_ns); }
+}
+
+int
+qsf_compare_tag_strings(const xmlChar *node_name, char *tag_name)
+{
+ return xmlStrcmp(node_name, (const xmlChar *)tag_name);
+}
+
+int
+qsf_strings_equal(const xmlChar *node_name, char *tag_name)
+{
+ if(0 == qsf_compare_tag_strings(node_name, tag_name)) { return 1; }
+ return 0;
+}
+
+int
+qsf_is_element(xmlNodePtr a, xmlNsPtr ns, char *c)
+{
+ g_return_val_if_fail(a != NULL, 0);
+ g_return_val_if_fail(ns != NULL, 0);
+ g_return_val_if_fail(c != NULL, 0);
+ if ((a->ns == ns) && (a->type == XML_ELEMENT_NODE) &&
+ qsf_strings_equal(a->name, c)) { return 1; }
+ return 0;
+}
+
+int
+qsf_check_tag(qsf_param *params, char *qof_type)
+{
+ return qsf_is_element(params->child_node, params->qsf_ns, qof_type);
+}
+
+gboolean
+qsf_is_valid(const char *schema_dir, const char* schema_filename, xmlDocPtr doc)
+{
+ xmlSchemaParserCtxtPtr qsf_schema_file;
+ xmlSchemaPtr qsf_schema;
+ xmlSchemaValidCtxtPtr qsf_context;
+ gchar *schema_path;
+ gint result;
+
+ g_return_val_if_fail(doc || schema_filename, FALSE);
+ schema_path = g_strdup_printf("%s/%s", schema_dir, schema_filename);
+ qsf_schema_file = xmlSchemaNewParserCtxt(schema_path);
+ qsf_schema = xmlSchemaParse(qsf_schema_file);
+ qsf_context = xmlSchemaNewValidCtxt(qsf_schema);
+ result = xmlSchemaValidateDoc(qsf_context, doc);
+ xmlSchemaFreeParserCtxt(qsf_schema_file);
+ xmlSchemaFreeValidCtxt(qsf_context);
+ xmlSchemaFree(qsf_schema);
+ if(result == 0) { return TRUE; }
+ return FALSE;
+}
+
+void
+qsf_valid_foreach(xmlNodePtr parent, qsf_validCB cb,
+ struct qsf_node_iterate *iter, qsf_validator *valid)
+{
+ xmlNodePtr cur_node;
+
+ iter->v_fcn = &cb;
+ for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+ {
+ cb(cur_node, iter->ns, valid);
+ }
+}
+
+void
+qsf_node_foreach(xmlNodePtr parent, qsf_nodeCB cb,
+ struct qsf_node_iterate *iter, qsf_param *params)
+{
+ xmlNodePtr cur_node;
+
+ iter->fcn = &cb;
+ for(cur_node = parent->children; cur_node != NULL; cur_node = cur_node->next)
+ {
+ cb(cur_node, iter->ns, params);
+ }
+}
+
+void
+qsf_object_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid)
+{
+ xmlNodePtr cur_node;
+ xmlChar *object_declaration;
+ guint count;
+
+ count = 0;
+ for(cur_node = child->children; cur_node != NULL;
+ cur_node = cur_node->next)
+ {
+ if(qsf_is_element(cur_node, ns, QSF_OBJECT_TAG)) {
+ object_declaration = xmlGetProp(cur_node, BAD_CAST QSF_OBJECT_TYPE);
+ count = g_hash_table_size(valid->validation_table);
+ g_hash_table_insert(valid->validation_table, object_declaration, xmlNodeGetContent(cur_node));
+ if(g_hash_table_size(valid->validation_table) > count)
+ {
+ valid->valid_object_count++;
+ if(TRUE == qof_class_is_registered((QofIdTypeConst) object_declaration))
+ {
+ valid->qof_registered_count++;
+ }
+ }
+ }
+ }
+}
+
+gboolean is_our_qsf_object(const char *path)
+{
+ xmlDocPtr doc;
+ struct qsf_node_iterate iter;
+ xmlNodePtr object_root;
+ qsf_validator valid;
+ gint table_count;
+
+ g_return_val_if_fail((path != NULL),FALSE);
+ doc = xmlParseFile(path);
+ if(doc == NULL) { return FALSE; }
+ if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) {
+ PINFO (" validation failed %s %s %s", QSF_SCHEMA_DIR,
+ QSF_OBJECT_SCHEMA, path);
+ return FALSE;
+ }
+ object_root = xmlDocGetRootElement(doc);
+ valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+ valid.qof_registered_count = 0;
+ valid.valid_object_count = 0;
+ iter.ns = object_root->ns;
+ qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+ table_count = g_hash_table_size(valid.validation_table);
+ g_hash_table_destroy(valid.validation_table);
+ if(table_count == valid.qof_registered_count) { return TRUE; }
+ return FALSE;
+}
+
+gboolean is_qsf_object(const char *path)
+{
+ xmlDocPtr doc;
+
+ g_return_val_if_fail((path != NULL),FALSE);
+ if(path == NULL) { return FALSE; }
+ doc = xmlParseFile(path);
+ if(doc == NULL) { return FALSE; }
+ if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc)) { return FALSE; }
+ /** \todo implement a way of finding more than one map */
+ return is_qsf_object_with_map(path, "pilot-qsf-GnuCashInvoice.xml");
+}
+
+gboolean is_our_qsf_object_be(qsf_param *params)
+{
+ xmlDocPtr doc;
+ struct qsf_node_iterate iter;
+ xmlNodePtr object_root;
+ qsf_validator valid;
+ gint table_count;
+ char *path;
+
+ g_return_val_if_fail((params != NULL),FALSE);
+ path = g_strdup(params->filepath);
+ if(path == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+ return FALSE;
+ }
+ if(params->file_type != QSF_UNDEF) { return FALSE; }
+ doc = xmlParseFile(path);
+ if(doc == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+ return FALSE;
+ }
+ if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc))
+ {
+ qof_backend_set_error(params->be, ERR_QSF_INVALID_OBJ);
+ return FALSE;
+ }
+ params->file_type = IS_QSF_OBJ;
+ object_root = xmlDocGetRootElement(doc);
+ valid.validation_table = g_hash_table_new(g_str_hash, g_str_equal);
+ valid.qof_registered_count = 0;
+ iter.ns = object_root->ns;
+ qsf_valid_foreach(object_root, qsf_object_validation_handler, &iter, &valid);
+ table_count = g_hash_table_size(valid.validation_table);
+ if(table_count == valid.qof_registered_count)
+ {
+ g_hash_table_destroy(valid.validation_table);
+ qof_backend_set_error(params->be, ERR_BACKEND_NO_ERR);
+ return TRUE;
+ }
+ g_hash_table_destroy(valid.validation_table);
+ qof_backend_set_error(params->be, ERR_QSF_NO_MAP);
+ return FALSE;
+}
+
+gboolean is_qsf_object_be(qsf_param *params)
+{
+ xmlDocPtr doc;
+ char *path;
+
+ g_return_val_if_fail((params != NULL),FALSE);
+ path = g_strdup(params->filepath);
+ if(path == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_FILE_NOT_FOUND);
+ return FALSE;
+ }
+ /* skip validation if is_our_qsf_object has already been called. */
+ if(ERR_QSF_INVALID_OBJ == qof_backend_get_error(params->be)) { return FALSE; }
+ if(params->file_type == QSF_UNDEF)
+ {
+ doc = xmlParseFile(path);
+ if(doc == NULL) {
+ qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
+ return FALSE;
+ }
+ if(TRUE != qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, doc))
+ {
+ qof_backend_set_error(params->be, ERR_QSF_INVALID_OBJ);
+ return FALSE;
+ }
+ }
+ /** \todo implement a way of finding more than one map */
+ return is_qsf_object_with_map_be("pilot-qsf-GnuCashInvoice.xml", params);
+}
+
+static void
+qsf_supported_data_types(gpointer type, gpointer user_data)
+{
+ qsf_param *params;
+
+ g_return_if_fail(user_data != NULL);
+ g_return_if_fail(type != NULL);
+ params = (qsf_param*) user_data;
+ if(qsf_is_element(params->param_node, params->qsf_ns, (char*)type))
+ {
+ g_hash_table_insert(params->qsf_parameter_hash,
+ xmlGetProp(params->param_node, BAD_CAST QSF_OBJECT_TYPE), params->param_node);
+ }
+}
+
+static void
+qsf_parameter_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params)
+{
+ params->param_node = child;
+ g_slist_foreach(params->supported_types, qsf_supported_data_types, params);
+}
+
+/* Despite the name, this function handles the QSF object book tag
+AND the object tags. */
+void
+qsf_object_node_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params)
+{
+ struct qsf_node_iterate iter;
+ qsf_objects *object_set;
+ char *tail, *object_count_s;
+ int c;
+
+ g_return_if_fail(child != NULL);
+ g_return_if_fail(qsf_ns != NULL);
+ params->qsf_ns = qsf_ns;
+ if(qsf_is_element(child, qsf_ns, QSF_OBJECT_TAG)) {
+ params->qsf_parameter_hash = NULL;
+ object_set = g_new(qsf_objects, 1);
+ params->object_set = object_set;
+ object_set->parameters = g_hash_table_new(g_str_hash, g_str_equal);
+ object_set->object_type = g_strdup((char*)xmlGetProp(child, BAD_CAST QSF_OBJECT_TYPE));
+ object_count_s = g_strdup((char*)xmlGetProp(child, BAD_CAST QSF_OBJECT_COUNT));
+ c = (int)strtol(object_count_s, &tail, 0);
+ g_free(object_count_s);
+ params->qsf_object_list = g_list_prepend(params->qsf_object_list, object_set);
+ iter.ns = qsf_ns;
+ params->qsf_parameter_hash = object_set->parameters;
+ qsf_node_foreach(child, qsf_parameter_handler, &iter, params);
+ }
+}
+
+void
+qsf_book_node_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params)
+{
+ char *book_count_s, *tail;
+ int book_count;
+ xmlNodePtr child_node;
+ struct qsf_node_iterate iter;
+ gchar *buffer;
+ GUID book_guid;
+
+ if(qsf_is_element(child, ns, QSF_BOOK_TAG)) {
+ book_count_s = (char*)xmlGetProp(child, BAD_CAST QSF_BOOK_COUNT);
+ if(book_count_s) {
+ book_count = (int)strtol(book_count_s, &tail, 0);
+ /* More than one book not currently supported. */
+ g_return_if_fail(book_count == 1);
+ }
+ iter.ns = ns;
+ qsf_node_foreach(child, qsf_object_node_handler, &iter, params);
+ }
+ for(child_node = child->children; child_node != NULL;
+ child_node = child_node->next)
+ {
+ if(qsf_is_element(child_node, ns, QSF_BOOK_GUID)) {
+ buffer = g_strdup((char*)xmlNodeGetContent(child_node));
+ g_return_if_fail(TRUE == string_to_guid(buffer, &book_guid));
+ qof_entity_set_guid((QofEntity*)params->book, &book_guid);
+ g_free(buffer);
+ }
+ if(qsf_is_element(child_node, ns, QSF_OBJECT_TAG)) {
+ iter.ns = ns;
+ qsf_node_foreach(child_node, qsf_object_node_handler, &iter, params);
+ }
+ }
+}
Added: gnucash/trunk/lib/libqof/backend/file/qsf-xml.h
===================================================================
--- gnucash/trunk/lib/libqof/backend/file/qsf-xml.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/backend/file/qsf-xml.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,852 @@
+/***************************************************************************
+ * qsf-xml.h
+ *
+ * Fri Nov 26 19:29:47 2004
+ * Copyright 2004-2005 Neil Williams <linux at codehelp.co.uk>
+ *
+ ****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+ #ifndef QSF_XML_H
+ #define QSF_XML_H
+
+/** @file qsf-xml.h
+ @brief Private QSF header
+ @author Copyright (C) 2004-2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <regex.h>
+#include <time.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlschemas.h>
+#include "gnc-date.h"
+#include "qof_book_merge.h"
+#include "qofbook.h"
+#include "qofclass.h"
+#include "qofobject.h"
+#include "kvp_frame.h"
+#include "qofbackend-p.h"
+#include "qofsession-p.h"
+#include "qofbook-p.h"
+
+#if defined(HAVE_GETTEXT) /* HAVE_GETTEXT */
+
+#include <libintl.h>
+#include <locale.h>
+
+#undef _
+#undef Q_
+
+#ifdef DISABLE_GETTEXT_UNDERSCORE
+#define _(String) (String)
+#define Q_(String) gnc_qualifier_prefix_noop(String)
+#else /* ENABLE_GETTEXT_UNDERSCORE */
+#define _(String) gettext(String)
+#define Q_(String) gnc_qualifier_prefix_gettext(String)
+#endif /* End ENABLE_GETTEXT_UNDERSCORE */
+
+#else /* Not HAVE_GETTEXT */
+#if !defined(__USE_GNU_GETTEXT)
+
+#undef _
+#undef Q_
+#define _(String) (String)
+#define Q_(String) gnc_qualifier_prefix_noop(String)
+#define gettext(String) (String)
+#define ngettext(msgid, msgid_plural, n) (((n)==1) ? \
+ (msgid) : (msgid_plural))
+
+#endif /* End not__USE_GNU_GETTEXT */
+#endif /* End Not HAVE_GETTEXT */
+
+#undef N_
+#define N_(String) (String)
+
+
+typedef enum {
+ QSF_UNDEF = 0, /**< Initial undefined value. */
+ IS_QSF_MAP, /**< A QSF map */
+ IS_QSF_OBJ, /**< A QSF object without a map - it may or may not need one. */
+ HAVE_QSF_MAP, /**< A QSF object with the map it needs. */
+ OUR_QSF_OBJ, /**< A QSF object that can be loaded without a map. */
+}qsf_type;
+
+/** \brief Holds a description of the QofObject.
+
+Used when converting QOF objects from another application. The incoming,
+\b unknown, objects need to be stored prior to conversion. This allows
+many-to-many conversions where an invoice can receive data from an incoming
+expense AND datebook and use data from an incoming contacts object to lookup
+the customer for the invoice.
+*/
+typedef struct qsf_object_set
+{
+ GHashTable *parameters;
+ QofIdType object_type;
+ int object_count;
+}qsf_objects;
+
+#define QSF_QOF_VERSION QOF_OBJECT_VERSION /**< QOF Version check.
+
+Make sure the same version of QOF is in use in both applications.
+*/
+/** @name QSF Object XML
+
+@{ */
+#define QSF_ROOT_TAG "qof-qsf" /**< The top level root tag */
+#define QSF_DEFAULT_NS "http://qof.sourceforge.net/" /**< Default namespace for QSF root tag
+
+The map namespace is not included as maps are not currently written out by QOF.
+*/
+#define QSF_DATE_LENGTH 31 /**< Max length of QSF_XSD_TIME */
+#define QSF_BOOK_TAG "book" /**< First level child: book tag - the ::QofBook. */
+#define QSF_BOOK_GUID "book-guid" /**< QOF GUID tag for the QofBook described by this QSF object file */
+#define QSF_BOOK_COUNT "count" /**< Sequential counter of each book in this file */
+#define QSF_OBJECT_TAG "object" /**< Second level child: object tag */
+#define QSF_OBJECT_TYPE "type" /**< QSF parameter name for object type specifiers */
+#define QSF_OBJECT_COUNT "count" /**< Sequential counter for each QSF object in this file */
+#define QSF_XML_VERSION "1.0" /**< The current XML version. */
+
+/** @} */
+/** @name Representing KVP as XML
+
+<kvp type="kvp" path="/from-sched-xaction" value="guid">c858b9a3235723b55bc1179f0e8c1322</kvp>
+A kvp type KVP parameter located at $path containing a GUID $value.
+
+The relevance of type="kvp" won't be evident in GnuCash, they all use "kvp".
+
+A non-GnuCash example helps:
+<kvp type="pilot_addr_kvp" path="/user/name" value="guid">c858b9a3235723b55bc1179f0e8c1322</kvp>
+A pilot_addr_kvp type KVP parameter located at /user/name containing a guid value.
+@{ */
+
+#define QSF_OBJECT_KVP "path" /**< The path to this KVP value in the entity frame. */
+#define QSF_OBJECT_VALUE "value" /**< The KVP Value. */
+/** @} */
+/** @name QSF Map XML
+
+@{ */
+#define MAP_ROOT_TAG "qsf-map" /**< Top level root tag for QSF Maps */
+#define MAP_DEFINITION_TAG "definition" /**< Second level container for defined objects
+
+Attributes: qof_version - Taken from the QOF_OBJECT_VERSION macro in QOF,
+At the time of QSF development, QOF_OBJECT_VERSION is defined as 3. All
+QSF maps and QSF objects must use the same qof_version which in turn must
+match the QOF_OBJECT_VERSION for the QOF library in use by the calling process.
+
+No text content allowed.
+*/
+#define MAP_DEFINE_TAG "define" /**< defines each object supported by this QSF map
+
+Attributes: e_type Copied directly from the QofObject definition.
+Content: The full QofObject description for the defined QOF object.
+*/
+#define MAP_DEFAULT_TAG "default" /**< User editable defaults for data not available within the
+available QSF objects.
+
+Some defaults will relate to how to format descriptive dates, whether discount should be considered,
+which account to use for certain QSF data from applications that don't use accounts.
+
+Some defaults are pre-defined and cannot be over-written:
+- qsf_time_now
+- qsf_time_string
+
+Attributes (All are mandatory):
+
+\a name The text name for this default. Certain pre-defined defaults exist but user- or
+map-defined defaults can have any unique text name. Spaces are \b NOT allowed, use undersccores
+instead. The value of name must not duplicate any existing default, define, object or parameter
+unless the special type, enum, is used.
+
+\a type QOF_TYPE - must be one of the recognised QOF data types for the
+qof_version in use or the special type, enum.
+
+\a value Text representation of the required value. For numeric, use the format
+[0-9]?/[0-9]?
+
+\attention Using boolean defaults
+
+A boolean default is not output in the QSF directly, instead the value is
+used in the calculations to modify certain values. If the boolean default
+is set to true, the if statement containing the boolean name will be evaluated. If the boolean
+default is set to false, the corresponding else will be evaluted. Make sure your
+calculations contain an appropriate else statement so that the boolean value
+can be adjusted without invalidating the map!
+
+QSF deals with partial QofBooks - each object is fully described but the
+book does not have to contain any specific object types or have any
+particular structure. To merge partial books into usual QofBook data
+sources, the map must deal with entities that need to be referenced in
+the target QofBook but which simply don't exist in the QofBook used to generate
+the QSF. e.g. pilot-link knows nothing of Accounts yet when QSF creates
+a gncInvoice from qof-datebook, gncInvoice needs to know the GUID of
+certain accounts in the target QofBook. This is handled in the map
+by specifying the name of the account as a default for that map. When imported,
+the QSF QofBackend looks up the object required using the name of
+the parameter to obtain the parameter type. This is the only situation
+where QSF converts between QOF data types. A string description of the
+required object is converted to the GUID for that specific entity. The
+map cannot contain the GUID as it is generic and used by multiple users.
+
+\attention Using enumerators
+- enum types are the only defaults that are allowed to use the same name value more than once.
+- enum types are used to increase the readability of a QSF map.
+- The enum name acts to group the enum values together - in a similar fashion to radio buttons in HTML forms.
+- enum types are used only where the QOF object itself uses an enum type.
+
+e.g. the tax_included enum type allows maps to use the full name of the enum value GNC_TAXINCLUDED_YES,
+instead of the cryptic digit value, 1.
+
+*/
+#define MAP_OBJECT_TAG "object" /**< Contains all the calculations to make one object from others.
+
+Note that creating an object for the import application can involve using data from more than one
+QSF object, as well as defaults and lookups in the import application itself. Conditionals, simple
+arithmetic and date/time formatting options are also available.
+*/
+#define MAP_CALCULATE_TAG "calculate" /**< One calculation for every parameter that needs to be set.
+
+QSF follows the same rule as qof_book_merge. Only if a getter and a setter function are defined for
+a parameter is it available to QSF. If a ::QofAccessFunc and ::QofSetterFunc are both defined
+for any QofObject parameter, that parameter \b MUST be calculated in any map that defines that object.
+*/
+#define MAP_QOF_VERSION "qof_version" /**< This is the QOF_OBJECT_VERSION from QOF.
+
+QSF maps may need to be updated if QOF itself is upgraded. This setting is coded into QOF and
+maps for one version cannot necessarily be used by other versions. At the first release of QSF,
+QOF_OBJECT_VERSION = 3.
+*/
+#define MAP_NAME_ATTR "name" /**< The name of the default setting.
+
+Use this name to refer to the value of this default in the map calculations.
+
+Make sure that the type of this default matches the type of the parameter being set by the
+parent calculation!
+*/
+#define MAP_TYPE_ATTR "type" /**< QSF will NOT convert between QOF types.
+
+QSF will allow a conditional to use a parameter of one type to determine the value from a parameter of
+another type, but the final value assigned \b MUST be of the same type as the parent calculation.
+*/
+#define MAP_VALUE_ATTR "value" /**< The value of the tag, used in defaults and calculations.
+
+The value of a default is a string representation of the value to be inserted into
+the calculation where the default is used.
+
+The value of a calculation is the name of the parameter that will be set by that calculation.
+*/
+#define MAP_E_TYPE "e_type" /**< Validates the objects defined in the map
+
+The e_type will be used to match incoming QSF objects with the relevant QSF map.
+The value of the e_type must be the value of the e_type for that object in the
+originating QOF application. The define tag must contain the value of the description
+of the same object in the same originating QOF application.
+*/
+/** \todo enum is an attempt to make enumerator values descriptive in the maps
+and QSF (possibly). Not working yet. */
+#define MAP_ENUM_TYPE "enum"
+
+/** \brief A specific boolean default for this map.
+*/
+#define QSF_BOOLEAN_DEFAULT "boolean"
+
+#define QSF_CONDITIONAL "if" /**< child of calculate.
+
+Conditionals can reference objects as if within the original application. In operation,
+the map is overlaid across both sets of defined objects, an import object in the source
+application and an output object for the destination object. The current import and output
+QSF objects are therefore always available to the map.
+Conditionals can reference parameter as well as object values.
+*/
+#define QSF_CONDITIONAL_SET "set" /**< Assignment statement
+
+Map assignments can use the native values within the output object. The output object
+must support setting the relevant parameter using the value exactly as given in the map
+because the relevant set() function will be called using this value. This may reduce the
+readability of the map but the relevant application could also be modified to support a more
+readable set function.
+*/
+#define QSF_CONDITIONAL_ELSE "else" /**< Alternative
+
+if(){} else{} is also supported. Nesting of conditionals causes problems for validating the
+final map against any sensible XML Schema and a map that doesn't validate will be rejected.
+When editing conditionals in a QSF map, ALWAYS validate the map using xmllint. If necessary,
+define a variable at the foot of the definitions block, using a similar syntax to a default,
+then use that variable in another conditional
+
+\a variable \a name="my_rate" \a type="numeric" \a value="0/1"
+
+The syntax for xmllint is:
+
+\a xmllint \a --schema \a <schema file> \a <qsf-file>
+
+Use the qsf-object.xsd.xml schema for objects and qsf-map.xsd.xml for map files.
+
+e.g. xmllint --schema qsf-object.xsd.xml --noout qof-qsf.xml
+
+*/
+#define QSF_OPTION "option" /**< enum operator
+
+Not implemented yet - may need to change once work starts.
+Theoretically, option will specify when an enumerator value is in use -
+it is quite possible that it will be unnecessary.
+*/
+
+#define QSF_FORMATTING_OPTION "format" /**< How to format dates/times
+
+When the QSF map uses a date/time value as a \b string, the formatting
+can be adjusted to personal preference. \a format will only be evaluated
+if the calculated parameter is a QOF_TYPE_STRING - any format attributes
+on other data types will be ignored.
+ */
+
+/** @} */
+
+#define QSF_XSD_TIME QOF_UTC_DATE_FORMAT /**< xsd:dateTime format in coordinated universal time, UTC.
+
+You can reproduce the string from the GNU/Linux command line using the date utility:
+
+date -u +%Y-%m-%dT%H:%M:%SZ
+
+2004-12-12T23:39:11Z
+
+The datestring must be timezone independent and include all specified fields.
+
+Remember to use gmtime() NOT localtime()!. From the command line, use the -u switch with the
+date command: date -u
+
+To generate a timestamp based on a real time, use the qsf_time_now and qsf_time_string defaults.
+
+qsf_time_now : Format: QOF_TYPE_DATE. The current time taken from the moment the default
+is read into a QSF object at runtime.
+
+qsf_time_string : Format: QOF_TYPE_STRING. The current timestamp taken from the moment the
+default is read into a QSF object at runtime. This form is used when the output parameter
+needs a formatted date string, not an actual date object. The format is determined by the
+optional format attribute of the set tag which takes the same operators as the GNU C Library
+for strftime() and output may therefore be dependent on the locale of the calling process -
+\b take \b care. Default value is %F, used when qsf_time_string is set without the format
+attribute.
+
+Both defaults use UTC.
+
+*/
+#define QSF_XML_BOOLEAN_TEST "true" /**< needs to be lowercase for XML validation */
+
+#define QSF_OBJECT_SCHEMA "qsf-object.xsd.xml" /**< Name of the QSF Object Schema. */
+#define QSF_MAP_SCHEMA "qsf-map.xsd.xml" /**< Name of the QSF Map Schema. */
+/** \brief QSF Parameters
+
+This struct is a catch-all for all parameters required
+for various stages of the process. There are lots of elements
+here that will finally be removed.
+*/
+typedef struct qsf_metadata
+{
+ qsf_type file_type; /**< what type of file is being handled */
+ qsf_objects *object_set; /**< current object set for qsf_object_list. */
+ int count; /**< sequential counter for each object in the book */
+ GList *qsf_object_list; /**< list of qsf_objects */
+ GSList *qsf_sequence; /**< Parameter list sorted into QSF order */
+ GList *referenceList; /**< Table of references, ::QofEntityReference. */
+ GHashTable *qsf_parameter_hash; /**< Hashtable of parameters for each object */
+ GHashTable *qsf_calculate_hash, *qsf_default_hash, *qsf_define_hash;
+ GSList *supported_types; /**< The partial list of QOF types currently supported, in QSF order. */
+ xmlDocPtr input_doc, output_doc; /**< Pointers to the input and output xml document(s). */
+ /** \todo Review the list of xml nodes in qsf_param and rationalise. */
+ xmlNodePtr child_node, cur_node, param_node, output_node, output_root, book_node, lister;
+ xmlNsPtr qsf_ns, map_ns; /**< Separate namespaces for QSF objects and QSF maps. */
+ const char *qof_type; /**< Holds details of the QOF_TYPE */
+ QofIdType qof_obj_type; /**< current QofObject type (e_type) for the parameters. */
+ QofEntity *qsf_ent; /**< Current entity in the book. */
+ QofBackend *be; /**< the current QofBackend for this operation. */
+ gboolean knowntype; /**< detect references by comparing with known QOF types. */
+ QofParam *qof_param; /**< used by kvp to handle the frame hash table */
+ QofBook *book; /**< the current QofBook.
+
+ Theoretically, QSF can handle multiple QofBooks - currently limited to 1.
+ */
+ int boolean_calculation_done; /**< simple trip once this boolean is complete. */
+ char *filepath; /**< Path to the QSF file. */
+}qsf_param;
+
+/** \brief Free the QSF context.
+
+Frees the two GHashTables, the GSList, the output xmlDoc
+and the two xmlNs namespaces.
+*/
+void qsf_free_params(qsf_param *params);
+
+/** \brief Validation metadata
+
+The validation is a separate parse with separate data.
+This may change but it currently saves workload.
+
+\todo Examine ways of making the Validation metadata
+into a sub-set of the main code, not an island on it's own.
+*/
+typedef struct qsf_validates
+{
+ QofBackendError error_state;
+ const char *object_path;
+ const char *map_path;
+ GHashTable *validation_table;
+ int valid_object_count;
+ int map_calculated_count;
+ int qof_registered_count;
+}qsf_validator;
+
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+as well as in a block.
+*/
+int
+qsf_compare_tag_strings(const xmlChar *node_name, char *tag_name);
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+as well as in a block.
+*/
+int
+qsf_strings_equal(const xmlChar *node_name, char *tag_name);
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+as well as in a block.
+*/
+int
+qsf_is_element(xmlNodePtr a, xmlNsPtr ns, char *c);
+
+/** \brief shorthand function
+
+This may look repetitive but each one is used separately
+as well as in a block.
+*/
+int
+qsf_check_tag(qsf_param *params, char *qof_type);
+
+/** \brief Checks all incoming objects for QOF registration.
+
+Sums all existing objects in the QSF and counts the number of those
+objects that are also registered with QOF in the host application.
+*/
+void
+qsf_object_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid);
+
+/** @name Map Checks
+@{
+Check that the map is sufficient for this object.
+
+Map is usable if all input objects are defined in the object file.
+Count define tags, subtract those calculated in the map (defined as objects)
+Check each remaining object e_type and description against the objects
+declared in the object file. Fail if some map objects remain undefined.
+
+not finished - expect noticeable changes.
+
+*/
+void
+qsf_map_validation_handler(xmlNodePtr child, xmlNsPtr ns, qsf_validator *valid);
+
+void
+qsf_map_top_node_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params);
+
+void
+qsf_map_object_handler(xmlNodePtr child, xmlNsPtr ns, qsf_param *params);
+/** @} */
+
+/** \brief Compares an xmlDoc in memory against the schema file.
+
+ at param schema_dir set at compile time to $prefix/share/qsf/
+ at param schema_filename Either the QSF Object Schema or the QSF Map Schema.
+ at param doc The xmlDoc read from the file using libxml2.
+
+Ensure that you call the right schema_filename for the doc in question!
+
+Incorrect validation will result in output to the terminal window.
+
+ at return TRUE if the doc validates against the assigned schema, otherwise FALSE.
+*/
+gboolean
+qsf_is_valid(const char *schema_dir, const char* schema_filename, xmlDocPtr doc);
+
+/** \brief Validate a QSF file and identify a suitable QSF map
+
+ at param params Pointer to qsf_param context
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the
+corresponding function in other code.
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to find out if a suitable QSF
+map exists. Map files are accepted if all objects described in the QSF object
+file are defined in the QSF map.
+
+ at return TRUE if the file validates and a QSF map can be found,
+otherwise FALSE.
+*/
+gboolean is_qsf_object_be(qsf_param *params);
+
+/** \brief Validate a QSF file and determine type.
+
+ at param params Pointer to qsf_param context
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to see if it is registered
+with QOF within the QOF environment of the calling process.
+
+Files that pass the test can be imported into the QOF appliction without the need
+for a QSF map.
+
+ at return TRUE if the file validates and all objects pass,
+otherwise FALSE.
+*/
+gboolean is_our_qsf_object_be(qsf_param *params);
+
+/** \brief Validate a QSF map file.
+
+ at param params Pointer to qsf_param context
+
+The file is validated aginst the QSF map schema, qsf-map.xsd.xsml. This
+function is called by ::is_qsf_object. If called directly, the map file
+is validated and closed with a QofBackend error. QSF maps do not contain
+user data and are used to import QSF object files.
+
+ at return TRUE if the map validates, otherwise FALSE.
+*/
+gboolean is_qsf_map_be(qsf_param *params);
+
+/** \brief Validate a QSF file and a selected QSF map
+
+ at param map_path Absolute or relative path to the selected QSF map file
+ at param params Pointer to qsf_param context
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to find out if the supplied QSF
+map is suitable. Map files are accepted if all objects described in the QSF object
+file are defined in the QSF map.
+
+\todo Need to code for how to find these files.
+
+
+ at return TRUE if the file validates and the supplied QSF map is usable,
+otherwise FALSE.
+*/
+gboolean is_qsf_object_with_map_be(char *map_path, qsf_param *params);
+
+gboolean is_qsf_object_with_map(const char *path, char *map_file);
+
+/** \brief QOF processing routine.
+
+Called by ::qof_session_load if a map is required.
+Accepts QSF_OBJECT.
+
+Checks available QSF maps for match. Only succeeds if a suitable map exists.
+
+*/
+gboolean
+load_qsf_object(QofBook *book, const char *fullpath, qsf_param *params);
+
+/** \brief QOF processing routine.
+
+Called using ::qof_session_load when all objects defined in the
+XML are registered in the current instance of QOF.
+*/
+gboolean
+load_our_qsf_object(QofBook *book, const char *fullpath, qsf_param *params);
+
+/** \brief Book and book-guid node handler.
+
+Reads the book count="" attribute (currently only 1 QofBook is supported per QSF object file)
+Sets the book-guid as the GUID of the current QofBackend QofBook in qsf_param.
+Calls the next handler, qsf_object_node_handler, with the child of the book tag.
+*/
+void qsf_book_node_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params);
+
+/** \brief Commit the QSF object data to a new QofBook.
+
+The parentage of qof_book_merge should be obvious in this function.
+
+Large chunks were just lifted directly from the commit loop and adjusted
+to obtain the data to commit from the xmlNodePtr instead of qof_book_mergeRule. If
+anything, it's easier here because all entities are new, there are no targets.
+
+Unlike qof_book_merge, this routine runs once per parameter within a loop
+that iterates over objects - it does not have a loop of it's own.
+
+All entities are new.
+
+Using the parent of the current node to
+retrieve the type parameter of the parent provides the type parameter of
+the object tag - the e_type of the current QofObject which allows
+qof_class_get_parameter_setter(obj_type, key);
+
+ at param key name of the parameter: QofIdType
+ at param value xmlNodePtr value->name == QOF_TYPE, content(value) = data to commit.
+ at param data qsf_param* - inevitably.
+
+*/
+void qsf_object_commitCB(gpointer key, gpointer value, gpointer data);
+
+/** \brief Convert a string value into KvpValue
+
+Partner to ::kvp_value_to_string. Given the type of KvpValue
+required, attempts to convert the string into that type of
+value.
+
+ at param content A string representation of the value, ideally as
+ output by kvp_value_to_string.
+ at param type KvpValueType of the intended KvpValue
+
+ at return KvpValue* or NULL on failure.
+*/
+KvpValue*
+string_to_kvp_value(const char *content, KvpValueType type);
+
+/** \brief Backend init routine.
+
+Sets the sequence of parameters to match the schema and provide a reliable
+parse. Sets the default strings for qsf_enquiry_date, qsf_time_now and
+qsf_time_string.
+
+Filters the parameter list to set each type in this order:
+- QOF_TYPE_STRING
+- QOF_TYPE_GUID
+- QOF_TYPE_BOOLEAN
+- QOF_TYPE_NUMERIC
+- QOF_TYPE_DATE
+- QOF_TYPE_INT32
+- QOF_TYPE_INT64
+- QOF_TYPE_DOUBLE
+- QOF_TYPE_CHAR
+- QOF_TYPE_KVP
+- QOF_TYPE_COLLECT
+- QOF_TYPE_CHOICE
+
+*/
+void qsf_param_init(qsf_param *params);
+
+
+/** \brief map callback
+
+Investigate ways to get the map callback to do both
+the map and the validation tasks.
+**/
+typedef void (* qsf_nodeCB)(xmlNodePtr, xmlNsPtr, qsf_param*);
+
+/** \brief validator callback
+
+\todo The need for separate metadata means a separate callback typedef
+ is needed for the validator, but this should be fixed to only need one.
+*/
+typedef void (* qsf_validCB)(xmlNodePtr, xmlNsPtr, qsf_validator*);
+
+
+/** \brief One iterator, two typedefs
+
+\todo resolve the two callbacks in ::qsf_node_iterate into one.
+*/
+struct qsf_node_iterate {
+ qsf_nodeCB *fcn;
+ qsf_validCB *v_fcn;
+ xmlNsPtr ns;
+};
+
+/** \brief Validate a QSF file and identify a suitable QSF map
+
+ at param path Absolute or relative path to the file to be validated.
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the
+corresponding function in other code.
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to find out if a suitable QSF
+map exists. Map files are accepted if all objects described in the QSF object
+file are defined in the QSF map.
+
+ at return TRUE if the file validates and a QSF map can be found,
+otherwise FALSE.
+*/
+gboolean is_qsf_object(const char *path);
+
+/** \brief Validate a QSF file and determine type.
+
+ at param path Absolute or relative path to the file to be validated
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the
+corresponding function in other code.
+
+The file is validated against the QSF object schema, qsf-object.xsd.xml and
+each object described in the file is checked to see if it is registered
+with QOF within the QOF environment of the calling process.
+
+Files that pass the test can be imported into the QOF appliction without the need
+for a QSF map.
+
+ at return TRUE if the file validates and all objects pass,
+otherwise FALSE.
+*/
+gboolean is_our_qsf_object(const char *path);
+
+/** \brief Validate a QSF map file.
+
+ at param path Absolute or relative path to the file to be validated
+
+These functions are in pairs. When called from within a QofSession, the qsf_param
+context will be available. When just determining the type of file, qsf_param is
+not necessary. Use the *_be functions from within the QofBackend and the
+corresponding function in other code.
+
+The file is validated aginst the QSF map schema, qsf-map.xsd.xsml. This
+function is called by ::is_qsf_object. If called directly, the map file
+is validated and closed, no data is retrieved. QSF maps do not contain
+user data but are used to import QSF object files from other applications.
+
+ at return TRUE if the map validates, otherwise FALSE.
+*/
+gboolean is_qsf_map(const char *path);
+
+/** \brief Determine the type of QSF and load it into the QofBook
+
+- is_our_qsf_object, OUR_QSF_OBJ, QSF object file using only QOF objects known to the calling process.
+ No map is required.
+- is_qsf_object, IS_QSF_OBJ, QSF object file that may or may not have a QSF map
+ to convert external objects. This temporary type will be set to HAVE_QSF_MAP if a suitable
+ map exists, or an error value returned: ERR_QSF_NO_MAP, ERR_QSF_BAD_MAP or ERR_QSF_WRONG_MAP
+ This allows the calling process to inform the user that the QSF itself is valid but a
+ suitable map cannot be found.
+- is_qsf_map, IS_QSF_MAP, QSF map file. In the backend, this generates ERR_QSF_MAP_NOT_OBJ but
+ it can be used internally when processing maps to match a QSF object.
+
+ at return NULL on error, otherwise a pointer to the QofBook. Use
+ the qof_book_merge API to merge the new data into the current
+ QofBook.
+*/
+void
+qsf_file_type (QofBackend *be, QofBook *book);
+
+void
+qsf_valid_foreach(xmlNodePtr parent, qsf_validCB cb,
+ struct qsf_node_iterate *iter, qsf_validator *valid);
+
+void
+qsf_node_foreach(xmlNodePtr parent, qsf_nodeCB cb,
+ struct qsf_node_iterate *iter, qsf_param *params);
+
+/** \brief Loads the QSF into a QofSession, ready to merge.
+
+Loads a QSF object file containing only GnuCash objects
+into a second QofSession.
+
+ at param first_session A QofSession pointer to the original session. This
+will become the target of the subsequent qof_book_merge.
+
+ at param path Absolute or relative path to the file to be loaded
+
+ at return ERR_BACKEND_NO_ERR == 0 on success, otherwise the QofBackendError
+ set by the QSFBackend.
+
+\todo Build the qof_book_merge code onto this function if session loads
+ properly.
+*/
+QofBackendError
+qof_session_load_our_qsf_object(QofSession *first_session, const char *path);
+
+/** \brief Placeholder so far.
+
+\todo Determine the map to use and convert the QOF objects
+
+ Much of the map code is written but there is still work to do.
+*/
+QofBackendError
+qof_session_load_qsf_object(QofSession *first_session, const char *path);
+
+/** \brief Convert between QSF objects
+
+This is the main workhorse of the conversion between QSF objects using
+maps.
+
+ at param mapDoc The map document, parsed by libxml2.
+ at param qsf_root The top node of the QSF object to be converted using the map.
+ at param params The QSF backend parameters.
+
+Each calculation in the map is performed over the child nodes of the
+object tree. A new xmlDoc is created and this is made available to QOF to
+be loaded into the book.
+
+*/
+xmlDocPtr
+qsf_object_convert(xmlDocPtr mapDoc, xmlNodePtr qsf_root, qsf_param *params);
+
+void
+qsf_object_node_handler(xmlNodePtr child, xmlNsPtr qsf_ns, qsf_param *params);
+
+/** \brief Backend routine to write a file or stdout.
+
+This function is used by ::qof_session_save to write any QofBook to QSF,
+any process that can create a new QofSession and populate the QofBook
+with QOF objects can write the data as QSF XML - the book does not need
+an AccountGroup. Remember that only fully \b QOF-compliant objects
+are supported by QSF.
+
+Your QOF objects must have:
+ - a create: function in the QofObject definition
+ - a foreach: function in the QofObject definition
+ - QofParam params[] registered with QOF using
+ qof_class_register and containing all necessary parameters
+ to reconstruct this object without any further information.
+ - Logical distinction between those parameters that should be
+ set (have a QofAccessFunc and QofSetterFunc) and those that
+ should only be calculated (only a QofAccessFunc).
+
+If you begin your QSF session with ::QOF_STDOUT as the book_id,
+QSF will write to STDOUT - usually a terminal. This is used by QOF
+applications to provide data streaming. If you don't want terminal
+output, take care to check the path given to
+::qof_session_begin - don't try to change it later!
+
+The XML is validated against the QSF object schema before being
+written (to file or stdout).
+
+Check the QofBackendError - don't assume the file is OK.
+
+*/
+void qsf_write_file(QofBackend *be, QofBook *book);
+
+/** \brief Create a new QSF backend.
+*/
+QofBackend* qsf_backend_new(void);
+
+/** @} */
+/** @} */
+
+#endif /* QSF_XML_H */
Added: gnucash/trunk/lib/libqof/qof/Makefile.am
===================================================================
--- gnucash/trunk/lib/libqof/qof/Makefile.am 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/Makefile.am 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,86 @@
+lib_LTLIBRARIES = libqof.la
+
+libqof_la_LDFLAGS= -version-info $(LIBQOF_LIBRARY_VERSION)
+
+libqof_la_SOURCES = \
+ gnc-date.c \
+ gnc-engine-util.c \
+ gnc-numeric.c \
+ gnc-event.c \
+ gnc-trace.c \
+ guid.c \
+ kvp_frame.c \
+ kvp-util.c \
+ md5.c \
+ qofbackend.c \
+ qofclass.c \
+ qofchoice.c \
+ qofgobj.c \
+ qofid.c \
+ qofinstance.c \
+ qofquery.c \
+ qofbook.c \
+ qofobject.c \
+ qofquerycore.c \
+ qofsession.c \
+ qof_book_merge.c
+
+qofincludedir = ${pkgincludedir}
+
+qofinclude_HEADERS = \
+ gnc-date.h \
+ gnc-engine-util.h \
+ gnc-numeric.h \
+ gnc-event.h \
+ gnc-trace.h \
+ guid.h \
+ kvp_frame.h \
+ kvp-util.h \
+ kvp-util-p.h \
+ qof.h \
+ qof-be-utils.h \
+ qofbackend.h \
+ qofbackend-p.h \
+ qofclass.h \
+ qofchoice.h \
+ qofgobj.h \
+ qofid.h \
+ qofid-p.h \
+ qofinstance-p.h \
+ qofinstance.h \
+ qofquery.h \
+ qofbook.h \
+ qofobject.h \
+ qofquerycore.h \
+ qofsession.h \
+ qofla-dir.h \
+ qof_book_merge.h
+
+noinst_HEADERS += \
+ gnc-event-p.h \
+ md5.h \
+ qofclass-p.h \
+ qofmath128.h \
+ qofquery-p.h \
+ qofquery-deserial.h \
+ qofquery-serialize.h \
+ qofbook-p.h \
+ qofobject-p.h \
+ qofquerycore-p.h \
+ qofsession-p.h
+
+QOFLIBdir = $(libdir)
+
+EXTRA_DIST += \
+ qofla-dir.h.in \
+ qofmath128.c
+
+qofla-dir.h: qofla-dir.h.in
+ rm -f $@.tmp
+ sed < $< > $@.tmp \
+ -e 's:@-libdir-@:${QOFLIBdir}:g'
+ mv $@.tmp $@
+
+BUILT_SOURCES += qofla-dir.h
+
+
Added: gnucash/trunk/lib/libqof/qof/gnc-date.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-date.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-date.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1391 @@
+/********************************************************************\
+ * gnc-date.c -- misc utility functions to handle date and time *
+ * *
+ * Copyright (C) 1997 Robin D. Clark <rclark at cs.hmc.edu> *
+ * Copyright (C) 1998-2000, 20003 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#define _GNU_SOURCE
+#define __EXTENSIONS__
+
+#include "config.h"
+
+#include <ctype.h>
+
+#ifdef HAVE_LANGINFO_D_FMT
+#include <langinfo.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <glib.h>
+
+#include "gnc-date.h"
+#include "gnc-trace.h"
+
+#ifndef HAVE_STRPTIME
+#include "strptime.h"
+#endif
+#ifndef HAVE_LOCALTIME_R
+#include "localtime_r.h"
+#endif
+
+#define NANOS_PER_SECOND 1000000000
+
+#ifdef HAVE_LANGINFO_D_FMT
+# define GNC_D_FMT (nl_langinfo (D_FMT))
+# define GNC_D_T_FMT (nl_langinfo (D_T_FMT))
+# define GNC_T_FMT (nl_langinfo (T_FMT))
+#else
+# define GNC_D_FMT "%Y-%m-%d"
+# define GNC_D_T_FMT "%Y-%m-%d %r"
+# define GNC_T_FMT "%r"
+#endif
+
+
+/* This is now user configured through the gnome options system() */
+static QofDateFormat dateFormat = QOF_DATE_FORMAT_LOCALE;
+static QofDateFormat prevQofDateFormat = QOF_DATE_FORMAT_LOCALE;
+
+/* This static indicates the debugging module that this .o belongs to. */
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+/********************************************************************\
+\********************************************************************/
+
+const char*
+gnc_date_dateformat_to_string(QofDateFormat format)
+{
+ switch (format) {
+ case QOF_DATE_FORMAT_US:
+ return "us";
+ case QOF_DATE_FORMAT_UK:
+ return "uk";
+ case QOF_DATE_FORMAT_CE:
+ return "ce";
+ case QOF_DATE_FORMAT_ISO:
+ return "iso";
+ case QOF_DATE_FORMAT_UTC:
+ return "utc";
+ case QOF_DATE_FORMAT_LOCALE:
+ return "locale";
+ case QOF_DATE_FORMAT_CUSTOM:
+ return "custom";
+ default:
+ return NULL;
+ }
+}
+
+gboolean
+gnc_date_string_to_dateformat(const char* fmt_str, QofDateFormat *format)
+{
+ if (!fmt_str)
+ return TRUE;
+
+ if (!strcmp(fmt_str, "us"))
+ *format = QOF_DATE_FORMAT_US;
+ else if (!strcmp(fmt_str, "uk"))
+ *format = QOF_DATE_FORMAT_UK;
+ else if (!strcmp(fmt_str, "ce"))
+ *format = QOF_DATE_FORMAT_CE;
+ else if (!strcmp(fmt_str, "utc"))
+ *format = QOF_DATE_FORMAT_UTC;
+ else if (!strcmp(fmt_str, "iso"))
+ *format = QOF_DATE_FORMAT_ISO;
+ else if (!strcmp(fmt_str, "locale"))
+ *format = QOF_DATE_FORMAT_LOCALE;
+ else if (!strcmp(fmt_str, "custom"))
+ *format = QOF_DATE_FORMAT_CUSTOM;
+ else
+ return TRUE;
+
+ return FALSE;
+}
+
+
+const char*
+gnc_date_monthformat_to_string(GNCDateMonthFormat format)
+{
+ switch (format) {
+ case GNCDATE_MONTH_NUMBER:
+ return "number";
+ case GNCDATE_MONTH_ABBREV:
+ return "abbrev";
+ case GNCDATE_MONTH_NAME:
+ return "name";
+ default:
+ return NULL;
+ }
+}
+
+gboolean
+gnc_date_string_to_monthformat(const char *fmt_str, GNCDateMonthFormat *format)
+{
+ if (!fmt_str)
+ return TRUE;
+
+ if (!strcmp(fmt_str, "number"))
+ *format = GNCDATE_MONTH_NUMBER;
+ else if (!strcmp(fmt_str, "abbrev"))
+ *format = GNCDATE_MONTH_ABBREV;
+ else if (!strcmp(fmt_str, "name"))
+ *format = GNCDATE_MONTH_NAME;
+ else
+ return TRUE;
+
+ return FALSE;
+}
+
+/********************************************************************\
+\********************************************************************/
+
+static void
+timespec_normalize(Timespec *t)
+{
+ if(t->tv_nsec > NANOS_PER_SECOND)
+ {
+ t->tv_sec+= (t->tv_nsec / NANOS_PER_SECOND);
+ t->tv_nsec= t->tv_nsec % NANOS_PER_SECOND;
+ }
+
+ if(t->tv_nsec < - NANOS_PER_SECOND)
+ {
+ t->tv_sec+= - (-t->tv_nsec / NANOS_PER_SECOND);
+ t->tv_nsec = - (-t->tv_nsec % NANOS_PER_SECOND);
+ }
+
+ if (t->tv_sec > 0 && t->tv_nsec < 0)
+ {
+ t->tv_sec--;
+ t->tv_nsec = NANOS_PER_SECOND + t->tv_nsec;
+ }
+
+ if (t->tv_sec < 0 && t->tv_nsec > 0)
+ {
+ t->tv_sec++;
+ t->tv_nsec = - NANOS_PER_SECOND + t->tv_nsec;
+ }
+ return;
+}
+
+
+gboolean
+timespec_equal (const Timespec *ta, const Timespec *tb)
+{
+ if(ta == tb) return TRUE;
+ if(ta->tv_sec != tb->tv_sec) return FALSE;
+ if(ta->tv_nsec != tb->tv_nsec) return FALSE;
+ return TRUE;
+}
+
+gint
+timespec_cmp(const Timespec *ta, const Timespec *tb)
+{
+ if(ta == tb) return 0;
+ if(ta->tv_sec < tb->tv_sec) return -1;
+ if(ta->tv_sec > tb->tv_sec) return 1;
+ if(ta->tv_nsec < tb->tv_nsec) return -1;
+ if(ta->tv_nsec > tb->tv_nsec) return 1;
+ return 0;
+}
+
+Timespec
+timespec_diff(const Timespec *ta, const Timespec *tb)
+{
+ Timespec retval;
+ retval.tv_sec = ta->tv_sec - tb->tv_sec;
+ retval.tv_nsec = ta->tv_nsec - tb->tv_nsec;
+ timespec_normalize(&retval);
+ return retval;
+}
+
+Timespec
+timespec_abs(const Timespec *t)
+{
+ Timespec retval = *t;
+
+ timespec_normalize(&retval);
+ if (retval.tv_sec < 0)
+ {
+ retval.tv_sec = - retval.tv_sec;
+ retval.tv_nsec = - retval.tv_nsec;
+ }
+
+ return retval;
+}
+
+/** \brief Converts any time on a day to midday that day.
+
+ * given a timepair contains any time on a certain day (local time)
+ * converts it to be midday that day.
+ */
+
+Timespec
+timespecCanonicalDayTime(Timespec t)
+{
+ struct tm tm, *result;
+ Timespec retval;
+ time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
+ result = localtime(&t_secs);
+ tm = *result;
+ gnc_tm_set_day_middle(&tm);
+ retval.tv_sec = mktime(&tm);
+ retval.tv_nsec = 0;
+ return retval;
+}
+
+int gnc_date_my_last_mday (int month, int year)
+{
+ gboolean is_leap;
+ static int days_in_month[2][12] =
+ {/* non leap */ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ /* leap */ {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
+
+ /* Is this a leap year? */
+ if (year % 2000 == 0)
+ is_leap = TRUE;
+ else if (year % 400 == 0)
+ is_leap = FALSE;
+ else
+ is_leap = (year % 4 == 0);
+
+ return days_in_month[is_leap][month-1];
+}
+
+/* Retrieve the last numerical day of the month
+
+ Retrieve the last numerical day for the month specified in the
+ tm_year and tm_mon fields.
+
+param tm: the time value in question
+return the last day of the month, integer.
+*/
+int date_get_last_mday(struct tm *tm)
+{
+ return gnc_date_my_last_mday (tm->tm_mon+1, tm->tm_year+1900);
+}
+
+/* Determines if tm_mday is the last day of the month.
+
+ Determines whether the tm_mday field contains the last day of the
+ month as specified in the tm_year and tm_mon fields.
+param tm: the time value in question
+return TRUE if tm_mday matches the last day of the month, else FALSE.
+*/
+gboolean date_is_last_mday(struct tm *tm)
+{
+ return(tm->tm_mday == date_get_last_mday(tm));
+}
+
+/* Add a number of months to a time value
+
+ Add a number of months to a time value, and normalize. Optionally
+ also track the last day of the month, i.e. 1/31 -> 2/28 -> 3/30.
+
+param tm: base time value
+param months: The number of months to add to this time
+param track_last_day: Coerce the date value if necessary.
+
+return void
+*/
+void date_add_months (struct tm *tm, int months, gboolean track_last_day)
+{
+ gboolean was_last_day;
+ int new_last_mday;
+
+ /* Have to do this now */
+ was_last_day = date_is_last_mday(tm);
+
+ /* Add in the months and normalize */
+ tm->tm_mon += months;
+ while (tm->tm_mon > 11) {
+ tm->tm_mon -= 12;
+ tm->tm_year++;
+ }
+
+ if (!track_last_day)
+ return;
+
+ /* Track last day of the month, i.e. 1/31 -> 2/28 -> 3/30 */
+ new_last_mday = date_get_last_mday(tm);
+ if (was_last_day || (tm->tm_mday > new_last_mday))
+ tm->tm_mday = new_last_mday;
+}
+
+/* Return the set dateFormat.
+
+return QofDateFormat: enumeration indicating preferred format
+
+Global: dateFormat
+*/
+QofDateFormat qof_date_format_get (void)
+{
+ return dateFormat;
+}
+
+/* set date format
+
+set date format to one of US, UK, CE, ISO OR UTC
+checks to make sure it's a legal value
+
+param QofDateFormat: enumeration indicating preferred format
+
+return void
+
+Globals: dateFormat
+*/
+void qof_date_format_set(QofDateFormat df)
+{
+ if(df >= DATE_FORMAT_FIRST && df <= DATE_FORMAT_LAST)
+ {
+ prevQofDateFormat = dateFormat;
+ dateFormat = df;
+ }
+ else
+ { /* hack alert - Use a neutral default. */
+ PERR("non-existent date format set attempted. Setting ISO default");
+ prevQofDateFormat = dateFormat;
+ dateFormat = QOF_DATE_FORMAT_ISO;
+ }
+
+ return;
+}
+
+/*
+ qof_date_format_get_string
+ get the date format string for the current format
+ returns: string
+
+ Globals: dateFormat
+*/
+const gchar *qof_date_format_get_string(QofDateFormat df)
+{
+ switch(df) {
+ case QOF_DATE_FORMAT_US:
+ return "%m/%d/%y";
+ case QOF_DATE_FORMAT_UK:
+ return "%d/%m/%y";
+ case QOF_DATE_FORMAT_CE:
+ return "%d.%m.%y";
+ case QOF_DATE_FORMAT_UTC:
+ return "%Y-%m-%dT%H:%M:%SZ";
+ case QOF_DATE_FORMAT_ISO:
+ return "%y-%m-%d";
+ case QOF_DATE_FORMAT_LOCALE:
+ default:
+ return GNC_D_FMT;
+ };
+}
+
+/* get the date format string for the current format
+
+get the date format string for the current format
+
+param df Required date format.
+return string
+
+Globals: dateFormat
+*/
+const gchar *qof_date_text_format_get_string(QofDateFormat df)
+{
+ switch(df) {
+ case QOF_DATE_FORMAT_US:
+ return "%b %d, %y";
+ case QOF_DATE_FORMAT_UK:
+ case QOF_DATE_FORMAT_CE:
+ return "%d %b, %y";
+ case QOF_DATE_FORMAT_UTC:
+ return "%Y-%m-%dT%H:%M:%SZ";
+ case QOF_DATE_FORMAT_ISO:
+ return "%y-%b-%d";
+ case QOF_DATE_FORMAT_LOCALE:
+ default:
+ return GNC_D_FMT;
+ };
+}
+
+/* Convert day, month and year values to a date string
+
+ Convert a date as day / month / year integers into a localized string
+ representation
+
+param buff - pointer to previously allocated character array; its size
+ must be at lease MAX_DATE_LENTH bytes.
+param day - value to be set with the day of the month as 1 ... 31
+param month - value to be set with the month of the year as 1 ... 12
+param year - value to be set with the year (4-digit)
+
+return void
+
+Globals: global dateFormat value
+*/
+size_t
+qof_print_date_dmy_buff (char * buff, size_t len, int day, int month, int year)
+{
+ int flen;
+ if (!buff) return 0;
+
+ /* Note that when printing year, we use %-4d in format string;
+ * this causes a one, two or three-digit year to be left-adjusted
+ * when printed (i.e. padded with blanks on the right). This is
+ * important while the user is editing the year, since erasing a
+ * digit can temporarily cause a three-digit year, and having the
+ * blank on the left is a real pain for the user. So pad on the
+ * right.
+ */
+ switch(dateFormat)
+ {
+ case QOF_DATE_FORMAT_UK:
+ flen = g_snprintf (buff, len, "%2d/%2d/%-4d", day, month, year);
+ break;
+ case QOF_DATE_FORMAT_CE:
+ flen = g_snprintf (buff, len, "%2d.%2d.%-4d", day, month, year);
+ break;
+ case QOF_DATE_FORMAT_LOCALE:
+ {
+ struct tm tm_str;
+ time_t t;
+
+ tm_str.tm_mday = day;
+ tm_str.tm_mon = month - 1; /* tm_mon = 0 through 11 */
+ tm_str.tm_year = year - 1900; /* this is what the standard
+ * says, it's not a Y2K thing */
+
+ gnc_tm_set_day_start (&tm_str);
+ t = mktime (&tm_str);
+ localtime_r (&t, &tm_str);
+ flen = strftime (buff, len, GNC_D_FMT, &tm_str);
+ if (flen != 0)
+ break;
+ }
+ /* FALLTHROUGH */
+ case QOF_DATE_FORMAT_ISO:
+ case QOF_DATE_FORMAT_UTC:
+ flen = g_snprintf (buff, len, "%04d-%02d-%02d", year, month, day);
+ break;
+ case QOF_DATE_FORMAT_US:
+ default:
+ flen = g_snprintf (buff, len, "%2d/%2d/%-4d", month, day, year);
+ break;
+ }
+
+ return flen;
+}
+
+size_t
+qof_print_date_buff (char * buff, size_t len, time_t t)
+{
+ struct tm *theTime;
+
+ if (!buff) return 0 ;
+
+ theTime = localtime (&t);
+
+ return qof_print_date_dmy_buff (buff, len,
+ theTime->tm_mday,
+ theTime->tm_mon + 1,
+ theTime->tm_year + 1900);
+}
+
+size_t
+qof_print_gdate( char *buf, size_t len, GDate *gd )
+{
+ return qof_print_date_dmy_buff( buf, len,
+ g_date_day(gd),
+ g_date_month(gd),
+ g_date_year(gd) );
+}
+
+char *
+qof_print_date (time_t t)
+{
+ char buff[MAX_DATE_LENGTH];
+ qof_print_date_buff (buff, MAX_DATE_LENGTH, t);
+ return g_strdup (buff);
+}
+
+const char *
+gnc_print_date (Timespec ts)
+{
+ static char buff[MAX_DATE_LENGTH];
+ time_t t;
+
+ t = ts.tv_sec + (ts.tv_nsec / 1000000000.0);
+
+ qof_print_date_buff (buff, MAX_DATE_LENGTH, t);
+
+ return buff;
+}
+
+/* ============================================================== */
+
+size_t
+qof_print_hours_elapsed_buff (char * buff, size_t len, int secs, gboolean show_secs)
+{
+ size_t flen;
+ if (0 <= secs)
+ {
+ if (show_secs)
+ {
+ flen = g_snprintf(buff, len,
+ "%02d:%02d:%02d", (int)(secs / 3600),
+ (int)((secs % 3600) / 60), (int)(secs % 60));
+ }
+ else
+ {
+ flen = g_snprintf(buff, len,
+ "%02d:%02d", (int)(secs / 3600),
+ (int)((secs % 3600) / 60));
+ }
+ }
+ else
+ {
+ if (show_secs)
+ {
+ flen = g_snprintf(buff, len,
+ "-%02d:%02d:%02d", (int)(-secs / 3600),
+ (int)((-secs % 3600) / 60), (int)(-secs % 60));
+ }
+ else
+ {
+ flen = g_snprintf(buff, len,
+ "-%02d:%02d", (int)(-secs / 3600),
+ (int)((-secs % 3600) / 60));
+ }
+ }
+ return flen;
+}
+
+/* ============================================================== */
+
+size_t
+qof_print_minutes_elapsed_buff (char * buff, size_t len, int secs, gboolean show_secs)
+{
+ size_t flen;
+ if (0 <= secs)
+ {
+ if (show_secs)
+ {
+ flen = g_snprintf(buff, len,
+ "%02d:%02d",
+ (int)(secs / 60), (int)(secs % 60));
+ }
+ else
+ {
+ flen = g_snprintf(buff, len,
+ "%02d", (int)(secs / 60));
+ }
+ }
+ else
+ {
+ if (show_secs)
+ {
+ flen = g_snprintf(buff, len,
+ "-%02d:%02d", (int)(-secs / 60), (int)(-secs % 60));
+ }
+ else
+ {
+ flen = g_snprintf(buff, len,
+ "-%02d", (int)(-secs / 60));
+ }
+ }
+ return flen;
+}
+
+/* ============================================================== */
+
+size_t
+qof_print_date_time_buff (char * buff, size_t len, time_t secs)
+{
+ int flen;
+ int day, month, year, hour, min, sec;
+ struct tm ltm, gtm;
+
+ if (!buff) return 0;
+
+ /* Note that when printing year, we use %-4d in format string;
+ * this causes a one, two or three-digit year to be left-adjusted
+ * when printed (i.e. padded with blanks on the right). This is
+ * important while the user is editing the year, since erasing a
+ * digit can temporarily cause a three-digit year, and having the
+ * blank on the left is a real pain for the user. So pad on the
+ * right.
+ */
+ ltm = *localtime (&secs);
+ day = ltm.tm_mday;
+ month = ltm.tm_mon +1;
+ year = ltm.tm_year +1900;
+ hour = ltm.tm_hour;
+ min = ltm.tm_min;
+ sec = ltm.tm_sec;
+
+ switch(dateFormat)
+ {
+ case QOF_DATE_FORMAT_UK:
+ flen = g_snprintf (buff, len, "%2d/%2d/%-4d %2d:%02d", day, month, year, hour, min);
+ break;
+ case QOF_DATE_FORMAT_CE:
+ flen = g_snprintf (buff, len, "%2d.%2d.%-4d %2d:%02d", day, month, year, hour, min);
+ break;
+ case QOF_DATE_FORMAT_ISO:
+ flen = g_snprintf (buff, len, "%04d-%02d-%02d %02d:%02d", year, month, day, hour, min);
+ break;
+ case QOF_DATE_FORMAT_UTC:
+ {
+ gtm = *gmtime (&secs);
+ flen = strftime (buff, len, QOF_UTC_DATE_FORMAT, >m);
+ break;
+ }
+ case QOF_DATE_FORMAT_LOCALE:
+ {
+ flen = strftime (buff, len, GNC_D_T_FMT, <m);
+ }
+ break;
+
+ case QOF_DATE_FORMAT_US:
+ default:
+ flen = g_snprintf (buff, len, "%2d/%2d/%-4d %2d:%02d", month, day, year, hour, min);
+ break;
+ }
+ return flen;
+}
+
+size_t
+qof_print_time_buff (char * buff, size_t len, time_t secs)
+{
+ int flen;
+ struct tm ltm, gtm;
+
+ if (!buff) return 0;
+ if(dateFormat == QOF_DATE_FORMAT_UTC)
+ {
+ gtm = *gmtime (&secs);
+ flen = strftime(buff, len, QOF_UTC_DATE_FORMAT, >m);
+ return flen;
+ }
+ ltm = *localtime (&secs);
+ flen = strftime (buff, len, GNC_T_FMT, <m);
+
+ return flen;
+}
+
+/* ============================================================== */
+
+int
+qof_is_same_day (time_t ta, time_t tb)
+{
+ struct tm lta, ltb;
+ lta = *localtime (&ta);
+ ltb = *localtime (&tb);
+ if (lta.tm_year == ltb.tm_year)
+ {
+ return (ltb.tm_yday - lta.tm_yday);
+ }
+ return (ltb.tm_year - lta.tm_year)*365; /* very approximate */
+}
+
+/* ============================================================== */
+
+/* Convert a string into day, month and year integers
+
+ Convert a string into day / month / year integers according to
+ the current dateFormat value.
+
+ This function will always parse a single number as the day of
+ the month, regardless of the ordering of the dateFormat value.
+ Two numbers will always be parsed as the day and the month, in
+ the same order that they appear in the dateFormat value. Three
+ numbers are parsed exactly as specified in the dateFormat field.
+
+ Fully formatted UTC timestamp strings are converted separately.
+
+param buff - pointer to date string
+param day - will store day of the month as 1 ... 31
+param month - will store month of the year as 1 ... 12
+param year - will store the year (4-digit)
+
+return TRUE if date appeared to be valid.
+
+ Globals: global dateFormat value
+*/
+static gboolean
+qof_scan_date_internal (const char *buff, int *day, int *month, int *year,
+ QofDateFormat which_format)
+{
+ char *dupe, *tmp, *first_field, *second_field, *third_field;
+ int iday, imonth, iyear;
+ struct tm *now, utc;
+ time_t secs;
+
+ if (!buff) return(FALSE);
+
+ if(which_format == QOF_DATE_FORMAT_UTC)
+ {
+ if(strptime(buff, QOF_UTC_DATE_FORMAT, &utc)) {
+ *day = utc.tm_mday;
+ *month = utc.tm_mon + 1;
+ *year = utc.tm_year + 1900;
+ return TRUE;
+ }
+ else { return FALSE; }
+ }
+ dupe = g_strdup (buff);
+
+ tmp = dupe;
+ first_field = NULL;
+ second_field = NULL;
+ third_field = NULL;
+
+ /* Use strtok to find delimiters */
+ if (tmp) {
+ static char *delims = ".,-+/\\() ";
+
+ first_field = strtok (tmp, delims);
+ if (first_field) {
+ second_field = strtok (NULL, delims);
+ if (second_field) {
+ third_field = strtok (NULL, delims);
+ }
+ }
+ }
+
+ /* If any fields appear to be blank, use today's date */
+ time (&secs);
+ now = localtime (&secs);
+ iday = now->tm_mday;
+ imonth = now->tm_mon+1;
+ iyear = now->tm_year+1900;
+
+ /* get numeric values */
+ switch (which_format)
+ {
+ case QOF_DATE_FORMAT_LOCALE:
+ if (buff[0] != '\0')
+ {
+ struct tm thetime;
+
+ /* Parse time string. */
+ memset(&thetime, -1, sizeof(struct tm));
+ strptime (buff, GNC_D_FMT, &thetime);
+
+ if (third_field) {
+ /* Easy. All three values were parsed. */
+ iyear = thetime.tm_year + 1900;
+ iday = thetime.tm_mday;
+ imonth = thetime.tm_mon + 1;
+ } else if (second_field) {
+ /* Hard. Two values parsed. Figure out the ordering. */
+ if (thetime.tm_year == -1) {
+ /* %m-%d or %d-%m. Don't care. Already parsed correctly. */
+ iday = thetime.tm_mday;
+ imonth = thetime.tm_mon + 1;
+ } else if (thetime.tm_mon != -1) {
+ /* Must be %Y-%m-%d. Reparse as %m-%d.*/
+ imonth = atoi(first_field);
+ iday = atoi(second_field);
+ } else {
+ /* Must be %Y-%d-%m. Reparse as %d-%m. */
+ iday = atoi(first_field);
+ imonth = atoi(second_field);
+ }
+ } else if (first_field) {
+ iday = atoi(first_field);
+ }
+ }
+ break;
+ case QOF_DATE_FORMAT_UK:
+ case QOF_DATE_FORMAT_CE:
+ if (third_field) {
+ iday = atoi(first_field);
+ imonth = atoi(second_field);
+ iyear = atoi(third_field);
+ } else if (second_field) {
+ iday = atoi(first_field);
+ imonth = atoi(second_field);
+ } else if (first_field) {
+ iday = atoi(first_field);
+ }
+ break;
+ case QOF_DATE_FORMAT_ISO:
+ if (third_field) {
+ iyear = atoi(first_field);
+ imonth = atoi(second_field);
+ iday = atoi(third_field);
+ } else if (second_field) {
+ imonth = atoi(first_field);
+ iday = atoi(second_field);
+ } else if (first_field) {
+ iday = atoi(first_field);
+ }
+ break;
+ case QOF_DATE_FORMAT_US:
+ default:
+ if (third_field) {
+ imonth = atoi(first_field);
+ iday = atoi(second_field);
+ iyear = atoi(third_field);
+ } else if (second_field) {
+ imonth = atoi(first_field);
+ iday = atoi(second_field);
+ } else if (first_field) {
+ iday = atoi(first_field);
+ }
+ break;
+ }
+
+ g_free (dupe);
+
+ if ((12 < imonth) || (31 < iday))
+ {
+ /*
+ * Ack! Thppfft! Someone just fed this routine a string in the
+ * wrong date format. This is known to happen if a register
+ * window is open when changing the date format. Try the
+ * previous date format. If that doesn't work, see if we can
+ * exchange month and day. If that still doesn't work,
+ * bail and give the caller what they asked for (garbage)
+ * parsed in the new format.
+ *
+ * Note: This test cannot detect any format change that only
+ * swaps month and day field, if the day is 12 or less. This is
+ * deemed acceptable given the obscurity of this bug.
+ */
+ if ((which_format != prevQofDateFormat) &&
+ qof_scan_date_internal(buff, day, month, year, prevQofDateFormat))
+ {
+ return(TRUE);
+ }
+ if ((12 < imonth) && (12 >= iday))
+ {
+ int tmp = imonth; imonth = iday; iday = tmp;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /* If the year entered is smaller than 100, assume we mean the current
+ century (and are not revising some roman emperor's books) */
+ if (iyear < 100)
+ iyear += ((int) ((now->tm_year+1950-iyear)/100)) * 100;
+
+ if (year) *year=iyear;
+ if (month) *month=imonth;
+ if (day) *day=iday;
+ return(TRUE);
+}
+
+gboolean
+qof_scan_date (const char *buff, int *day, int *month, int *year)
+{
+ return qof_scan_date_internal(buff, day, month, year, dateFormat);
+}
+
+gboolean
+qof_scan_date_secs (const char *buff, time_t *secs)
+{
+ gboolean rc;
+ int day, month, year;
+
+ rc = qof_scan_date_internal(buff, &day, &month, &year, dateFormat);
+ if (secs) *secs = xaccDMYToSec (day, month, year);
+
+ return rc;
+}
+
+/* Return the field separator for the current date format
+return date character
+*/
+char dateSeparator (void)
+{
+ static char locale_separator = '\0';
+
+ switch (dateFormat)
+ {
+ case QOF_DATE_FORMAT_CE:
+ return '.';
+ case QOF_DATE_FORMAT_ISO:
+ case QOF_DATE_FORMAT_UTC:
+ return '-';
+ case QOF_DATE_FORMAT_US:
+ case QOF_DATE_FORMAT_UK:
+ default:
+ return '/';
+ case QOF_DATE_FORMAT_LOCALE:
+ if (locale_separator != '\0')
+ return locale_separator;
+ else
+ { /* Make a guess */
+ char string[256];
+ struct tm *tm;
+ time_t secs;
+ char *s;
+
+ secs = time(NULL);
+ tm = localtime(&secs);
+ strftime(string, sizeof(string), GNC_D_FMT, tm);
+
+ for (s = string; s != '\0'; s++)
+ if (!isdigit(*s))
+ return (locale_separator = *s);
+ }
+ }
+
+ return '\0';
+}
+
+/********************************************************************\
+\********************************************************************/
+
+/* Convert time in seconds to a textual.
+
+The xaccDateUtilGetStamp() routine will take the given time in
+seconds and return a buffer containing a textual for the date.
+
+param thyme The time in seconds to convert.
+return A pointer to the generated string.
+The caller owns this buffer and must free it when done.
+*/
+char *
+xaccDateUtilGetStamp (time_t thyme)
+{
+ struct tm *stm;
+
+ stm = localtime (&thyme);
+
+ return g_strdup_printf("%04d%02d%02d%02d%02d%02d",
+ (stm->tm_year + 1900),
+ (stm->tm_mon +1),
+ stm->tm_mday,
+ stm->tm_hour,
+ stm->tm_min,
+ stm->tm_sec
+ );
+}
+
+
+/* Convert textual to time in seconds.
+
+The xaccDateUtilGetStampNow() routine returns the current time in
+seconds in textual format.
+
+return A pointer to the generated string.
+
+note The caller owns this buffer and must free it when done.
+*/
+char *
+xaccDateUtilGetStampNow (void)
+{
+ time_t now;
+ time (&now);
+ return xaccDateUtilGetStamp (now);
+}
+
+/********************************************************************\
+ * iso 8601 datetimes should look like 1998-07-02 11:00:00.68-05
+\********************************************************************/
+/* hack alert -- this routine returns incorrect values for
+ * dates before 1970 */
+
+Timespec
+gnc_iso8601_to_timespec_gmt(const char *str)
+{
+ char buf[4];
+ Timespec ts;
+ struct tm stm;
+ long int nsec =0;
+
+ ts.tv_sec=0;
+ ts.tv_nsec=0;
+ if (!str) return ts;
+
+ stm.tm_year = atoi(str) - 1900;
+ str = strchr (str, '-'); if (str) { str++; } else { return ts; }
+ stm.tm_mon = atoi(str) - 1;
+ str = strchr (str, '-'); if (str) { str++; } else { return ts; }
+ stm.tm_mday = atoi(str);
+
+ str = strchr (str, ' '); if (str) { str++; } else { return ts; }
+ stm.tm_hour = atoi(str);
+ str = strchr (str, ':'); if (str) { str++; } else { return ts; }
+ stm.tm_min = atoi(str);
+ str = strchr (str, ':'); if (str) { str++; } else { return ts; }
+ stm.tm_sec = atoi (str);
+
+ /* The decimal point, optionally present ... */
+ /* hack alert -- this algo breaks if more than 9 decimal places present */
+ if (strchr (str, '.'))
+ {
+ int decimals, i, multiplier=1000000000;
+ str = strchr (str, '.') +1;
+ decimals = strcspn (str, "+- ");
+ for (i=0; i<decimals; i++) multiplier /= 10;
+ nsec = atoi(str) * multiplier;
+ }
+ stm.tm_isdst = -1;
+
+ /* Timezone format can be +hh or +hhmm or +hh.mm (or -) (or not present) */
+ str += strcspn (str, "+-");
+ if (str)
+ {
+ buf[0] = str[0];
+ buf[1] = str[1];
+ buf[2] = str[2];
+ buf[3] = 0;
+ stm.tm_hour -= atoi(buf);
+
+ str +=3;
+ if ('.' == *str) str++;
+ if (isdigit (*str) && isdigit (*(str+1)))
+ {
+ int cyn;
+ /* copy sign from hour part */
+ if ('+' == buf[0]) { cyn = -1; } else { cyn = +1; }
+ buf[0] = str[0];
+ buf[1] = str[1];
+ buf[2] = str[2];
+ buf[3] = 0;
+ stm.tm_min += cyn * atoi(buf);
+ }
+ }
+
+ /* Note that mktime returns 'local seconds' which is the true time
+ * minus the timezone offset. We don't want to work with local
+ * seconds, since they swim around acording to daylight savings, etc.
+ * We want to work with universal time. Thus, add an offset
+ * to undo the damage that mktime causes.
+ */
+ {
+ struct tm tmp_tm;
+ struct tm *tm;
+ long int tz;
+ int tz_hour;
+ time_t secs;
+
+ /* Use a temporary tm struct so the mktime call below
+ * doesn't mess up stm. */
+ tmp_tm = stm;
+ tmp_tm.tm_isdst = -1;
+
+ secs = mktime (&tmp_tm);
+
+ /* The call to localtime is 'bogus', but it forces 'timezone' to
+ * be set. Note that we must use the accurate date, since the
+ * value of 'gnc_timezone' includes daylight savings corrections
+ * for that date. */
+ tm = localtime (&secs);
+
+ tz = gnc_timezone (tm);
+
+ tz_hour = tz / 3600;
+ stm.tm_hour -= tz_hour;
+ stm.tm_min -= (tz - (3600 * tz_hour)) / 60;
+ stm.tm_isdst = tmp_tm.tm_isdst;
+ }
+
+ ts.tv_sec = mktime (&stm);
+ ts.tv_nsec = nsec;
+
+ return ts;
+}
+
+/********************************************************************\
+\********************************************************************/
+
+char *
+gnc_timespec_to_iso8601_buff (Timespec ts, char * buff)
+{
+ int len;
+ int tz_hour, tz_min;
+ char cyn;
+ time_t tmp;
+ struct tm parsed;
+
+ tmp = ts.tv_sec;
+ localtime_r(&tmp, &parsed);
+
+ tz_hour = gnc_timezone (&parsed) / 3600;
+ tz_min = (gnc_timezone (&parsed) - 3600*tz_hour) / 60;
+ if (0>tz_min) { tz_min +=60; tz_hour --; }
+ if (60<=tz_min) { tz_min -=60; tz_hour ++; }
+
+ /* We also have to print the sign by hand, to work around a bug
+ * in the glibc 2.1.3 printf (where %+02d fails to zero-pad).
+ */
+ cyn = '-';
+ if (0>tz_hour) { cyn = '+'; tz_hour = -tz_hour; }
+
+ len = sprintf (buff, "%4d-%02d-%02d %02d:%02d:%02d.%06ld %c%02d%02d",
+ parsed.tm_year + 1900,
+ parsed.tm_mon + 1,
+ parsed.tm_mday,
+ parsed.tm_hour,
+ parsed.tm_min,
+ parsed.tm_sec,
+ ts.tv_nsec / 1000,
+ cyn,
+ tz_hour,
+ tz_min);
+
+ /* Return pointer to end of string. */
+ buff += len;
+ return buff;
+}
+
+int
+gnc_timespec_last_mday (Timespec t)
+{
+ struct tm *result;
+ time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
+ result = localtime(&t_secs);
+ return date_get_last_mday (result);
+}
+
+void
+gnc_timespec2dmy (Timespec t, int *day, int *month, int *year)
+{
+ struct tm *result;
+ time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
+ result = localtime(&t_secs);
+
+ if (day) *day = result->tm_mday;
+ if (month) *month = result->tm_mon+1;
+ if (year) *year = result->tm_year+1900;
+}
+
+/********************************************************************\
+\********************************************************************/
+/* hack alert -- this routine returns incorrect values for
+ * dates before 1970 */
+
+time_t
+xaccDMYToSec (int day, int month, int year)
+{
+ struct tm stm;
+ time_t secs;
+
+ stm.tm_year = year - 1900;
+ stm.tm_mon = month - 1;
+ stm.tm_mday = day;
+ gnc_tm_set_day_start(&stm);
+
+ /* compute number of seconds */
+ secs = mktime (&stm);
+
+ return secs;
+}
+
+
+#define THIRTY_TWO_YEARS 0x3c30fc00LL
+
+static Timespec
+gnc_dmy2timespec_internal (int day, int month, int year, gboolean start_of_day)
+{
+ Timespec result;
+ struct tm date;
+ long long secs = 0;
+ long long era = 0;
+
+ year -= 1900;
+
+ /* make a crude attempt to deal with dates outside the range of Dec
+ * 1901 to Jan 2038. Note we screw up centennial leap years here so
+ * hack alert */
+ if ((2 > year) || (136 < year))
+ {
+ era = year / 32;
+ year %= 32;
+ if (0 > year) { year += 32; era -= 1; }
+ }
+
+ date.tm_year = year;
+ date.tm_mon = month - 1;
+ date.tm_mday = day;
+
+ if (start_of_day)
+ gnc_tm_set_day_start(&date);
+ else
+ gnc_tm_set_day_end(&date);
+
+ /* compute number of seconds */
+ secs = mktime (&date);
+
+ secs += era * THIRTY_TWO_YEARS;
+
+ result.tv_sec = secs;
+ result.tv_nsec = 0;
+
+ return result;
+}
+
+Timespec
+gnc_dmy2timespec (int day, int month, int year)
+{
+ return gnc_dmy2timespec_internal (day, month, year, TRUE);
+}
+
+Timespec
+gnc_dmy2timespec_end (int day, int month, int year)
+{
+ return gnc_dmy2timespec_internal (day, month, year, FALSE);
+}
+
+/********************************************************************\
+\********************************************************************/
+
+long int
+gnc_timezone (struct tm *tm)
+{
+ g_return_val_if_fail (tm != NULL, 0);
+
+#ifdef HAVE_STRUCT_TM_GMTOFF
+ /* tm_gmtoff is seconds *east* of UTC and is
+ * already adjusted for daylight savings time. */
+ return -(tm->tm_gmtoff);
+#else
+ /* timezone is seconds *west* of UTC and is
+ * not adjusted for daylight savings time.
+ * In Spring, we spring forward, wheee! */
+ return timezone - (tm->tm_isdst > 0 ? 60 * 60 : 0);
+#endif
+}
+
+
+void
+timespecFromTime_t( Timespec *ts, time_t t )
+{
+ ts->tv_sec = t;
+ ts->tv_nsec = 0;
+}
+
+time_t
+timespecToTime_t (Timespec ts)
+{
+ return ts.tv_sec;
+}
+
+void
+gnc_tm_get_day_start (struct tm *tm, time_t time_val)
+{
+ /* Get the equivalent time structure */
+ tm = localtime_r(&time_val, tm);
+ gnc_tm_set_day_start(tm);
+}
+
+void
+gnc_tm_get_day_end (struct tm *tm, time_t time_val)
+{
+ /* Get the equivalent time structure */
+ tm = localtime_r(&time_val, tm);
+ gnc_tm_set_day_end(tm);
+}
+
+time_t
+gnc_timet_get_day_start (time_t time_val)
+{
+ struct tm tm;
+
+ gnc_tm_get_day_start(&tm, time_val);
+ return mktime(&tm);
+}
+
+time_t
+gnc_timet_get_day_end (time_t time_val)
+{
+ struct tm tm;
+
+ gnc_tm_get_day_end(&tm, time_val);
+ return mktime(&tm);
+}
+
+
+#ifndef GNUCASH_MAJOR_VERSION
+time_t
+gnc_timet_get_day_start_gdate (GDate *date)
+{
+ struct tm stm;
+ time_t secs;
+
+ stm.tm_year = g_date_year (date) - 1900;
+ stm.tm_mon = g_date_month (date) - 1;
+ stm.tm_mday = g_date_day (date);
+ gnc_tm_set_day_start(&stm);
+
+ /* Compute number of seconds */
+ secs = mktime (&stm);
+ return secs;
+}
+
+time_t
+gnc_timet_get_day_end_gdate (GDate *date)
+{
+ struct tm stm;
+ time_t secs;
+
+ stm.tm_year = g_date_year (date) - 1900;
+ stm.tm_mon = g_date_month (date) - 1;
+ stm.tm_mday = g_date_day (date);
+ gnc_tm_set_day_end(&stm);
+
+ /* Compute number of seconds */
+ secs = mktime (&stm);
+ return secs;
+}
+#endif /* GNUCASH_MAJOR_VERSION */
+
+/* ======================================================== */
+
+void
+gnc_tm_get_today_start (struct tm *tm)
+{
+ gnc_tm_get_day_start(tm, time(NULL));
+}
+
+void
+gnc_tm_get_today_end (struct tm *tm)
+{
+ gnc_tm_get_day_end(tm, time(NULL));
+}
+
+time_t
+gnc_timet_get_today_start (void)
+{
+ struct tm tm;
+
+ gnc_tm_get_day_start(&tm, time(NULL));
+ return mktime(&tm);
+}
+
+time_t
+gnc_timet_get_today_end (void)
+{
+ struct tm tm;
+
+ gnc_tm_get_day_end(&tm, time(NULL));
+ return mktime(&tm);
+}
+
+/********************** END OF FILE *********************************\
+\********************************************************************/
Added: gnucash/trunk/lib/libqof/qof/gnc-date.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-date.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-date.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,539 @@
+/********************************************************************\
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+\********************************************************************/
+/** @addtogroup Date
+ Utility functions to handle date and time (adjusting, getting
+ the current date, printing the date and time, etc.)
+
+ Overall, this file is quite a mess. Note, however, that other
+ applications, besides just GnuCash, use this file. In particular,
+ GnoTime (gttr.sourcefore.net) uses this file, and this file is
+ formally a part of QOF (qof.sourceforge.net).
+
+ An important note about time-keeping: The general goal of any
+ program that works with numeric time values SHOULD BE to always
+ stores and use UNIVERSAL TIME internally. Universal time is the
+ 'one true time' that is independent of one's location on planet
+ Earth. It is measured in seconds from midnight January 1, 1970
+ in localtime-Grenwich (GMT). If one wants to display the local
+ time, then the display-print routine should make all final
+ tweaks to print the local time. The local time *must not* be
+ kept as a numeric value anywhere in the program. If one wants
+ to parse a user's input string as if it were local time, then
+ the output of the parse routine MUST BE universal time.
+ A sane program must never ever store (to file or db) a time
+ that is not Universal Time. Break these rules, and you will
+ rue the day...
+
+ \warning HACK ALERT -- the scan and print routines should probably be moved
+ to somewhere else. The engine really isn't involved with things
+ like printing formats. This is needed mostly by the GUI and so on.
+ If a file-io backend needs date handling, it should do it itself,
+ instead of depending on the routines here.
+
+ @author Copyright (C) 1997 Robin D. Clark <rclark at cs.hmc.edu>
+ @author Copyright (C) 1998-2001,2003 Linas Vepstas <linas at linas.org>
+*/
+
+/* @{
+ @file gnc-date.h
+ @brief Date and Time handling routines
+*/
+
+#ifndef GNC_DATE_H
+#define GNC_DATE_H
+
+#include <glib.h>
+#include <time.h>
+
+/** The maximum length of a string created by the date printers */
+#define MAX_DATE_LENGTH 31
+
+/** Constants *******************************************************/
+/** \brief UTC date format string.
+
+Timezone independent, date and time inclusive, as used in the QSF backend.
+The T and Z characters are from xsd:dateTime format in coordinated universal time, UTC.
+You can reproduce the string from the GNU/Linux command line using the date utility:
+date -u +%Y-%m-%dT%H:M:SZ = 2004-12-12T23:39:11Z The datestring must be timezone independent
+and include all specified fields. Remember to use gmtime() NOT localtime()!
+*/
+
+#define QOF_UTC_DATE_FORMAT "%Y-%m-%dT%H:%M:%SZ"
+
+/** Enum for determining a date format */
+typedef enum
+{
+ QOF_DATE_FORMAT_US, /**< United states: mm/dd/yyyy */
+ QOF_DATE_FORMAT_UK, /**< Britain: dd/mm/yyyy */
+ QOF_DATE_FORMAT_CE, /**< Continental Europe: dd.mm.yyyy */
+ QOF_DATE_FORMAT_ISO, /**< ISO: yyyy-mm-dd */
+ QOF_DATE_FORMAT_UTC, /**< UTC: 2004-12-12T23:39:11Z */
+ QOF_DATE_FORMAT_LOCALE, /**< Take from locale information */
+ QOF_DATE_FORMAT_CUSTOM /**< Used by the check printing code */
+} QofDateFormat;
+
+#define DATE_FORMAT_FIRST QOF_DATE_FORMAT_US
+#define DATE_FORMAT_LAST QOF_DATE_FORMAT_LOCALE
+
+
+/**
+ * This is how to format the month, as a number, an abbreviated string,
+ * or the full name.
+ */
+typedef enum {
+ GNCDATE_MONTH_NUMBER,
+ GNCDATE_MONTH_ABBREV,
+ GNCDATE_MONTH_NAME
+} GNCDateMonthFormat;
+
+
+/** \name String / DateFormat conversion. */
+//@{
+
+/** \brief The string->value versions return FALSE on success and TRUE on failure */
+const char* gnc_date_dateformat_to_string(QofDateFormat format);
+
+/** \brief Converts the date format to a printable string.
+
+Note the reversed return values!
+ at return FALSE on success, TRUE on failure.
+*/
+gboolean gnc_date_string_to_dateformat(const char* format_string,
+ QofDateFormat *format);
+
+const char* gnc_date_monthformat_to_string(GNCDateMonthFormat format);
+
+/** \brief Converts the month format to a printable string.
+
+Note the reversed return values!
+ at return FALSE on success, TRUE on failure.
+*/
+gboolean gnc_date_string_to_monthformat(const char *format_string,
+ GNCDateMonthFormat *format);
+// @}
+
+/* Datatypes *******************************************************/
+
+/** \brief Use a 64-bit signed int timespec
+ *
+ * struct timespec64 is just like the unix 'struct timespec' except
+ * that we use a 64-bit
+ * signed int to store the seconds. This should adequately cover
+ * dates in the distant future as well as the distant past, as long as
+ * they're not more than a couple dozen times the age of the universe.
+ * Note that both gcc and the IBM Toronto xlC compiler (aka CSet,
+ * VisualAge, etc) correctly handle long long as a 64 bit quantity,
+ * even on the 32-bit Intel x86 and PowerPC architectures. I'm
+ * assuming that all the other modern compilers are clean on this
+ * issue too. */
+
+#ifndef SWIG /* swig 1.1p5 can't hack the long long type */
+struct timespec64
+{
+ long long int tv_sec;
+ long int tv_nsec;
+};
+#endif /* SWIG */
+
+/** The Timespec is just like the unix 'struct timespec'
+ * except that we use a 64-bit signed int to
+ * store the seconds. This should adequately cover dates in the
+ * distant future as well as the distant past, as long as they're not
+ * more than a couple dozen times the age of the universe. Note that
+ * both gcc and the IBM Toronto xlC compiler (aka CSet, VisualAge,
+ * etc) correctly handle long long as a 64 bit quantity, even on the
+ * 32-bit Intel x86 and PowerPC architectures. I'm assuming that all
+ * the other modern compilers are clean on this issue too. */
+typedef struct timespec64 Timespec;
+
+
+/* Prototypes ******************************************************/
+
+/** \name Timespec functions */
+// @{
+/** strict equality */
+gboolean timespec_equal(const Timespec *ta, const Timespec *tb);
+
+/** comparison: if (ta < tb) -1; else if (ta > tb) 1; else 0; */
+int timespec_cmp(const Timespec *ta, const Timespec *tb);
+
+/** difference between ta and tb, results are normalised
+ * ie tv_sec and tv_nsec of the result have the same size
+ * abs(result.tv_nsec) <= 1000000000 */
+Timespec timespec_diff(const Timespec *ta, const Timespec *tb);
+
+/** absolute value, also normalised */
+Timespec timespec_abs(const Timespec *t);
+
+/** convert a timepair on a certain day (localtime) to
+ * the timepair representing midday on that day */
+Timespec timespecCanonicalDayTime(Timespec t);
+
+/** Turns a time_t into a Timespec */
+void timespecFromTime_t( Timespec *ts, time_t t );
+
+/** Turns a Timespec into a time_t */
+time_t timespecToTime_t (Timespec ts);
+
+/** Convert a day, month, and year to a Timespec */
+Timespec gnc_dmy2timespec (int day, int month, int year);
+
+/** Same as gnc_dmy2timespec, but last second of the day */
+Timespec gnc_dmy2timespec_end (int day, int month, int year);
+
+/** The gnc_iso8601_to_timespec_gmt() routine converts an ISO-8601 style
+ * date/time string to Timespec. Please note that ISO-8601 strings
+ * are a representation of Universal Time (UTC), and as such, they
+ * 'store' UTC. To make them human readable, they show timezone
+ * information along with a local-time string. But fundamentally,
+ * they *are* UTC. Thus, thir routine takes a UTC input, and
+ * returns a UTC output.
+ *
+ * For example: 1998-07-17 11:00:00.68-0500
+ * is 680 milliseconds after 11 o'clock, central daylight time
+ * It is also 680 millisecs after 16:00:00 hours UTC.
+ * \return The universl time.
+ *
+ * XXX Caution: this routine does not handle strings that specify
+ * times before January 1 1970.
+ */
+Timespec gnc_iso8601_to_timespec_gmt(const char *);
+
+/** The gnc_timespec_to_iso8601_buff() routine takes the input
+ * UTC Timespec value and prints it as an ISO-8601 style string.
+ * The buffer must be long enough to contain the NULL-terminated
+ * string (32 characters + NUL). This routine returns a pointer
+ * to the null terminator (and can thus be used in the 'stpcpy'
+ * metaphor of string concatenation).
+ *
+ * Please note that ISO-8601 strings are a representation of
+ * Universal Time (UTC), and as such, they 'store' UTC. To make them
+ * human readable, they show timezone information along with a
+ * local-time string. But fundamentally, they *are* UTC. Thus,
+ * this routine takes a UTC input, and returns a UTC output.
+ *
+ * The string generated by this routine uses the local timezone
+ * on the machine on which it is executing to create the timestring.
+ */
+char * gnc_timespec_to_iso8601_buff (Timespec ts, char * buff);
+
+/** DOCUMENT ME! FIXME: Probably similar to xaccDMYToSec() this date
+ * routine might return incorrect values for dates before 1970. */
+void gnc_timespec2dmy (Timespec ts, int *day, int *month, int *year);
+
+/** Add a number of months to a time value and normalize. Optionally
+ * also track the last day of the month, i.e. 1/31 -> 2/28 -> 3/30. */
+void date_add_months (struct tm *tm, int months, gboolean track_last_day);
+
+/** \warning hack alert XXX FIXME -- these date routines return incorrect
+ * values for dates before 1970. Most of them are good only up
+ * till 2038. This needs fixing ...
+ *
+ * XXX This routine should be modified to assume that the
+ * the user wanted the time at noon, localtime. The returned
+ * time_t should be seconds (at GMT) of the local noon-time.
+*/
+time_t xaccDMYToSec (int day, int month, int year);
+
+/** The gnc_timezone function returns the number of seconds *west*
+ * of UTC represented by the tm argument, adjusted for daylight
+ * savings time.
+ *
+ * This function requires a tm argument returned by localtime or set
+ * by mktime. This is a strange function! It requires that localtime
+ * or mktime be called before use. Subsequent calls to localtime or
+ * mktime *may* invalidate the result! The actual contents of tm *may*
+ * be used for both timezone offset and daylight savings time, or only
+ * daylight savings time! Timezone stuff under unix is not
+ * standardized and is a big mess.
+ */
+long int gnc_timezone (struct tm *tm);
+// @}
+
+/* ------------------------------------------------------------------------ */
+/** \name QofDateFormat functions */
+// @{
+/** The qof_date_format_get routine returns the date format that
+ * the date printing will use when printing a date, and the scaning
+ * routines will assume when parsing a date.
+ * @returns: the one of the enumerated date formats.
+ */
+QofDateFormat qof_date_format_get(void);
+
+/**
+ * The qof_date_format_set() routine sets date format to one of
+ * US, UK, CE, OR ISO. Checks to make sure it's a legal value.
+ * Args: QofDateFormat: enumeration indicating preferred format
+ */
+void qof_date_format_set(QofDateFormat df);
+
+/** This function returns a strftime formatting string for printing an
+ * all numeric date (e.g. 2005-09-14). The string returned is based
+ * upon the location specified.
+ *
+ * @param df The date style (us, uk, iso, etc) that should be provided.
+ *
+ * @return A formatting string that will print a date in the
+ * requested style */
+const gchar *qof_date_format_get_string(QofDateFormat df);
+
+/** This function returns a strftime formatting string for printing a
+ * date using words and numbers (e.g. 2005-September-14). The string
+ * returned is based upon the location specified.
+ *
+ * @param df The date style (us, uk, iso, etc) that should be provided.
+ *
+ * @return A formatting string that will print a date in the
+ * requested style */
+const gchar *qof_date_text_format_get_string(QofDateFormat df);
+// @}
+
+/** dateSeparator
+ * Return the field separator for the current date format
+ *
+ * Args: none
+ *
+ * Return: date character
+ *
+ * Globals: global dateFormat value
+ */
+char dateSeparator(void);
+
+/** \name Date Printing/Scanning functions
+ */
+// @{
+/**
+ * \warning HACK ALERT -- the scan and print routines should probably
+ * be moved to somewhere else. The engine really isn't involved with
+ * things like printing formats. This is needed mostly by the GUI and
+ * so on. If a file-io thing needs date handling, it should do it
+ * itself, instead of depending on the routines here.
+ */
+
+/** qof_print_date_dmy_buff
+ * Convert a date as day / month / year integers into a localized string
+ * representation
+ *
+ * Args: buff - pointer to previously allocated character array; its size
+ * must be at lease MAX_DATE_LENTH bytes.
+ * len - length of the buffer, in bytes.
+ * day - day of the month as 1 ... 31
+ * month - month of the year as 1 ... 12
+ * year - year (4-digit)
+ *
+ * Returns: number of characters printed
+ *
+ * Globals: global dateFormat value
+ **/
+size_t qof_print_date_dmy_buff (char * buff, size_t buflen, int day, int month, int year);
+
+/** Convenience: calls through to qof_print_date_dmy_buff(). **/
+size_t qof_print_date_buff (char * buff, size_t buflen, time_t secs);
+
+/** Convenience; calls through to qof_print_date_dmy_buff(). **/
+size_t qof_print_gdate( char *buf, size_t bufflen, GDate *gd );
+
+/** Convenience; calls through to qof_print_date_dmy_buff().
+ * Return: string, which should be freed when no longer needed.
+ * **/
+char * qof_print_date (time_t secs);
+
+/** Convenience; calls through to qof_print_date_dmy_buff().
+ * Return: static global string.
+ * \warning This routine is not thread-safe, because it uses a single
+ * global buffer to store the return value. Use qof_print_date_buff()
+ * or qof_print_date() instead.
+ * **/
+const char * gnc_print_date(Timespec ts);
+
+/* ------------------------------------------------------------------ */
+/* time printing utilities */
+
+/** The qof_print_hours_elapsed_buff() routine will print the 'secs' argument
+ * as HH:MM, and will print the seconds if show_secs is true.
+ * Thus, for example, secs=3599 will print as 0:59
+ * Returns the number of bytes copied.
+ */
+size_t qof_print_hours_elapsed_buff (char * buff, size_t len, int secs, gboolean show_secs);
+size_t qof_print_minutes_elapsed_buff (char * buff, size_t len, int secs, gboolean show_secs);
+
+/** The qof_print_time_buff() routine prints only the hour-part of the date.
+ * Thus, if secs is ...
+ * Returns the number of bytes printed.
+ */
+
+size_t qof_print_time_buff (char * buff, size_t len, time_t secs);
+size_t qof_print_date_time_buff (char * buff, size_t len, time_t secs);
+
+/** The qof_is_same_day() routine returns 0 if both times are in the
+ * same day.
+ */
+
+gboolean qof_is_same_day (time_t, time_t);
+
+/* ------------------------------------------------------------------ */
+/** The xaccDateUtilGetStamp() routine will take the given time in
+ * seconds and return a buffer containing a textual for the date.
+ * @param thyme The time in seconds to convert.
+ * @return A pointer to the generated string.
+ * @note The caller owns this buffer and must free it when done. */
+char * xaccDateUtilGetStamp (time_t thyme);
+
+/** qof_scan_date
+ * Convert a string into day / month / year integers according to
+ * the current dateFormat value.
+ *
+ * Args: buff - pointer to date string
+ * day - will store day of the month as 1 ... 31
+ * month - will store month of the year as 1 ... 12
+ * year - will store the year (4-digit)
+ *
+ * Return: TRUE if the string seemed to be a valid date; else FALSE.
+ *
+ * Globals: uses global dateFormat value to assist in parsing.
+ */
+gboolean qof_scan_date (const char *buff, int *day, int *month, int *year);
+
+/** as above, but returns seconds */
+gboolean qof_scan_date_secs (const char *buff, time_t *secs);
+
+// @}
+/** \name Date Start/End Adjustment routines
+ * Given a time value, adjust it to be the beginning or end of that day.
+ */
+// @{
+
+/** The gnc_tm_set_day_start() inline routine will set the appropriate
+ * fields in the struct tm to indicate the first second of that day.
+ * This routine assumes that the contents of the data structure is
+ * already in normalized form. */
+static inline
+void gnc_tm_set_day_start (struct tm *tm)
+{
+ /* First second of the day */
+ tm->tm_hour = 0;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ tm->tm_isdst = -1;
+}
+
+/** The gnc_tm_set_day_start() inline routine will set the appropriate
+ * fields in the struct tm to indicate noon of that day. This
+ * routine assumes that the contents of the data structure is already
+ * in normalized form.*/
+static inline
+void gnc_tm_set_day_middle (struct tm *tm)
+{
+ /* First second of the day */
+ tm->tm_hour = 12;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ tm->tm_isdst = -1;
+}
+
+/** The gnc_tm_set_day_start() inline routine will set the appropriate
+ * fields in the struct tm to indicate the last second of that day.
+ * This routine assumes that the contents of the data structure is
+ * already in normalized form.*/
+static inline
+void gnc_tm_set_day_end (struct tm *tm)
+{
+ /* Last second of the day */
+ tm->tm_hour = 23;
+ tm->tm_min = 59;
+ tm->tm_sec = 59;
+ tm->tm_isdst = -1;
+}
+
+/** The gnc_tm_get_day_start() routine will convert the given time in
+ * seconds to the struct tm format, and then adjust it to the
+ * first second of that day. */
+void gnc_tm_get_day_start(struct tm *tm, time_t time_val);
+
+/** The gnc_tm_get_day_end() routine will convert the given time in
+ * seconds to the struct tm format, and then adjust it to the
+ * last second of that day. */
+void gnc_tm_get_day_end(struct tm *tm, time_t time_val);
+
+/** The gnc_timet_get_day_start() routine will take the given time in
+ * seconds and adjust it to the last second of that day. */
+time_t gnc_timet_get_day_start(time_t time_val);
+
+/** The gnc_timet_get_day_end() routine will take the given time in
+ * seconds and adjust it to the last second of that day. */
+time_t gnc_timet_get_day_end(time_t time_val);
+
+#ifndef GNUCASH_MAJOR_VERSION
+/** The gnc_timet_get_day_start() routine will take the given time in
+ * GLib GDate format and adjust it to the last second of that day.
+ *
+ * @deprecated
+ */
+time_t gnc_timet_get_day_start_gdate (GDate *date);
+
+/** The gnc_timet_get_day_end() routine will take the given time in
+ * GLib GDate format and adjust it to the last second of that day.
+ *
+ * @deprecated
+ */
+time_t gnc_timet_get_day_end_gdate (GDate *date);
+#endif /* GNUCASH_MAJOR_VERSION */
+
+/** Get the numerical last date of the month. (28, 29, 30, 31) */
+int date_get_last_mday(struct tm *tm);
+
+/** Is the mday field the last day of the specified month.*/
+gboolean date_is_last_mday(struct tm *tm);
+
+/** DOCUMENT ME! Probably the same as date_get_last_mday() */
+int gnc_date_my_last_mday (int month, int year);
+/** DOCUMENT ME! Probably the same as date_get_last_mday() */
+int gnc_timespec_last_mday (Timespec ts);
+// @}
+
+/* ======================================================== */
+
+/** \name Today's Date */
+// @{
+/** The gnc_tm_get_today_start() routine takes a pointer to a struct
+ * tm and fills it in with the first second of the today. */
+void gnc_tm_get_today_start(struct tm *tm);
+
+/** The gnc_tm_get_today_end() routine takes a pointer to a struct
+ * tm and fills it in with the last second of the today. */
+void gnc_tm_get_today_end(struct tm *tm);
+
+/** The gnc_timet_get_today_start() routine returns a time_t value
+ * corresponding to the first second of today. */
+time_t gnc_timet_get_today_start(void);
+
+/** The gnc_timet_get_today_end() routine returns a time_t value
+ * corresponding to the last second of today. */
+time_t gnc_timet_get_today_end(void);
+
+/** The xaccDateUtilGetStampNow() routine returns the current time in
+ * seconds in textual format.
+ * @return A pointer to the generated string.
+ * @note The caller owns this buffer and must free it when done. */
+char * xaccDateUtilGetStampNow (void);
+
+//@}
+//@}
+#endif /* GNC_DATE_H */
+
Added: gnucash/trunk/lib/libqof/qof/gnc-engine-util.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-engine-util.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-engine-util.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,276 @@
+/********************************************************************\
+ * gnc-engine-util.c -- QOF utility functions *
+ * Copyright (C) 1997 Robin D. Clark *
+ * Copyright (C) 1997-2001,2004 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ * Author: Rob Clark (rclark at cs.hmc.edu) *
+ * Author: Linas Vepstas (linas at linas.org) *
+\********************************************************************/
+
+#include "config.h"
+
+#include <ctype.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <string.h>
+#include "qof.h"
+#include "gnc-engine-util.h"
+
+
+/********************************************************************\
+\********************************************************************/
+
+/* Search for str2 in first nchar chars of str1, ignore case.. Return
+ * pointer to first match, or null. */
+char *
+strncasestr(const char *str1, const char *str2, size_t len)
+{
+ while (*str1 && len--)
+ {
+ if (toupper(*str1) == toupper(*str2))
+ {
+ if (strncasecmp(str1,str2,strlen(str2)) == 0)
+ {
+ return (char *) str1;
+ }
+ }
+ str1++;
+ }
+ return NULL;
+}
+
+/* Search for str2 in str1, ignore case. Return pointer to first
+ * match, or null. */
+char *
+strcasestr(const char *str1, const char *str2)
+{
+ size_t len = strlen (str1);
+ char * retval = strncasestr (str1, str2, len);
+ return retval;
+}
+
+/********************************************************************\
+\********************************************************************/
+
+int
+safe_strcmp (const char * da, const char * db)
+{
+ SAFE_STRCMP (da, db);
+ return 0;
+}
+
+int
+safe_strcasecmp (const char * da, const char * db)
+{
+ SAFE_STRCASECMP (da, db);
+ return 0;
+}
+
+int
+null_strcmp (const char * da, const char * db)
+{
+ if (da && db) return strcmp (da, db);
+ if (!da && db && 0==db[0]) return 0;
+ if (!db && da && 0==da[0]) return 0;
+ if (!da && db) return -1;
+ if (da && !db) return +1;
+ return 0;
+}
+
+/********************************************************************\
+\********************************************************************/
+
+#define MAX_DIGITS 50
+
+/* inverse of strtoul */
+char *
+ultostr (unsigned long val, int base)
+{
+ char buf[MAX_DIGITS];
+ unsigned long broke[MAX_DIGITS];
+ int i;
+ unsigned long places=0, reval;
+
+ if ((2>base) || (36<base)) return NULL;
+
+ /* count digits */
+ places = 0;
+ for (i=0; i<MAX_DIGITS; i++) {
+ broke[i] = val;
+ places ++;
+ val /= base;
+ if (0 == val) break;
+ }
+
+ /* normalize */
+ reval = 0;
+ for (i=places-2; i>=0; i--) {
+ reval += broke[i+1];
+ reval *= base;
+ broke[i] -= reval;
+ }
+
+ /* print */
+ for (i=0; i<(int)places; i++) {
+ if (10>broke[i]) {
+ buf[places-1-i] = 0x30+broke[i]; /* ascii digit zero */
+ } else {
+ buf[places-1-i] = 0x41-10+broke[i]; /* ascii capital A */
+ }
+ }
+ buf[places] = 0x0;
+
+ return g_strdup (buf);
+}
+
+/********************************************************************\
+ * returns TRUE if the string is a number, possibly with whitespace
+\********************************************************************/
+
+gboolean
+gnc_strisnum(const char *s)
+{
+ if (s == NULL) return FALSE;
+ if (*s == 0) return FALSE;
+
+ while (*s && isspace(*s))
+ s++;
+
+ if (*s == 0) return FALSE;
+ if (!isdigit(*s)) return FALSE;
+
+ while (*s && isdigit(*s))
+ s++;
+
+ if (*s == 0) return TRUE;
+
+ while (*s && isspace(*s))
+ s++;
+
+ if (*s == 0) return TRUE;
+
+ return FALSE;
+}
+
+/********************************************************************\
+ * our own version of stpcpy
+\********************************************************************/
+
+char *
+gnc_stpcpy (char *dest, const char *src)
+{
+ strcpy (dest, src);
+ return (dest + strlen (src));
+}
+
+/* =================================================================== */
+/* Return NULL if the field is whitespace (blank, tab, formfeed etc.)
+ * Else return pointer to first non-whitespace character. */
+
+const char *
+qof_util_whitespace_filter (const char * val)
+{
+ size_t len;
+ if (!val) return NULL;
+
+ len = strspn (val, "\a\b\t\n\v\f\r ");
+ if (0 == val[len]) return NULL;
+ return val+len;
+}
+
+/* =================================================================== */
+/* Return integer 1 if the string starts with 't' or 'T' or contains the
+ * word 'true' or 'TRUE'; if string is a number, return that number. */
+
+int
+qof_util_bool_to_int (const char * val)
+{
+ const char * p = qof_util_whitespace_filter (val);
+ if (!p) return 0;
+ if ('t' == p[0]) return 1;
+ if ('T' == p[0]) return 1;
+ if ('y' == p[0]) return 1;
+ if ('Y' == p[0]) return 1;
+ if (strstr (p, "true")) return 1;
+ if (strstr (p, "TRUE")) return 1;
+ if (strstr (p, "yes")) return 1;
+ if (strstr (p, "YES")) return 1;
+ return atoi (val);
+}
+
+/********************************************************************\
+ * The engine string cache
+\********************************************************************/
+
+static GCache * gnc_string_cache = NULL;
+
+/* maybe better to make this function static */
+GCache*
+gnc_engine_get_string_cache(void)
+{
+ if(!gnc_string_cache)
+ {
+ gnc_string_cache = g_cache_new(
+ (GCacheNewFunc) g_strdup, g_free,
+ (GCacheDupFunc) g_strdup, g_free, g_str_hash,
+ g_str_hash, g_str_equal);
+ }
+ return gnc_string_cache;
+}
+
+void
+gnc_engine_string_cache_destroy (void)
+{
+ g_cache_destroy (gnc_string_cache);
+ gnc_string_cache = NULL;
+}
+
+void gnc_string_cache_remove(gpointer key)
+{
+ g_cache_remove(gnc_engine_get_string_cache(), key);
+}
+
+gpointer gnc_string_cache_insert(gpointer key)
+{
+ return g_cache_insert(gnc_engine_get_string_cache(), key);
+}
+
+void
+qof_init (void)
+{
+ gnc_engine_get_string_cache ();
+ guid_init ();
+ qof_object_initialize ();
+ qof_query_init ();
+ qof_book_register ();
+}
+
+void
+qof_close(void)
+{
+ qof_query_shutdown ();
+ qof_object_shutdown ();
+ guid_shutdown ();
+ gnc_engine_string_cache_destroy ();
+}
+
+
+/************************* END OF FILE ******************************\
+\********************************************************************/
Added: gnucash/trunk/lib/libqof/qof/gnc-engine-util.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-engine-util.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-engine-util.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,248 @@
+/********************************************************************\
+ * gnc-engine-util.h -- QOF utility functions *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+\********************************************************************/
+
+/** @addtogroup Utilities
+ @{ */
+/** @file gnc-engine-util.h
+ @brief QOF utility functions
+ @author Copyright (C) 1997 Robin D. Clark <rclark at cs.hmc.edu>
+ @author Copyright (C) 2000 Bill Gribble <grib at billgribble.com>
+ @author Copyright (C) 1997-2002,2004 Linas Vepstas <linas at linas.org>
+*/
+
+#ifndef QOF_UTIL_H
+#define QOF_UTIL_H
+
+#include <glib.h>
+#include <stddef.h>
+#include "config.h"
+#include "qof.h"
+
+/** Macros *****************************************************/
+
+/* CAS: Notice that this macro does nothing if pointer args are equal.
+ Otherwise, it returns an integer. Actually, perhaps these macro
+ should be private. They are NOT good substitutes for the function
+ versions like safe_strcmp(). Maybe external users of these 3
+ macros should be converted to use safe_strcmp(). Actually, THESE
+ MACROS AFFECT CONTROL FLOW. YUCK! */
+#define SAFE_STRCMP_REAL(fcn,da,db) { \
+ if ((da) && (db)) { \
+ if ((da) != (db)) { \
+ int retval = fcn ((da), (db)); \
+ /* if strings differ, return */ \
+ if (retval) return retval; \
+ } \
+ } else \
+ if ((!(da)) && (db)) { \
+ return -1; \
+ } else \
+ if ((da) && (!(db))) { \
+ return +1; \
+ } \
+}
+
+#define SAFE_STRCMP(da,db) SAFE_STRCMP_REAL(strcmp,(da),(db))
+#define SAFE_STRCASECMP(da,db) SAFE_STRCMP_REAL(strcasecmp,(da),(db))
+
+/** \name typedef enum as string macros
+@{
+*/
+#define ENUM_BODY(name, value) \
+ name value,
+
+#define AS_STRING_CASE(name, value) \
+ case name: { return #name; }
+
+#define FROM_STRING_CASE(name, value) \
+ if (strcmp(str, #name) == 0) { \
+ return name; }
+
+#define DEFINE_ENUM(name, list) \
+ typedef enum { \
+ list(ENUM_BODY) \
+ }name;
+
+#define AS_STRING_DEC(name, list) \
+ const char* name##asString(name n);
+
+#define AS_STRING_FUNC(name, list) \
+ const char* name##asString(name n) { \
+ switch (n) { \
+ list(AS_STRING_CASE) \
+ default: return ""; } }
+
+#define FROM_STRING_DEC(name, list) \
+ name name##fromString \
+ (const char* str);
+
+#define FROM_STRING_FUNC(name, list) \
+ name name##fromString \
+ (const char* str) { \
+ if(str == NULL) { return 0; } \
+ list(FROM_STRING_CASE) \
+ return 0; }
+
+/** @} */
+
+/** \name enum as string with no typedef
+@{
+
+ Similar but used when the enum is NOT a typedef
+ note the LACK of a define_enum macro - don't use one!
+
+ ENUM_BODY is used in both types.
+ */
+
+#define FROM_STRING_DEC_NON_TYPEDEF(name, list) \
+ void name##fromString \
+ (const char* str, enum name *type);
+
+#define FROM_STRING_CASE_NON_TYPEDEF(name, value) \
+ if (strcmp(str, #name) == 0) { *type = name; }
+
+#define FROM_STRING_FUNC_NON_TYPEDEF(name, list) \
+ void name##fromString \
+ (const char* str, enum name *type) { \
+ if(str == NULL) { return; } \
+ list(FROM_STRING_CASE_NON_TYPEDEF) }
+
+#define AS_STRING_DEC_NON_TYPEDEF(name, list) \
+ const char* name##asString(enum name n);
+
+#define AS_STRING_FUNC_NON_TYPEDEF(name, list) \
+ const char* name##asString(enum name n) { \
+ switch (n) { \
+ list(AS_STRING_CASE_NON_TYPEDEF) \
+ default: return ""; } }
+
+#define AS_STRING_CASE_NON_TYPEDEF(name, value) \
+ case name: { return #name; }
+
+/** @} */
+
+/* Define the long long int conversion for scanf */
+#if HAVE_SCANF_LLD
+# define GNC_SCANF_LLD "%lld"
+#else
+# define GNC_SCANF_LLD "%qd"
+#endif
+
+/** @name Convenience wrappers
+ @{
+*/
+
+/** \brief Initialise the Query Object Framework
+
+Used for non-Guile applications or test routines.
+*/
+void qof_init (void);
+
+/** \brief Safely close down the Query Object Framework
+
+Used for non-Guile applications or test routines.
+*/
+void qof_close (void);
+
+/** @} */
+
+/** Prototypes *************************************************/
+
+/** The safe_strcmp compares strings a and b the same way that strcmp()
+ * does, except that either may be null. This routine assumes that
+ * a non-null string is always greater than a null string.
+ */
+int safe_strcmp (const char * da, const char * db);
+int safe_strcasecmp (const char * da, const char * db);
+
+/** The null_strcmp compares strings a and b the same way that strcmp()
+ * does, except that either may be null. This routine assumes that
+ * a null string is equal to the empty string.
+ */
+int null_strcmp (const char * da, const char * db);
+
+/** Search for str2 in first nchar chars of str1, ignore case. Return
+ * pointer to first match, or null. These are just like that strnstr
+ * and the strstr functions, except that they ignore the case. */
+extern char *strncasestr(const char *str1, const char *str2, size_t len);
+extern char *strcasestr(const char *str1, const char *str2);
+
+/** The ultostr() subroutine is the inverse of strtoul(). It accepts a
+ * number and prints it in the indicated base. The returned string
+ * should be g_freed when done. */
+char * ultostr (unsigned long val, int base);
+
+/** Returns true if string s is a number, possibly surrounded by
+ * whitespace. */
+gboolean gnc_strisnum(const char *s);
+
+/** Local copy of stpcpy, used wtih libc's that don't have one. */
+char * gnc_stpcpy (char *dest, const char *src);
+
+#ifndef HAVE_STPCPY
+#define stpcpy gnc_stpcpy
+#endif
+
+/** Return NULL if the field is whitespace (blank, tab, formfeed etc.)
+ * Else return pointer to first non-whitespace character.
+ */
+const char * qof_util_whitespace_filter (const char * val);
+
+/** Return integer 1 if the string starts with 't' or 'T' or
+ * contains the word 'true' or 'TRUE'; if string is a number,
+ * return that number. (Leading whitespace is ignored). */
+int qof_util_bool_to_int (const char * val);
+
+/** Many strings used throughout the engine are likely to be duplicated.
+ * So we provide a reference counted cache system for the strings, which
+ * shares strings whenever possible.
+ *
+ * Use g_cache_insert to insert a string into the cache (it will return a
+ * pointer to the cached string).
+ * Basically you should use this instead of g_strdup.
+ *
+ * Use g_cache_remove (giving it a pointer to a cached string) if the string
+ * is unused. If this is the last reference to the string it will be
+ * removed from the cache, otherwise it will just decrement the
+ * reference count.
+ * Basically you should use this instead of g_free.
+ *
+ * Note that all the work is done when inserting or removing. Once
+ * cached the strings are just plain C strings.
+ */
+
+/** Get the gnc_string_cache. Create it if it doesn't exist already
+
+\todo hide the gcache as a static */
+GCache* gnc_engine_get_string_cache(void);
+
+void gnc_engine_string_cache_destroy (void);
+
+/* You can use this function as a destroy notifier for a GHashTable
+ that uses common strings as keys (or values, for that matter.) */
+void gnc_string_cache_remove(gpointer key);
+
+/* You can use this function with g_hash_table_insert(), or the key
+ (or value), as long as you use the destroy notifier above. */
+gpointer gnc_string_cache_insert(gpointer key);
+
+#endif /* QOF_UTIL_H */
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/gnc-event-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-event-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-event-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,37 @@
+/********************************************************************
+ * gnc-event-p.h -- private engine event handling interface *
+ * Copyright 2000 Dave Peticolas <dave at krondo.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ ********************************************************************/
+
+#ifndef GNC_EVENT_P_H
+#define GNC_EVENT_P_H
+
+#include "gnc-event.h"
+#include "qofid.h"
+
+/* XXX deprecated, but still usedion on postgres backend */
+void gnc_engine_generate_event (const GUID *, QofIdType, GNCEngineEventType);
+
+/* generates an event even when events are suspended! */
+void gnc_engine_force_event (QofEntity *entity,
+ GNCEngineEventType event_type);
+
+#endif
Added: gnucash/trunk/lib/libqof/qof/gnc-event.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-event.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-event.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,224 @@
+/********************************************************************
+ * gnc-event.c -- engine event handling implementation *
+ * Copyright 2000 Dave Peticolas <dave at krondo.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ ********************************************************************/
+
+#include "config.h"
+
+#include "gnc-event-p.h"
+#include "gnc-trace.h"
+
+
+/** Declarations ****************************************************/
+
+typedef struct
+{
+ GNCEngineEventHandler handler;
+ gpointer user_data;
+
+ gint handler_id;
+} HandlerInfo;
+
+
+/** Static Variables ************************************************/
+static guint suspend_counter = 0;
+static gint next_handler_id = 1;
+static GList *handlers = NULL;
+
+/* This static indicates the debugging module that this .o belongs to. */
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+
+/** Implementations *************************************************/
+
+gint
+gnc_engine_register_event_handler (GNCEngineEventHandler handler,
+ gpointer user_data)
+{
+ HandlerInfo *hi;
+ gint handler_id;
+ GList *node;
+
+ ENTER ("(handler=%p, data=%p)", handler, user_data);
+ /* sanity check */
+ if (!handler)
+ {
+ PERR ("no handler specified");
+ return 0;
+ }
+
+ /* look for a free handler id */
+ handler_id = next_handler_id;
+ node = handlers;
+
+ while (node)
+ {
+ hi = node->data;
+
+ if (hi->handler_id == handler_id)
+ {
+ handler_id++;
+ node = handlers;
+ continue;
+ }
+
+ node = node->next;
+ }
+
+ /* Found one, add the handler */
+ hi = g_new0 (HandlerInfo, 1);
+
+ hi->handler = handler;
+ hi->user_data = user_data;
+ hi->handler_id = handler_id;
+
+ handlers = g_list_prepend (handlers, hi);
+
+ /* Update id for next registration */
+ next_handler_id = handler_id + 1;
+
+ LEAVE ("(handler=%p, data=%p) handler_id=%d", handler, user_data, handler_id);
+ return handler_id;
+}
+
+void
+gnc_engine_unregister_event_handler (gint handler_id)
+{
+ GList *node;
+
+ ENTER ("(handler_id=%d)", handler_id);
+ for (node = handlers; node; node = node->next)
+ {
+ HandlerInfo *hi = node->data;
+
+ if (hi->handler_id != handler_id)
+ continue;
+
+ /* Found it, take out of list */
+ handlers = g_list_remove_link (handlers, node);
+
+ LEAVE ("(handler_id=%d) handler=%p data=%p", handler_id, hi->handler, hi->user_data);
+ /* safety */
+ hi->handler = NULL;
+
+ g_list_free_1 (node);
+ g_free (hi);
+
+ return;
+ }
+
+ PERR ("no such handler: %d", handler_id);
+}
+
+void
+gnc_engine_suspend_events (void)
+{
+ suspend_counter++;
+
+ if (suspend_counter == 0)
+ {
+ PERR ("suspend counter overflow");
+ }
+}
+
+void
+gnc_engine_resume_events (void)
+{
+ if (suspend_counter == 0)
+ {
+ PERR ("suspend counter underflow");
+ return;
+ }
+
+ suspend_counter--;
+}
+
+static void
+gnc_engine_generate_event_internal (QofEntity *entity,
+ GNCEngineEventType event_type)
+{
+ GList *node;
+ GList *next_node = NULL;
+
+ g_return_if_fail(entity);
+
+ switch (event_type)
+ {
+ case GNC_EVENT_NONE:
+ return;
+
+ case GNC_EVENT_CREATE:
+ case GNC_EVENT_MODIFY:
+ case GNC_EVENT_DESTROY:
+ case GNC_EVENT_ADD:
+ case GNC_EVENT_REMOVE:
+ break;
+
+ default:
+ PERR ("bad event type %d", event_type);
+ return;
+ }
+
+ for (node = handlers; node; node = next_node)
+ {
+ HandlerInfo *hi = node->data;
+
+ next_node = node->next;
+ PINFO ("id=%d hi=%p han=%p", hi->handler_id, hi, hi->handler);
+ if (hi->handler)
+ hi->handler ((GUID *)&entity->guid, entity->e_type, event_type, hi->user_data);
+ }
+}
+
+void
+gnc_engine_force_event (QofEntity *entity,
+ GNCEngineEventType event_type)
+{
+ if (!entity)
+ return;
+
+ gnc_engine_generate_event_internal (entity, event_type);
+}
+
+void
+gnc_engine_gen_event (QofEntity *entity, GNCEngineEventType event_type)
+{
+ if (!entity)
+ return;
+
+ if (suspend_counter)
+ return;
+
+ gnc_engine_generate_event_internal (entity, event_type);
+}
+
+void
+gnc_engine_generate_event (const GUID *guid, QofIdType e_type,
+ GNCEngineEventType event_type)
+{
+ QofEntity ent;
+ ent.guid = *guid;
+ ent.e_type = e_type;
+ if (suspend_counter) return;
+ gnc_engine_generate_event_internal (&ent, event_type);
+}
+
+/* =========================== END OF FILE ======================= */
Added: gnucash/trunk/lib/libqof/qof/gnc-event.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-event.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-event.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,118 @@
+/********************************************************************
+ * gnc-event.h -- engine event handling interface *
+ * Copyright 2000 Dave Peticolas <dave at krondo.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ ********************************************************************/
+
+/** @addtogroup Event
+@{
+*/
+/** @file gnc-event.h
+ @brief engine event handling interface
+ @author Copyright 2000 Dave Peticolas <dave at krondo.com>
+*/
+
+#ifndef GNC_EVENT_H
+#define GNC_EVENT_H
+
+#include <glib.h>
+
+#include "guid.h"
+#include "qofid.h"
+
+typedef enum
+{
+ GNC_EVENT_NONE = 0,
+ GNC_EVENT_CREATE = 1 << 0,
+ GNC_EVENT_MODIFY = 1 << 1,
+ GNC_EVENT_DESTROY = 1 << 2,
+ GNC_EVENT_ADD = 1 << 3,
+ GNC_EVENT_REMOVE = 1 << 4,
+ GNC_EVENT_ALL = 0xff
+} GNCEngineEventType;
+
+
+/** GNCEngineEventHandler
+
+ * Handler invoked when an engine event occurs.
+ *
+ * @param entity: GUID of entity generating event
+ * @param type: QofIdType of the entity generating the event
+ * @param event_type: one of the single-bit GNCEngineEventTypes, not a combination
+ * @param user_data: user_data supplied when handler was registered.
+ */
+typedef void (*GNCEngineEventHandler) (GUID *entity, QofIdType type,
+ GNCEngineEventType event_type,
+ gpointer user_data);
+
+/** gnc_engine_register_event_handler
+
+ * Register a handler for engine events.
+ *
+ * @param handler: handler to register
+ * @param user_data: data provided when handler is invoked
+ *
+ * @return id identifying handler
+ */
+gint gnc_engine_register_event_handler (GNCEngineEventHandler handler,
+ gpointer user_data);
+
+/** gnc_engine_unregister_event_handler
+
+ * Unregister an engine event handler.
+ *
+ * @param handler_id: the id of the handler to unregister
+ */
+void gnc_engine_unregister_event_handler (gint handler_id);
+
+/** gnc_engine_generate_event
+
+ * Invoke all registered event handlers using the given arguments.
+ *
+ * GNC_EVENT_CREATE events should be generated after the object
+ * has been created and registered in the engine entity table.
+ * GNC_EVENT_MODIFY events should be generated whenever any data
+ * member or submember (i.e., splits) is changed.
+ * GNC_EVENT_DESTROY events should be called before the object
+ * has been destroyed or removed from the entity table.
+ *
+ * @param entity: the GUID of the entity generating the event
+ * @param event_type: the type of event -- this should be one of the
+ * single-bit GNCEngineEventType values, not a combination.
+ */
+void gnc_engine_gen_event (QofEntity *entity,
+ GNCEngineEventType event_type);
+/** gnc_engine_suspend_events
+
+ * Suspend all engine events. This function may be
+ * called multiple times. To resume event generation,
+ * an equal number of calls to gnc_engine_resume_events
+ * must be made.
+ */
+void gnc_engine_suspend_events (void);
+
+/** gnc_engine_resume_events
+
+ * Resume engine event generation.
+ */
+void gnc_engine_resume_events (void);
+
+#endif
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/gnc-numeric.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-numeric.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-numeric.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1306 @@
+/********************************************************************
+ * gnc-numeric.c -- an exact-number library for accounting use *
+ * Copyright (C) 2000 Bill Gribble *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ *******************************************************************/
+
+#define _GNU_SOURCE
+
+#include "config.h"
+
+#include <glib.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gnc-numeric.h"
+#include "qofmath128.c"
+
+/* static short module = MOD_ENGINE; */
+
+/* =============================================================== */
+
+#if 0
+static const char * _numeric_error_strings[] =
+{
+ "No error",
+ "Argument is not a valid number",
+ "Intermediate result overflow",
+ "Argument denominators differ in GNC_HOW_DENOM_FIXED operation",
+ "Remainder part in GNC_HOW_RND_NEVER operation"
+};
+#endif
+
+/* =============================================================== */
+/* This function is small, simple, and used everywhere below,
+ * lets try to inline it.
+ */
+inline GNCNumericErrorCode
+gnc_numeric_check(gnc_numeric in)
+{
+ if(in.denom != 0)
+ {
+ return GNC_ERROR_OK;
+ }
+ else if(in.num)
+ {
+ if ((0 < in.num) || (-4 > in.num))
+ {
+ in.num = (gint64) GNC_ERROR_OVERFLOW;
+ }
+ return (GNCNumericErrorCode) in.num;
+ }
+ else
+ {
+ return GNC_ERROR_ARG;
+ }
+}
+
+/**
+ * Find the least common multiple of the denominators of a and b.
+ */
+
+static inline gint64
+gnc_numeric_lcd(gnc_numeric a, gnc_numeric b)
+{
+ qofint128 lcm;
+ if(gnc_numeric_check(a) || gnc_numeric_check(b))
+ {
+ return GNC_ERROR_ARG;
+ }
+
+ if (b.denom == a.denom) return a.denom;
+
+ /* Special case: smaller divides smoothly into larger */
+ if ((b.denom < a.denom) && ((a.denom % b.denom) == 0))
+ {
+ return a.denom;
+ }
+ if ((a.denom < b.denom) && ((b.denom % a.denom) == 0))
+ {
+ return b.denom;
+ }
+
+ lcm = lcm128 (a.denom, b.denom);
+ if (lcm.isbig) return GNC_ERROR_ARG;
+ return lcm.lo;
+}
+
+
+/** Return the ratio n/d reduced so that there are no common factors. */
+static inline gnc_numeric
+reduce128(qofint128 n, gint64 d)
+{
+ gint64 t;
+ gint64 num;
+ gint64 denom;
+ gnc_numeric out;
+ qofint128 red;
+
+ t = rem128 (n, d);
+ num = d;
+ denom = t;
+
+ /* The strategy is to use Euclid's algorithm */
+ while (denom > 0)
+ {
+ t = num % denom;
+ num = denom;
+ denom = t;
+ }
+ /* num now holds the GCD (Greatest Common Divisor) */
+
+ red = div128 (n, num);
+ if (red.isbig)
+ {
+ return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+ }
+ out.num = red.lo;
+ if (red.isneg) out.num = -out.num;
+ out.denom = d / num;
+ return out;
+}
+
+/********************************************************************
+ * gnc_numeric_zero_p
+ ********************************************************************/
+
+gboolean
+gnc_numeric_zero_p(gnc_numeric a)
+{
+ if(gnc_numeric_check(a))
+ {
+ return 0;
+ }
+ else
+ {
+ if((a.num == 0) && (a.denom != 0))
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+}
+
+/********************************************************************
+ * gnc_numeric_negative_p
+ ********************************************************************/
+
+gboolean
+gnc_numeric_negative_p(gnc_numeric a)
+{
+ if(gnc_numeric_check(a))
+ {
+ return 0;
+ }
+ else
+ {
+ if((a.num < 0) && (a.denom != 0))
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+}
+
+/********************************************************************
+ * gnc_numeric_positive_p
+ ********************************************************************/
+
+gboolean
+gnc_numeric_positive_p(gnc_numeric a)
+{
+ if(gnc_numeric_check(a))
+ {
+ return 0;
+ }
+ else
+ {
+ if((a.num > 0) && (a.denom != 0))
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+}
+
+/********************************************************************
+ * gnc_numeric_compare
+ * returns 1 if a>b, -1 if b>a, 0 if a == b
+ ********************************************************************/
+
+int
+gnc_numeric_compare(gnc_numeric a, gnc_numeric b)
+{
+ gint64 aa, bb;
+ qofint128 l, r;
+
+ if(gnc_numeric_check(a) || gnc_numeric_check(b))
+ {
+ return 0;
+ }
+
+ if (a.denom == b.denom)
+ {
+ if(a.num == b.num) return 0;
+ if(a.num > b.num) return 1;
+ return -1;
+ }
+
+ if ((a.denom > 0) && (b.denom > 0))
+ {
+ /* Avoid overflows using 128-bit intermediate math */
+ l = mult128 (a.num, b.denom);
+ r = mult128 (b.num, a.denom);
+ return cmp128 (l,r);
+ }
+
+ aa = a.num * a.denom;
+ bb = b.num * b.denom;
+
+ if(aa == bb) return 0;
+ if(aa > bb) return 1;
+ return -1;
+}
+
+
+/********************************************************************
+ * gnc_numeric_eq
+ ********************************************************************/
+
+gboolean
+gnc_numeric_eq(gnc_numeric a, gnc_numeric b)
+{
+ return ((a.num == b.num) && (a.denom == b.denom));
+}
+
+
+/********************************************************************
+ * gnc_numeric_equal
+ ********************************************************************/
+
+gboolean
+gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
+{
+ if ((a.denom == b.denom) && (a.denom > 0))
+ {
+ return (a.num == b.num);
+ }
+ if ((a.denom > 0) && (b.denom > 0))
+ {
+ // return (a.num*b.denom == b.num*a.denom);
+ qofint128 l = mult128 (a.num, b.denom);
+ qofint128 r = mult128 (b.num, a.denom);
+ return equal128 (l, r);
+
+#if ALT_WAY_OF_CHECKING_EQUALITY
+ gnc_numeric ra = gnc_numeric_reduce (a);
+ gnc_numeric rb = gnc_numeric_reduce (b);
+ if (ra.denom != rb.denom) return 0;
+ if (ra.num != rb.num) return 0;
+ return 1;
+#endif
+ }
+ if ((a.denom < 0) && (b.denom < 0))
+ {
+ return ((a.num * b.denom) == (a.denom * b.num));
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+
+/********************************************************************
+ * gnc_numeric_same
+ * would a and b be equal() if they were both converted to the same
+ * denominator?
+ ********************************************************************/
+
+int
+gnc_numeric_same(gnc_numeric a, gnc_numeric b, gint64 denom,
+ gint how) {
+ gnc_numeric aconv, bconv;
+
+ aconv = gnc_numeric_convert(a, denom, how);
+ bconv = gnc_numeric_convert(b, denom, how);
+
+ return(gnc_numeric_equal(aconv, bconv));
+}
+
+
+
+/********************************************************************
+ * gnc_numeric_add
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_add(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how)
+{
+ gnc_numeric sum;
+
+ if(gnc_numeric_check(a) || gnc_numeric_check(b))
+ {
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+
+ if((denom == GNC_DENOM_AUTO) &&
+ (how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_FIXED)
+ {
+ if(a.denom == b.denom) {
+ denom = a.denom;
+ }
+ else if(b.num == 0) {
+ denom = a.denom;
+ b.denom = a.denom;
+ }
+ else if(a.num == 0) {
+ denom = b.denom;
+ a.denom = b.denom;
+ }
+ else {
+ return gnc_numeric_error(GNC_ERROR_DENOM_DIFF);
+ }
+ }
+
+ if(a.denom < 0)
+ {
+ a.num *= a.denom;
+ a.denom = 1;
+ }
+
+ if(b.denom < 0)
+ {
+ b.num *= b.denom;
+ b.denom = 1;
+ }
+
+ /* Get an exact answer.. same denominator is the common case. */
+ if(a.denom == b.denom)
+ {
+ sum.num = a.num + b.num;
+ sum.denom = a.denom;
+ }
+ else
+ {
+ /* We want to do this:
+ * sum.num = a.num*b.denom + b.num*a.denom;
+ * sum.denom = a.denom*b.denom;
+ * but the multiply could overflow.
+ * Computing the LCD minimizes likelyhood of overflow
+ */
+ gint64 lcd;
+ qofint128 ca, cb, cab;
+ lcd = gnc_numeric_lcd(a,b);
+ if (GNC_ERROR_ARG == lcd)
+ {
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
+ ca = mult128 (a.num, lcd/a.denom);
+ if (ca.isbig) return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+
+ cb = mult128 (b.num, lcd/b.denom);
+ if (cb.isbig) return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+
+ cab = add128 (ca, cb);
+ if (cab.isbig) return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+
+ sum.num = cab.lo;
+ if (cab.isneg) sum.num = -sum.num;
+ sum.denom = lcd;
+ }
+
+ if((denom == GNC_DENOM_AUTO) &&
+ ((how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_LCD))
+ {
+ denom = gnc_numeric_lcd(a, b);
+ how = how & GNC_NUMERIC_RND_MASK;
+ }
+
+ return gnc_numeric_convert(sum, denom, how);
+}
+
+/********************************************************************
+ * gnc_numeric_sub
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how)
+{
+ gnc_numeric nb;
+ if(gnc_numeric_check(a) || gnc_numeric_check(b))
+ {
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+
+ nb = b;
+ nb.num = -nb.num;
+ return gnc_numeric_add (a, nb, denom, how);
+}
+
+/********************************************************************
+ * gnc_numeric_mul
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how)
+{
+ gnc_numeric product, result;
+ qofint128 bignume, bigdeno;
+
+ if(gnc_numeric_check(a) || gnc_numeric_check(b)) {
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+
+ if((denom == GNC_DENOM_AUTO) &&
+ (how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_FIXED) {
+ if(a.denom == b.denom) {
+ denom = a.denom;
+ }
+ else if(b.num == 0) {
+ denom = a.denom;
+ }
+ else if(a.num == 0) {
+ denom = b.denom;
+ }
+ else {
+ return gnc_numeric_error(GNC_ERROR_DENOM_DIFF);
+ }
+ }
+
+ if((denom == GNC_DENOM_AUTO) &&
+ ((how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_LCD))
+ {
+ denom = gnc_numeric_lcd(a, b);
+ how = how & GNC_NUMERIC_RND_MASK;
+ }
+
+ if(a.denom < 0) {
+ a.num *= a.denom;
+ a.denom = 1;
+ }
+
+ if(b.denom < 0) {
+ b.num *= b.denom;
+ b.denom = 1;
+ }
+
+ bignume = mult128 (a.num, b.num);
+ bigdeno = mult128 (a.denom, b.denom);
+ product.num = a.num*b.num;
+ product.denom = a.denom*b.denom;
+
+ /* If it looks to be overflowing, try to reduce the fraction ... */
+ if (bignume.isbig || bigdeno.isbig)
+ {
+ gint64 tmp;
+ a = gnc_numeric_reduce (a);
+ b = gnc_numeric_reduce (b);
+ tmp = a.num;
+ a.num = b.num;
+ b.num = tmp;
+ a = gnc_numeric_reduce (a);
+ b = gnc_numeric_reduce (b);
+
+ bignume = mult128 (a.num, b.num);
+ bigdeno = mult128 (a.denom, b.denom);
+ product.num = a.num*b.num;
+ product.denom = a.denom*b.denom;
+ }
+
+ /* If it its still overflowing, and rounding is allowed then round */
+ if (bignume.isbig || bigdeno.isbig)
+ {
+ /* If rounding allowed, then shift until there's no
+ * more overflow. The conversion at the end will fix
+ * things up for the final value. Else overflow. */
+ if ((how & GNC_NUMERIC_RND_MASK) == GNC_HOW_RND_NEVER)
+ {
+ if (bigdeno.isbig)
+ {
+ return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+ }
+ product = reduce128 (bignume, product.denom);
+ if (gnc_numeric_check (product))
+ {
+ return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+ }
+ }
+ else
+ {
+ while (bignume.isbig || bigdeno.isbig)
+ {
+ bignume = shift128 (bignume);
+ bigdeno = shift128 (bigdeno);
+ }
+ product.num = bignume.lo;
+ if (bignume.isneg) product.num = -product.num;
+
+ product.denom = bigdeno.lo;
+ if (0 == product.denom)
+ {
+ return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+ }
+ }
+ }
+
+#if 0 /* currently, product denom won't ever be zero */
+ if(product.denom < 0) {
+ product.num = -product.num;
+ product.denom = -product.denom;
+ }
+#endif
+
+ result = gnc_numeric_convert(product, denom, how);
+ return result;
+}
+
+
+/********************************************************************
+ * gnc_numeric_div
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_div(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how)
+{
+ gnc_numeric quotient;
+ qofint128 nume, deno;
+
+ if(gnc_numeric_check(a) || gnc_numeric_check(b))
+ {
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+
+ if((denom == GNC_DENOM_AUTO) &&
+ (how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_FIXED)
+ {
+ if(a.denom == b.denom)
+ {
+ denom = a.denom;
+ }
+ else if(a.denom == 0)
+ {
+ denom = b.denom;
+ }
+ else
+ {
+ return gnc_numeric_error(GNC_ERROR_DENOM_DIFF);
+ }
+ }
+
+
+ if(a.denom < 0)
+ {
+ a.num *= a.denom;
+ a.denom = 1;
+ }
+
+ if(b.denom < 0)
+ {
+ b.num *= b.denom;
+ b.denom = 1;
+ }
+
+ if(a.denom == b.denom)
+ {
+ quotient.num = a.num;
+ quotient.denom = b.num;
+ }
+ else
+ {
+ gint64 sgn = 1;
+ if (0 > a.num)
+ {
+ sgn = -sgn;
+ a.num = -a.num;
+ }
+ if (0 > b.num)
+ {
+ sgn = -sgn;
+ b.num = -b.num;
+ }
+ nume = mult128(a.num, b.denom);
+ deno = mult128(b.num, a.denom);
+
+ /* Try to avoid overflow by removing common factors */
+ if (nume.isbig && deno.isbig)
+ {
+ gnc_numeric ra = gnc_numeric_reduce (a);
+ gnc_numeric rb = gnc_numeric_reduce (b);
+
+ gint64 gcf_nume = gcf64(ra.num, rb.num);
+ gint64 gcf_deno = gcf64(rb.denom, ra.denom);
+ nume = mult128(ra.num/gcf_nume, rb.denom/gcf_deno);
+ deno = mult128(rb.num/gcf_nume, ra.denom/gcf_deno);
+ }
+
+ if ((0 == nume.isbig) && (0 == deno.isbig))
+ {
+ quotient.num = sgn * nume.lo;
+ quotient.denom = deno.lo;
+ goto dive_done;
+ }
+ else if (0 == deno.isbig)
+ {
+ quotient = reduce128 (nume, deno.lo);
+ if (0 == gnc_numeric_check (quotient))
+ {
+ quotient.num *= sgn;
+ goto dive_done;
+ }
+ }
+
+ /* If rounding allowed, then shift until there's no
+ * more overflow. The conversion at the end will fix
+ * things up for the final value. */
+ if ((how & GNC_NUMERIC_RND_MASK) == GNC_HOW_RND_NEVER)
+ {
+ return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+ }
+ while (nume.isbig || deno.isbig)
+ {
+ nume = shift128 (nume);
+ deno = shift128 (deno);
+ }
+ quotient.num = sgn * nume.lo;
+ quotient.denom = deno.lo;
+ if (0 == quotient.denom)
+ {
+ return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+ }
+ }
+
+ if(quotient.denom < 0)
+ {
+ quotient.num = -quotient.num;
+ quotient.denom = -quotient.denom;
+ }
+
+dive_done:
+ if((denom == GNC_DENOM_AUTO) &&
+ ((how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_LCD))
+ {
+ denom = gnc_numeric_lcd(a, b);
+ how = how & GNC_NUMERIC_RND_MASK;
+ }
+
+ return gnc_numeric_convert(quotient, denom, how);
+}
+
+/********************************************************************
+ * gnc_numeric_neg
+ * negate the argument
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_neg(gnc_numeric a) {
+ if(gnc_numeric_check(a)) {
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+ return gnc_numeric_create(- a.num, a.denom);
+}
+
+/********************************************************************
+ * gnc_numeric_neg
+ * return the absolute value of the argument
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_abs(gnc_numeric a)
+{
+ if(gnc_numeric_check(a)) {
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+ return gnc_numeric_create(ABS(a.num), a.denom);
+}
+
+/********************************************************************
+ * gnc_numeric_convert
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_convert(gnc_numeric in, gint64 denom, gint how)
+{
+ gnc_numeric out;
+ gnc_numeric temp;
+ gint64 temp_bc;
+ gint64 temp_a;
+ gint64 remainder;
+ gint64 sign;
+ gint denom_neg=0;
+ double ratio, logratio;
+ double sigfigs;
+ qofint128 nume, newm;
+
+ temp.num = 0;
+ temp.denom = 0;
+
+ if(gnc_numeric_check(in)) {
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+
+ if(denom == GNC_DENOM_AUTO)
+ {
+ switch(how & GNC_NUMERIC_DENOM_MASK)
+ {
+ default:
+ case GNC_HOW_DENOM_LCD: /* LCD is meaningless with AUTO in here */
+ case GNC_HOW_DENOM_EXACT:
+ return in;
+ break;
+
+ case GNC_HOW_DENOM_REDUCE:
+ /* reduce the input to a relatively-prime fraction */
+ return gnc_numeric_reduce(in);
+ break;
+
+ case GNC_HOW_DENOM_FIXED:
+ if(in.denom != denom) {
+ return gnc_numeric_error(GNC_ERROR_DENOM_DIFF);
+ }
+ else {
+ return in;
+ }
+ break;
+
+ case GNC_HOW_DENOM_SIGFIG:
+ ratio = fabs(gnc_numeric_to_double(in));
+ if(ratio < 10e-20) {
+ logratio = 0;
+ }
+ else {
+ logratio = log10(ratio);
+ logratio = ((logratio > 0.0) ?
+ (floor(logratio)+1.0) : (ceil(logratio)));
+ }
+ sigfigs = GNC_HOW_GET_SIGFIGS(how);
+
+ if(sigfigs-logratio >= 0) {
+ denom = (gint64)(pow(10, sigfigs-logratio));
+ }
+ else {
+ denom = -((gint64)(pow(10, logratio-sigfigs)));
+ }
+
+ how = how & ~GNC_HOW_DENOM_SIGFIG & ~GNC_NUMERIC_SIGFIGS_MASK;
+ break;
+
+ }
+ }
+
+ /* Make sure we need to do the work */
+ if(in.denom == denom) {
+ return in;
+ }
+
+ /* If the denominator of the input value is negative, get rid of that. */
+ if(in.denom < 0) {
+ in.num = in.num * (- in.denom);
+ in.denom = 1;
+ }
+
+ sign = (in.num < 0) ? -1 : 1;
+
+ /* If the denominator is less than zero, we are to interpret it as
+ * the reciprocal of its magnitude. */
+ if(denom < 0)
+ {
+
+ /* XXX FIXME: use 128-bit math here ... */
+ denom = - denom;
+ denom_neg = 1;
+ temp_a = (in.num < 0) ? -in.num : in.num;
+ temp_bc = in.denom * denom;
+ remainder = in.num % temp_bc;
+ out.num = in.num / temp_bc;
+ out.denom = - denom;
+ }
+ else
+ {
+ /* Do all the modulo and int division on positive values to make
+ * things a little clearer. Reduce the fraction denom/in.denom to
+ * help with range errors */
+ temp.num = denom;
+ temp.denom = in.denom;
+ temp = gnc_numeric_reduce(temp);
+
+ /* Symbolically, do the following:
+ * out.num = in.num * temp.num;
+ * remainder = out.num % temp.denom;
+ * out.num = out.num / temp.denom;
+ * out.denom = denom;
+ */
+ nume = mult128 (in.num, temp.num);
+ newm = div128 (nume, temp.denom);
+ remainder = rem128 (nume, temp.denom);
+
+ if (newm.isbig)
+ {
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
+
+ out.num = newm.lo;
+ out.denom = denom;
+ }
+
+ if (remainder)
+ {
+ switch(how & GNC_NUMERIC_RND_MASK)
+ {
+ case GNC_HOW_RND_FLOOR:
+ if(sign < 0) {
+ out.num = out.num + 1;
+ }
+ break;
+
+ case GNC_HOW_RND_CEIL:
+ if(sign > 0) {
+ out.num = out.num + 1;
+ }
+ break;
+
+ case GNC_HOW_RND_TRUNC:
+ break;
+
+ case GNC_HOW_RND_PROMOTE:
+ out.num = out.num + 1;
+ break;
+
+ case GNC_HOW_RND_ROUND_HALF_DOWN:
+ if(denom_neg)
+ {
+ if((2 * remainder) > in.denom*denom)
+ {
+ out.num = out.num + 1;
+ }
+ }
+ else if((2 * remainder) > temp.denom)
+ {
+ out.num = out.num + 1;
+ }
+ /* check that 2*remainder didn't over-flow */
+ else if (((2 * remainder) < remainder) &&
+ (remainder > (temp.denom / 2)))
+ {
+ out.num = out.num + 1;
+ }
+ break;
+
+ case GNC_HOW_RND_ROUND_HALF_UP:
+ if(denom_neg)
+ {
+ if((2 * remainder) >= in.denom*denom)
+ {
+ out.num = out.num + 1;
+ }
+ }
+ else if((2 * remainder ) >= temp.denom)
+ {
+ out.num = out.num + 1;
+ }
+ /* check that 2*remainder didn't over-flow */
+ else if (((2 * remainder) < remainder) &&
+ (remainder >= (temp.denom / 2)))
+ {
+ out.num = out.num + 1;
+ }
+ break;
+
+ case GNC_HOW_RND_ROUND:
+ if(denom_neg)
+ {
+ if((2 * remainder) > in.denom*denom)
+ {
+ out.num = out.num + 1;
+ }
+ else if((2 * remainder) == in.denom*denom)
+ {
+ if(out.num % 2)
+ {
+ out.num = out.num + 1;
+ }
+ }
+ }
+ else
+ {
+ if((2 * remainder ) > temp.denom)
+ {
+ out.num = out.num + 1;
+ }
+ /* check that 2*remainder didn't over-flow */
+ else if (((2 * remainder) < remainder) &&
+ (remainder > (temp.denom / 2)))
+ {
+ out.num = out.num + 1;
+ }
+ else if((2 * remainder) == temp.denom)
+ {
+ if(out.num % 2)
+ {
+ out.num = out.num + 1;
+ }
+ }
+ /* check that 2*remainder didn't over-flow */
+ else if (((2 * remainder) < remainder) &&
+ (remainder == (temp.denom / 2)))
+ {
+ if(out.num % 2)
+ {
+ out.num = out.num + 1;
+ }
+ }
+ }
+ break;
+
+ case GNC_HOW_RND_NEVER:
+ return gnc_numeric_error(GNC_ERROR_REMAINDER);
+ break;
+ }
+ }
+
+ out.num = (sign > 0) ? out.num : (-out.num);
+
+ return out;
+}
+
+
+/********************************************************************
+ ** reduce a fraction by GCF elimination. This is NOT done as a
+ * part of the arithmetic API unless GNC_HOW_DENOM_REDUCE is specified
+ * as the output denominator.
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_reduce(gnc_numeric in)
+{
+ gint64 t;
+ gint64 num = (in.num < 0) ? (- in.num) : in.num ;
+ gint64 denom = in.denom;
+ gnc_numeric out;
+
+ if(gnc_numeric_check(in))
+ {
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+
+ /* The strategy is to use Euclid's algorithm */
+ while (denom > 0) {
+ t = num % denom;
+ num = denom;
+ denom = t;
+ }
+ /* num now holds the GCD (Greatest Common Divisor) */
+
+ /* All calculations are done on positive num, since it's not
+ * well defined what % does for negative values */
+ out.num = in.num / num;
+ out.denom = in.denom / num;
+ return out;
+}
+
+/********************************************************************
+ * double_to_gnc_numeric
+ ********************************************************************/
+
+gnc_numeric
+double_to_gnc_numeric(double in, gint64 denom, gint how)
+{
+ gnc_numeric out;
+ gint64 int_part=0;
+ double frac_part;
+ gint64 frac_int=0;
+ double logval;
+ double sigfigs;
+
+ if((denom == GNC_DENOM_AUTO) && (how & GNC_HOW_DENOM_SIGFIG))
+ {
+ if(fabs(in) < 10e-20) {
+ logval = 0;
+ }
+ else {
+ logval = log10(fabs(in));
+ logval = ((logval > 0.0) ?
+ (floor(logval)+1.0) : (ceil(logval)));
+ }
+ sigfigs = GNC_HOW_GET_SIGFIGS(how);
+ if(sigfigs-logval >= 0) {
+ denom = (gint64)(pow(10, sigfigs-logval));
+ }
+ else {
+ denom = -((gint64)(pow(10, logval-sigfigs)));
+ }
+
+ how = how & ~GNC_HOW_DENOM_SIGFIG & ~GNC_NUMERIC_SIGFIGS_MASK;
+ }
+
+ int_part = (gint64)(floor(fabs(in)));
+ frac_part = in - (double)int_part;
+
+ int_part = int_part * denom;
+ frac_part = frac_part * (double)denom;
+
+ switch(how & GNC_NUMERIC_RND_MASK) {
+ case GNC_HOW_RND_FLOOR:
+ frac_int = (gint64)floor(frac_part);
+ break;
+
+ case GNC_HOW_RND_CEIL:
+ frac_int = (gint64)ceil(frac_part);
+ break;
+
+ case GNC_HOW_RND_TRUNC:
+ frac_int = (gint64)frac_part;
+ break;
+
+ case GNC_HOW_RND_ROUND:
+ case GNC_HOW_RND_ROUND_HALF_UP:
+ frac_int = (gint64)rint(frac_part);
+ break;
+
+ case GNC_HOW_RND_NEVER:
+ frac_int = (gint64)floor(frac_part);
+ if(frac_part != (double) frac_int) {
+ /* signal an error */
+ }
+ break;
+ }
+
+ out.num = int_part + frac_int;
+ out.denom = denom;
+ return out;
+}
+
+/********************************************************************
+ * gnc_numeric_to_double
+ ********************************************************************/
+
+double
+gnc_numeric_to_double(gnc_numeric in)
+{
+ if(in.denom > 0)
+ {
+ return (double)in.num/(double)in.denom;
+ }
+ else
+ {
+ return (double)(in.num * in.denom);
+ }
+}
+
+/********************************************************************
+ * gnc_numeric_error
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_error(GNCNumericErrorCode error_code)
+{
+ return gnc_numeric_create(error_code, 0LL);
+}
+
+
+/********************************************************************
+ * gnc_numeric_add_with_error
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_add_with_error(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how,
+ gnc_numeric * error)
+{
+
+ gnc_numeric sum = gnc_numeric_add(a, b, denom, how);
+ gnc_numeric exact = gnc_numeric_add(a, b, GNC_DENOM_AUTO,
+ GNC_HOW_DENOM_REDUCE);
+ gnc_numeric err = gnc_numeric_sub(sum, exact, GNC_DENOM_AUTO,
+ GNC_HOW_DENOM_REDUCE);
+
+ if(error) {
+ *error = err;
+ }
+ return sum;
+}
+
+/********************************************************************
+ * gnc_numeric_sub_with_error
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_sub_with_error(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how,
+ gnc_numeric * error)
+{
+ gnc_numeric diff = gnc_numeric_sub(a, b, denom, how);
+ gnc_numeric exact = gnc_numeric_sub(a, b, GNC_DENOM_AUTO,
+ GNC_HOW_DENOM_REDUCE);
+ gnc_numeric err = gnc_numeric_sub(diff, exact, GNC_DENOM_AUTO,
+ GNC_HOW_DENOM_REDUCE);
+ if(error) {
+ *error = err;
+ }
+ return diff;
+}
+
+
+/********************************************************************
+ * gnc_numeric_mul_with_error
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_mul_with_error(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how,
+ gnc_numeric * error)
+{
+ gnc_numeric prod = gnc_numeric_mul(a, b, denom, how);
+ gnc_numeric exact = gnc_numeric_mul(a, b, GNC_DENOM_AUTO,
+ GNC_HOW_DENOM_REDUCE);
+ gnc_numeric err = gnc_numeric_sub(prod, exact, GNC_DENOM_AUTO,
+ GNC_HOW_DENOM_REDUCE);
+ if(error) {
+ *error = err;
+ }
+ return prod;
+}
+
+
+/********************************************************************
+ * gnc_numeric_div_with_error
+ ********************************************************************/
+
+gnc_numeric
+gnc_numeric_div_with_error(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how,
+ gnc_numeric * error)
+{
+ gnc_numeric quot = gnc_numeric_div(a, b, denom, how);
+ gnc_numeric exact = gnc_numeric_div(a, b, GNC_DENOM_AUTO,
+ GNC_HOW_DENOM_REDUCE);
+ gnc_numeric err = gnc_numeric_sub(quot, exact,
+ GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
+ if(error) {
+ *error = err;
+ }
+ return quot;
+}
+
+/********************************************************************
+ * gnc_numeric text IO
+ ********************************************************************/
+
+gchar *
+gnc_numeric_to_string(gnc_numeric n)
+{
+ gchar *result;
+ gint64 tmpnum = n.num;
+ gint64 tmpdenom = n.denom;
+
+ result = g_strdup_printf("%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, tmpnum, tmpdenom);
+
+ return result;
+}
+
+gchar *
+gnc_num_dbg_to_string(gnc_numeric n)
+{
+ static char buff[1000];
+ static char *p = buff;
+ gint64 tmpnum = n.num;
+ gint64 tmpdenom = n.denom;
+
+ p+= 100;
+ if (p-buff >= 1000) p = buff;
+
+ sprintf(p, "%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, tmpnum, tmpdenom);
+
+ return p;
+}
+
+gboolean
+string_to_gnc_numeric(const gchar* str, gnc_numeric *n)
+{
+ size_t num_read;
+ gint64 tmpnum;
+ gint64 tmpdenom;
+
+ if(!str) return FALSE;
+
+#ifdef GNC_DEPRECATED
+ /* must use "<" here because %n's effects aren't well defined */
+ if(sscanf(str, " " GNC_SCANF_LLD "/" GNC_SCANF_LLD "%n",
+ &tmpnum, &tmpdenom, &num_read) < 2) {
+ return FALSE;
+ }
+#else
+ tmpnum = strtoll (str, NULL, 0);
+ str = strchr (str, '/');
+ if (!str) return FALSE;
+ str ++;
+ tmpdenom = strtoll (str, NULL, 0);
+ num_read = strspn (str, "0123456789");
+#endif
+ n->num = tmpnum;
+ n->denom = tmpdenom;
+ return TRUE;
+}
+
+/********************************************************************
+ * gnc_numeric misc testing
+ ********************************************************************/
+#ifdef _GNC_NUMERIC_TEST
+
+static char *
+gnc_numeric_print(gnc_numeric in)
+{
+ char * retval;
+ if(gnc_numeric_check(in)) {
+ retval = g_strdup_printf("<ERROR> [%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT "]",
+ in.num,
+ in.denom);
+ }
+ else {
+ retval = g_strdup_printf("[%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT "]",
+ in.num,
+ in.denom);
+ }
+ return retval;
+}
+
+int
+main(int argc, char ** argv)
+{
+ gnc_numeric a = gnc_numeric_create(1, 3);
+ gnc_numeric b = gnc_numeric_create(1, 4);
+ gnc_numeric c;
+
+ gnc_numeric err;
+
+ c = gnc_numeric_add_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
+ printf("add 100ths/error : %s + %s = %s + (error) %s\n\n",
+ gnc_numeric_print(a), gnc_numeric_print(b),
+ gnc_numeric_print(c),
+ gnc_numeric_print(err));
+
+ c = gnc_numeric_sub_with_error(a, b, 100, GNC_HOW_RND_FLOOR, &err);
+ printf("sub 100ths/error : %s - %s = %s + (error) %s\n\n",
+ gnc_numeric_print(a), gnc_numeric_print(b),
+ gnc_numeric_print(c),
+ gnc_numeric_print(err));
+
+ c = gnc_numeric_mul_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
+ printf("mul 100ths/error : %s * %s = %s + (error) %s\n\n",
+ gnc_numeric_print(a), gnc_numeric_print(b),
+ gnc_numeric_print(c),
+ gnc_numeric_print(err));
+
+ c = gnc_numeric_div_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
+ printf("div 100ths/error : %s / %s = %s + (error) %s\n\n",
+ gnc_numeric_print(a), gnc_numeric_print(b),
+ gnc_numeric_print(c),
+ gnc_numeric_print(err));
+
+ printf("multiply (EXACT): %s * %s = %s\n",
+ gnc_numeric_print(a), gnc_numeric_print(b),
+ gnc_numeric_print(gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT)));
+
+ printf("multiply (REDUCE): %s * %s = %s\n",
+ gnc_numeric_print(a), gnc_numeric_print(b),
+ gnc_numeric_print(gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE)));
+
+
+ return 0;
+}
+#endif
+
+/* ======================== END OF FILE =================== */
Added: gnucash/trunk/lib/libqof/qof/gnc-numeric.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-numeric.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-numeric.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,515 @@
+/********************************************************************
+ * gnc-numeric.h - A rational number library *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ *******************************************************************/
+
+/** @addtogroup Numeric
+
+ The 'Numeric' functions provide a way of working with rational
+ numbers while maintaining strict control over rounding errors
+ when adding rationals with different denominators. The Numeric
+ class is primarily used for working with monetary amounts,
+ where the denominator typically represents the smallest fraction
+ of the currency (e.g. pennies, centimes). The numeric class
+ can handle any fraction (e.g. twelfth's) and is not limited
+ to fractions that are powers of ten.
+
+ A 'Numeric' value represents a number in rational form, with a
+ 64-bit integer as numerator and denominator. Rationals are
+ ideal for many uses, such as performing exact, roundoff-error-free
+ addition and multiplication, but 64-bit rationals do not have
+ the dynamic range of floating point numbers.
+
+EXAMPLE\n
+-------\n
+The following program finds the best ::gnc_numeric approximation to
+the \a math.h constant \a M_PI given a maximum denominator. For
+large denominators, the ::gnc_numeric approximation is accurate to
+more decimal places than will generally be needed, but in some cases
+this may not be good enough. For example,
+
+ at verbatim
+ M_PI = 3.14159265358979323846
+ 245850922 / 78256779 = 3.14159265358979311599 (16 sig figs)
+ 3126535 / 995207 = 3.14159265358865047446 (12 sig figs)
+ 355 / 113 = 3.14159292035398252096 (7 sig figs)
+ at endverbatim
+
+ at verbatim
+#include <glib.h>
+#include "gnc-numeric.h"
+#include <math.h>
+
+int
+main(int argc, char ** argv)
+{
+ gnc_numeric approx, best;
+ double err, best_err=1.0;
+ double m_pi = M_PI;
+ gint64 denom;
+ gint64 max;
+
+ sscanf(argv[1], "%Ld", &max);
+
+ for (denom = 1; denom < max; denom++)
+ {
+ approx = double_to_gnc_numeric (m_pi, denom, GNC_RND_ROUND);
+ err = m_pi - gnc_numeric_to_double (approx);
+ if (fabs (err) < fabs (best_err))
+ {
+ best = approx;
+ best_err = err;
+ printf ("%Ld / %Ld = %.30f\n", gnc_numeric_num (best),
+ gnc_numeric_denom (best), gnc_numeric_to_double (best));
+ }
+ }
+}
+ at endverbatim
+
+@{ */
+/** @file gnc-numeric.h
+ @brief An exact-rational-number library for gnucash.
+ @author Copyright (C) 2000 Bill Gribble
+ @author Copyright (C) 2004 Linas Vepstas <linas at linas.org>
+*/
+
+
+#ifndef GNC_NUMERIC_H
+#define GNC_NUMERIC_H
+
+#include <glib.h>
+
+struct _gnc_numeric
+{
+ gint64 num;
+ gint64 denom;
+};
+
+/** @brief An rational-number type
+ *
+ * This is a rational number, defined by numerator and denominator. */
+typedef struct _gnc_numeric gnc_numeric;
+
+/** @name Arguments Standard Arguments to most functions
+
+ Most of the gnc_numeric arithmetic functions take two arguments
+ in addition to their numeric args: 'denom', which is the denominator
+ to use in the output gnc_numeric object, and 'how'. which
+ describes how the arithmetic result is to be converted to that
+ denominator. This combination of output denominator and rounding policy
+ allows the results of financial and other rational computations to be
+ properly rounded to the appropriate units.
+
+ Valid values for denom are:
+ GNC_DENOM_AUTO -- compute denominator exactly
+ integer n -- Force the denominator of teh result to be this integer
+ GNC_DENOM_RECIPROCAL -- Use 1/n as the denominator (???huh???)
+
+ Valid values for 'how' are bitwise combinations of zero or one
+ "rounding instructions" with zero or one "denominator types".
+ Valid rounding instructions are:
+ GNC_HOW_RND_FLOOR
+ GNC_HOW_RND_CEIL
+ GNC_HOW_RND_TRUNC
+ GNC_HOW_RND_PROMOTE
+ GNC_HOW_RND_ROUND_HALF_DOWN
+ GNC_HOW_RND_ROUND_HALF_UP
+ GNC_HOW_RND_ROUND
+ GNC_HOW_RND_NEVER
+
+ The denominator type specifies how to compute a denominator if
+ GNC_DENOM_AUTO is specified as the 'denom'. Valid
+ denominator types are:
+ GNC_HOW_DENOM_EXACT
+ GNC_HOW_DENOM_REDUCE
+ GNC_HOW_DENOM_LCD
+ GNC_HOW_DENOM_FIXED
+ GNC_HOW_DENOM_SIGFIGS(N)
+
+ To use traditional rational-number operational semantics (all results
+ are exact and are reduced to relatively-prime fractions) pass the
+ argument GNC_DENOM_AUTO as 'denom' and
+ GNC_HOW_DENOM_REDUCE| GNC_HOW_RND_NEVER as 'how'.
+
+ To enforce strict financial semantics (such that all operands must have
+ the same denominator as each other and as the result), use
+ GNC_DENOM_AUTO as 'denom' and
+ GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER as 'how'.
+@{
+*/
+
+/** \brief bitmasks for HOW flags.
+
+ * bits 8-15 of 'how' are reserved for the number of significant
+ * digits to use in the output with GNC_HOW_DENOM_SIGFIG
+ */
+#define GNC_NUMERIC_RND_MASK 0x0000000f
+#define GNC_NUMERIC_DENOM_MASK 0x000000f0
+#define GNC_NUMERIC_SIGFIGS_MASK 0x0000ff00
+
+/** \brief Rounding/Truncation modes for operations.
+
+ * Rounding instructions control how fractional parts in the specified
+ * denominator affect the result. For example, if a computed result is
+ * "3/4" but the specified denominator for the return value is 2, should
+ * the return value be "1/2" or "2/2"?
+ *
+ * Possible rounding instructions are:
+ */
+enum {
+ /** Round toward -infinity */
+ GNC_HOW_RND_FLOOR = 0x01,
+
+ /** Round toward +infinity */
+ GNC_HOW_RND_CEIL = 0x02,
+
+ /** Truncate fractions (round toward zero) */
+ GNC_HOW_RND_TRUNC = 0x03,
+
+ /** Promote fractions (round away from zero) */
+ GNC_HOW_RND_PROMOTE = 0x04,
+
+ /** Round to the nearest integer, rounding toward zero
+ * when there are two equidistant nearest integers.
+ */
+ GNC_HOW_RND_ROUND_HALF_DOWN = 0x05,
+
+ /** Round to the nearest integer, rounding away from zero
+ * when there are two equidistant nearest integers.
+ */
+ GNC_HOW_RND_ROUND_HALF_UP = 0x06,
+
+ /** Use unbiased ("banker's") rounding. This rounds to the
+ * nearest integer, and to the nearest even integer when there
+ * are two equidistant nearest integers. This is generally the
+ * one you should use for financial quantities.
+ */
+ GNC_HOW_RND_ROUND = 0x07,
+
+ /** Never round at all, and signal an error if there is a
+ * fractional result in a computation.
+ */
+ GNC_HOW_RND_NEVER = 0x08
+};
+
+/** How to compute a denominator, or'ed into the "how" field. */
+enum {
+ /** Use any denominator which gives an exactly correct ratio of
+ * numerator to denominator. Use EXACT when you do not wish to
+ * lose any information in the result but also do not want to
+ * spend any time finding the "best" denominator.
+ */
+ GNC_HOW_DENOM_EXACT = 0x10,
+
+ /** Reduce the result value by common factor elimination,
+ * using the smallest possible value for the denominator that
+ * keeps the correct ratio. The numerator and denominator of
+ * the result are relatively prime.
+ */
+ GNC_HOW_DENOM_REDUCE = 0x20,
+
+ /** Find the least common multiple of the arguments' denominators
+ * and use that as the denominator of the result.
+ */
+ GNC_HOW_DENOM_LCD = 0x30,
+
+ /** All arguments are required to have the same denominator,
+ * that denominator is to be used in the output, and an error
+ * is to be signaled if any argument has a different denominator.
+ */
+ GNC_HOW_DENOM_FIXED = 0x40,
+
+ /** Round to the number of significant figures given in the rounding
+ * instructions by the GNC_HOW_DENOM_SIGFIGS () macro.
+ */
+ GNC_HOW_DENOM_SIGFIG = 0x50
+};
+
+/** Build a 'how' value that will generate a denominator that will
+ * keep at least n significant figures in the result.
+ */
+#define GNC_HOW_DENOM_SIGFIGS( n ) ( ((( n ) & 0xff) << 8) | GNC_HOW_DENOM_SIGFIG)
+#define GNC_HOW_GET_SIGFIGS( a ) ( (( a ) & 0xff00 ) >> 8)
+
+/** Error codes */
+typedef enum {
+ GNC_ERROR_OK = 0, /**< No error */
+ GNC_ERROR_ARG = -1, /**< Argument is not a valid number */
+ GNC_ERROR_OVERFLOW = -2, /**< Intermediate result overflow */
+
+ /** GNC_HOW_DENOM_FIXED was specified, but argument denominators differed. */
+ GNC_ERROR_DENOM_DIFF = -3,
+
+ /** GNC_HOW_RND_NEVER was specified, but the result could not be
+ * converted to the desired denominator without a remainder. */
+ GNC_ERROR_REMAINDER = -4
+} GNCNumericErrorCode;
+
+
+/** Values that can be passed as the 'denom' argument.
+ * The include a positive number n to be used as the
+ * denominator of teh output value. Other possibilities
+ * include the list below:
+ */
+
+/** Compute an appropriate denominator automatically. Flags in
+ * the 'how' argument will specify how to compute the denominator.
+ */
+#define GNC_DENOM_AUTO 0
+
+/** Use the value 1/n as the denominator of the output value. */
+#define GNC_DENOM_RECIPROCAL( a ) (- ( a ))
+
+/** @} */
+
+/** @name Constructors
+@{
+*/
+/** Make a gnc_numeric from numerator and denominator */
+static inline
+gnc_numeric gnc_numeric_create(gint64 num, gint64 denom) {
+ gnc_numeric out;
+ out.num = num;
+ out.denom = denom;
+ return out;
+}
+
+/** create a zero-value gnc_numeric */
+static inline
+gnc_numeric gnc_numeric_zero(void) { return gnc_numeric_create(0, 1); }
+
+/** Convert a floating-point number to a gnc_numeric.
+ * Both 'denom' and 'how' are used as in arithmetic,
+ * but GNC_DENOM_AUTO is not recognized; a denominator
+ * must be specified either explicitctly or through sigfigs.
+ */
+gnc_numeric double_to_gnc_numeric(double in, gint64 denom,
+ gint how);
+
+/** Read a gnc_numeric from str, skipping any leading whitespace.
+ * Return TRUE on success and store the resulting value in "n".
+ * Return NULL on error. */
+gboolean string_to_gnc_numeric(const gchar* str, gnc_numeric *n);
+
+/** Create a gnc_numeric object that signals the error condition
+ * noted by error_code, rather than a number.
+ */
+gnc_numeric gnc_numeric_error(GNCNumericErrorCode error_code);
+/** @} */
+
+/** @name Value Accessors
+ @{
+*/
+/** Return numerator */
+static inline
+gint64 gnc_numeric_num(gnc_numeric a) { return a.num; }
+/** Return denominator */
+static inline
+gint64 gnc_numeric_denom(gnc_numeric a) { return a.denom; }
+
+/** Convert numeric to floating-point value. */
+double gnc_numeric_to_double(gnc_numeric in);
+
+/** Convert to string. The returned buffer is to be g_free'd by the
+ * caller (it was allocated through g_strdup) */
+gchar *gnc_numeric_to_string(gnc_numeric n);
+
+/** Convert to string. Uses a static, non-thread-safe buffer.
+ * For internal use only. */
+gchar * gnc_num_dbg_to_string(gnc_numeric n);
+/** @}*/
+
+/** @name Comparisons and Predicates
+ @{
+*/
+/** Check for error signal in value. Returns GNC_ERROR_OK (==0) if
+ * the number appears to be valid, otherwise it returns the
+ * type of error. Error values always have a denominator of zero.
+ */
+GNCNumericErrorCode gnc_numeric_check(gnc_numeric a);
+
+/** Returns 1 if a>b, -1 if b>a, 0 if a == b */
+int gnc_numeric_compare(gnc_numeric a, gnc_numeric b);
+
+/** Returns 1 if the given gnc_numeric is 0 (zero), else returns 0. */
+gboolean gnc_numeric_zero_p(gnc_numeric a);
+
+/** Returns 1 if a < 0, otherwise returns 0. */
+gboolean gnc_numeric_negative_p(gnc_numeric a);
+
+/** Returns 1 if a > 0, otherwise returns 0. */
+gboolean gnc_numeric_positive_p(gnc_numeric a);
+
+/** Equivalence predicate: Returns TRUE (1) if a and b are
+ * exactly the same (have the same numerator and denominator)
+ */
+gboolean gnc_numeric_eq(gnc_numeric a, gnc_numeric b);
+
+/** Equivalence predicate: Returns TRUE (1) if a and b represent
+ * the same number. That is, return TRUE if the ratios, when
+ * reduced by eliminating common factors, are identical.
+ */
+gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b);
+
+/** Equivalence predicate:
+ * Convert both a and b to denom using the
+ * specified DENOM and method HOW, and compare numerators
+ * the results using gnc_numeric_equal.
+ *
+ For example, if a == 7/16 and b == 3/4,
+ gnc_numeric_same(a, b, 2, GNC_HOW_RND_TRUNC) == 1
+ because both 7/16 and 3/4 round to 1/2 under truncation. However,
+ gnc_numeric_same(a, b, 2, GNC_HOW_RND_ROUND) == 0
+ because 7/16 rounds to 1/2 under unbiased rounding but 3/4 rounds
+ to 2/2.
+ */
+int gnc_numeric_same(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how);
+/** @} */
+
+/** @name Arithmetic Operations
+ @{
+*/
+/** Return a+b. */
+gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how);
+
+/** Return a-b. */
+gnc_numeric gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how);
+
+/** Multiply a times b, returning the product. An overflow
+ * may occur if the result of the multiplication can't
+ * be represented as a ratio of 64-bit int's after removing
+ * common factors.
+ */
+gnc_numeric gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how);
+
+/** Division. Note that division can overflow, in the following
+ * sense: if we write x=a/b and y=c/d then x/y = (a*d)/(b*c)
+ * If, after eliminating all common factors between the numerator
+ * (a*d) and the denominator (b*c), then if either the numerator
+ * and/or the denominator are *still* greater than 2^63, then
+ * the division has overflowed.
+ */
+gnc_numeric gnc_numeric_div(gnc_numeric x, gnc_numeric y,
+ gint64 denom, gint how);
+/** Negate the argument */
+gnc_numeric gnc_numeric_neg(gnc_numeric a);
+
+/** Return the absolute value of the argument */
+gnc_numeric gnc_numeric_abs(gnc_numeric a);
+
+/**
+ * Shortcut for common case: gnc_numeric_add(a, b, GNC_DENOM_AUTO,
+ * GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
+ */
+static inline
+gnc_numeric gnc_numeric_add_fixed(gnc_numeric a, gnc_numeric b) {
+ return gnc_numeric_add(a, b, GNC_DENOM_AUTO,
+ GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
+}
+
+/**
+ * Shortcut for most common case: gnc_numeric_sub(a, b, GNC_DENOM_AUTO,
+ * GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
+ */
+static inline
+gnc_numeric gnc_numeric_sub_fixed(gnc_numeric a, gnc_numeric b) {
+ return gnc_numeric_sub(a, b, GNC_DENOM_AUTO,
+ GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER);
+}
+/** @} */
+
+/** @name Arithmetic Functions with Exact Error Returns
+ @{
+*/
+/** The same as gnc_numeric_add, but uses 'error' for accumulating
+ * conversion roundoff error. */
+gnc_numeric gnc_numeric_add_with_error(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how,
+ gnc_numeric * error);
+
+/** The same as gnc_numeric_sub, but uses error for accumulating
+ * conversion roundoff error. */
+gnc_numeric gnc_numeric_sub_with_error(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how,
+ gnc_numeric * error);
+
+/** The same as gnc_numeric_mul, but uses error for
+ * accumulating conversion roundoff error.
+ */
+gnc_numeric gnc_numeric_mul_with_error(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how,
+ gnc_numeric * error);
+
+/** The same as gnc_numeric_div, but uses error for
+ * accumulating conversion roundoff error.
+ */
+gnc_numeric gnc_numeric_div_with_error(gnc_numeric a, gnc_numeric b,
+ gint64 denom, gint how,
+ gnc_numeric * error);
+/** @} */
+
+/** @name Change Denominator
+ @{
+*/
+/** Change the denominator of a gnc_numeric value to the
+ * specified denominator under standard arguments
+ * 'denom' and 'how'.
+ */
+gnc_numeric gnc_numeric_convert(gnc_numeric in, gint64 denom,
+ gint how);
+
+/** Same as gnc_numeric_convert, but return a remainder
+ * value for accumulating conversion error.
+*/
+gnc_numeric gnc_numeric_convert_with_error(gnc_numeric in, gint64 denom,
+ gint how,
+ gnc_numeric * error);
+
+/** Return input after reducing it by Greated Common Factor (GCF)
+ * elimination */
+gnc_numeric gnc_numeric_reduce(gnc_numeric in);
+/** @} */
+
+/** @name Deprecated, backwards-compatible definitions
+ @{
+*/
+#define GNC_RND_FLOOR GNC_HOW_RND_FLOOR
+#define GNC_RND_CEIL GNC_HOW_RND_CEIL
+#define GNC_RND_TRUNC GNC_HOW_RND_TRUNC
+#define GNC_RND_PROMOTE GNC_HOW_RND_PROMOTE
+#define GNC_RND_ROUND_HALF_DOWN GNC_HOW_RND_ROUND_HALF_DOWN
+#define GNC_RND_ROUND_HALF_UP GNC_HOW_RND_ROUND_HALF_UP
+#define GNC_RND_ROUND GNC_HOW_RND_ROUND
+#define GNC_RND_NEVER GNC_HOW_RND_NEVER
+
+#define GNC_DENOM_EXACT GNC_HOW_DENOM_EXACT
+#define GNC_DENOM_REDUCE GNC_HOW_DENOM_REDUCE
+#define GNC_DENOM_LCD GNC_HOW_DENOM_LCD
+#define GNC_DENOM_FIXED GNC_HOW_DENOM_FIXED
+#define GNC_DENOM_SIGFIG GNC_HOW_DENOM_SIGFIG
+
+#define GNC_DENOM_SIGFIGS(X) GNC_HOW_DENOM_SIGFIGS(X)
+#define GNC_NUMERIC_GET_SIGFIGS(X) GNC_HOW_GET_SIGFIGS(X)
+/** @} */
+/** @} */
+#endif
Added: gnucash/trunk/lib/libqof/qof/gnc-numeric.scm
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-numeric.scm 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-numeric.scm 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,95 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; gnc-numeric.scm : rational number representation for gnucash
+;; Copyright 2000 Bill Gribble <grib at gnumatic.com>
+;; Copyright 2001 Christian Stimming <stimming at tu-harburg.de>
+;;
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2 of
+;; the License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, contact:
+;;
+;; Free Software Foundation Voice: +1-617-542-5942
+;; 59 Temple Place - Suite 330 Fax: +1-617-542-2652
+;; Boston, MA 02111-1307, USA gnu at gnu.org
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; use 'logior' in guile to bit-combine RND and DENOM flags.
+
+(define GNC-RND-FLOOR 1)
+(define GNC-RND-CEIL 2)
+(define GNC-RND-TRUNC 3)
+(define GNC-RND-PROMOTE 4)
+(define GNC-RND-ROUND-HALF-DOWN 5)
+(define GNC-RND-ROUND-HALF-UP 6)
+(define GNC-RND-ROUND 7)
+(define GNC-RND-NEVER 8)
+
+(define GNC-DENOM-AUTO 0)
+(define GNC-DENOM-REDUCE 32)
+(define GNC-DENOM-FIXED 64)
+(define GNC-DENOM-LCD 48)
+(define GNC-DENOM-SIGFIG 80)
+
+(define (GNC-DENOM-SIGFIGS n)
+ (logior GNC-DENOM-SIGFIG (* n 256)))
+
+(define GNC-ERROR-OK 0)
+(define GNC-ERROR-ARG -1)
+(define GNC-ERROR-OVERFLOW -2)
+(define GNC-ERROR-DENOM-DIFF -3)
+(define GNC-ERROR-REMAINDER -4)
+
+(define <gnc-numeric>
+ (make-record-type "<gnc-numeric>"
+ '(num denom)))
+
+(define gnc:make-gnc-numeric
+ (record-constructor <gnc-numeric>))
+
+(define gnc:gnc-numeric?
+ (record-predicate <gnc-numeric>))
+
+(define gnc:gnc-numeric-num
+ (record-accessor <gnc-numeric> 'num))
+
+(define gnc:gnc-numeric-denom
+ (record-accessor <gnc-numeric> 'denom))
+
+(define (gnc:gnc-numeric-denom-reciprocal arg)
+ (- arg))
+
+
+
+(define <gnc-monetary>
+ (make-record-type "<gnc-monetary>"
+ '(commodity amount)))
+
+;; Constructor; takes one <gnc:commodity*> and one <gnc-numeric>
+(define (gnc:make-gnc-monetary c a)
+ (if (and (gw:wcp-is-of-type? <gnc:commodity*> c) (gnc:gnc-numeric? a))
+ ((record-constructor <gnc-monetary>) c a)
+ (warn "wrong arguments for gnc:make-gnc-monetary: " c a)))
+
+(define gnc:gnc-monetary?
+ (record-predicate <gnc-monetary>))
+
+(define gnc:gnc-monetary-commodity
+ (record-accessor <gnc-monetary> 'commodity))
+
+(define gnc:gnc-monetary-amount
+ (record-accessor <gnc-monetary> 'amount))
+
+(define (gnc:monetary-neg a)
+ (if (gnc:gnc-monetary? a)
+ (gnc:make-gnc-monetary
+ (gnc:gnc-monetary-commodity a)
+ (gnc:numeric-neg (gnc:gnc-monetary-amount a)))
+ (warn "wrong arguments for gnc:monetary-neg: " a)))
Added: gnucash/trunk/lib/libqof/qof/gnc-trace.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-trace.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-trace.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,370 @@
+/* *****************************************************************\
+ * gnc-trace.c -- QOF error logging and tracing facility *
+ * Copyright (C) 1997-2003 Linas Vepstas <linas at linas.org> *
+ * Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ * Author: Rob Clark (rclark at cs.hmc.edu) *
+ * Author: Linas Vepstas (linas at linas.org) *
+\********************************************************************/
+
+/** @addtogroup Trace
+@{ */
+
+/** @file gnc-trace.c
+ @brief QOF error logging facility
+ @author Neil Williams <linux at codehelp.co.uk>
+*/
+
+#include "config.h"
+
+#include <glib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#else
+ /* What to do? */
+#endif
+#include <stdarg.h>
+#include <string.h>
+#include <sys/time.h>
+#include "qof.h"
+#include "gnc-trace.h"
+
+static FILE *fout = NULL;
+static gchar* filename = NULL;
+
+static const int MAX_TRACE_FILENAME = 100;
+static GHashTable *log_table = NULL;
+
+AS_STRING_FUNC(gncLogLevel, LOG_LEVEL_LIST) /**< enum_as_string function
+
+uses the enum_as_string macro from QOF
+but the From macro is not required. Lookups
+are done on the string. */
+
+FROM_STRING_FUNC(gncLogLevel, LOG_LEVEL_LIST)
+
+/* Don't be fooled: gnc_trace_num_spaces has external linkage and
+ static storage, but can't be defined with 'extern' because it has
+ an initializer, and can't be declared with 'static' because that
+ would give it internal linkage. */
+gint __attribute__ ((unused)) gnc_trace_num_spaces = 0;
+
+static void
+fh_printer (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ extern gint gnc_trace_num_spaces;
+ FILE *fh = user_data;
+ fprintf (fh, "%*s%s\n", gnc_trace_num_spaces, "", message);
+ fflush(fh);
+}
+
+void
+gnc_log_init (void)
+{
+ if(!fout) //allow gnc_set_logfile
+ {
+ fout = fopen ("/tmp/qof.trace", "w");
+ }
+
+ if(!fout && (filename = (char *)g_malloc(MAX_TRACE_FILENAME))) {
+ snprintf(filename, MAX_TRACE_FILENAME-1, "/tmp/qof.trace.%d",
+ getpid());
+ fout = fopen (filename, "w");
+ g_free(filename);
+ }
+
+ if(!fout)
+ fout = stderr;
+
+ g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MASK, fh_printer, fout);
+}
+
+/* Set the logging level of the given module. */
+void
+gnc_set_log_level(QofLogModule log_module, gncLogLevel level)
+{
+ gchar* level_string;
+
+ if(!log_module || level == 0) { return; }
+ level_string = g_strdup(gncLogLevelasString(level));
+ if(!log_table)
+ {
+ log_table = g_hash_table_new(g_str_hash, g_str_equal);
+ }
+ g_hash_table_insert(log_table, (gpointer)log_module, level_string);
+}
+
+static void
+log_module_foreach(gpointer key, gpointer value, gpointer data)
+{
+ g_hash_table_insert(log_table, key, data);
+}
+
+/* Set the logging level for all known modules. */
+void
+gnc_set_log_level_global(gncLogLevel level)
+{
+ gchar* level_string;
+
+ if(!log_table || level == 0) { return; }
+ level_string = g_strdup(gncLogLevelasString(level));
+ g_hash_table_foreach(log_table, log_module_foreach, level_string);
+}
+
+void
+gnc_set_logfile (FILE *outfile)
+{
+ if(!outfile) { fout = stderr; return; }
+ fout = outfile;
+}
+
+void
+qof_log_init_filename (const gchar* logfilename)
+{
+ if(!logfilename)
+ {
+ fout = stderr;
+ }
+ else
+ {
+ filename = g_strdup(logfilename);
+ fout = fopen(filename, "w");
+ }
+ gnc_log_init();
+}
+
+void
+qof_log_shutdown (void)
+{
+ if(fout && fout != stderr) { fclose(fout); }
+ if(filename) { g_free(filename); }
+ g_hash_table_destroy(log_table);
+}
+
+#define MAX_CHARS 50
+/* gnc_log_prettify() cleans up subroutine names. AIX/xlC has the habit
+ * of printing signatures not names; clean this up. On other operating
+ * systems, truncate name to 30 chars. Note this routine is not thread
+ * safe. Note we wouldn't need this routine if AIX did something more
+ * reasonable. Hope thread safety doesn't poke us in eye. */
+const char *
+gnc_log_prettify (const char *name)
+{
+ static char bf[128];
+ char *p;
+
+ if (!name)
+ return "";
+
+ strncpy (bf, name, MAX_CHARS-1); bf[MAX_CHARS-2] = 0;
+ p = strchr (bf, '(');
+
+ if (p)
+ {
+ *(p+1) = ')';
+ *(p+2) = 0x0;
+ }
+ else
+ strcpy (&bf[MAX_CHARS-4], "...()");
+
+ return bf;
+}
+
+/********************************************************************\
+\********************************************************************/
+
+#define NUM_CLOCKS 10
+
+static
+struct timeval gnc_clock[NUM_CLOCKS] = {
+ {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},
+ {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},
+};
+
+static
+struct timeval gnc_clock_total[NUM_CLOCKS] = {
+ {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},
+ {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},
+};
+
+void
+gnc_start_clock (int clockno, QofLogModule log_module, gncLogLevel log_level,
+ const char *function_name, const char *format, ...)
+{
+ struct timezone tz;
+ va_list ap;
+
+ if ((0>clockno) || (NUM_CLOCKS <= clockno)) return;
+ gettimeofday (&gnc_clock[clockno], &tz);
+
+ if (!fout) gnc_log_init();
+
+ fprintf (fout, "Clock %d Start: %s: ",
+ clockno, gnc_log_prettify (function_name));
+
+ va_start (ap, format);
+
+ vfprintf (fout, format, ap);
+
+ va_end (ap);
+
+ fprintf (fout, "\n");
+ fflush (fout);
+}
+
+void
+gnc_report_clock (int clockno, QofLogModule log_module, gncLogLevel log_level,
+ const char *function_name, const char *format, ...)
+{
+ struct timezone tz;
+ struct timeval now;
+ va_list ap;
+
+ if ((0>clockno) || (NUM_CLOCKS <= clockno)) return;
+ gettimeofday (&now, &tz);
+
+ /* need to borrow to make difference */
+ if (now.tv_usec < gnc_clock[clockno].tv_usec)
+ {
+ now.tv_sec --;
+ now.tv_usec += 1000000;
+ }
+ now.tv_sec -= gnc_clock[clockno].tv_sec;
+ now.tv_usec -= gnc_clock[clockno].tv_usec;
+
+ gnc_clock_total[clockno].tv_sec += now.tv_sec;
+ gnc_clock_total[clockno].tv_usec += now.tv_usec;
+
+ if (!fout) gnc_log_init();
+
+ fprintf (fout, "Clock %d Elapsed: %ld.%06lds %s: ",
+ clockno, (long int) now.tv_sec, (long int) now.tv_usec,
+ gnc_log_prettify (function_name));
+
+ va_start (ap, format);
+
+ vfprintf (fout, format, ap);
+
+ va_end (ap);
+
+ fprintf (fout, "\n");
+ fflush (fout);
+}
+
+void
+gnc_report_clock_total (int clockno,
+ QofLogModule log_module, gncLogLevel log_level,
+ const char *function_name, const char *format, ...)
+{
+ va_list ap;
+
+ if ((0>clockno) || (NUM_CLOCKS <= clockno)) return;
+
+ /* need to normalize usec */
+ while (gnc_clock_total[clockno].tv_usec >= 1000000)
+ {
+ gnc_clock_total[clockno].tv_sec ++;
+ gnc_clock_total[clockno].tv_usec -= 1000000;
+ }
+
+ if (!fout) gnc_log_init();
+
+ fprintf (fout, "Clock %d Total Elapsed: %ld.%06lds %s: ",
+ clockno,
+ (long int) gnc_clock_total[clockno].tv_sec,
+ (long int) gnc_clock_total[clockno].tv_usec,
+ gnc_log_prettify (function_name));
+
+ va_start (ap, format);
+
+ vfprintf (fout, format, ap);
+
+ va_end (ap);
+
+ fprintf (fout, "\n");
+ fflush (fout);
+}
+
+gboolean
+gnc_should_log(QofLogModule log_module, gncLogLevel log_level)
+{
+ gchar* log_string;
+ gncLogLevel maximum; /* Any log_level less than this will be logged. */
+
+ log_string = NULL;
+ if(!log_table || log_module == NULL || log_level == 0) { return FALSE; }
+ log_string = (gchar*)g_hash_table_lookup(log_table, log_module);
+ /* if log_module not found, do not log. */
+ if(!log_string) { return FALSE; }
+ maximum = gncLogLevelfromString(log_string);
+ if(log_level <= maximum) { return TRUE; }
+ return FALSE;
+}
+
+void qof_log_set_default(gncLogLevel log_level)
+{
+ gnc_set_log_level(QOF_MOD_BACKEND, log_level);
+ gnc_set_log_level(QOF_MOD_CLASS, log_level);
+ gnc_set_log_level(QOF_MOD_ENGINE, log_level);
+ gnc_set_log_level(QOF_MOD_OBJECT, log_level);
+ gnc_set_log_level(QOF_MOD_KVP, log_level);
+ gnc_set_log_level(QOF_MOD_MERGE, log_level);
+ gnc_set_log_level(QOF_MOD_QUERY, log_level);
+ gnc_set_log_level(QOF_MOD_SESSION, log_level);
+}
+
+struct hash_s
+{
+ QofLogCB cb;
+ gpointer data;
+};
+
+static void hash_cb (gpointer key, gpointer value, gpointer data)
+{
+ struct hash_s *iter;
+
+ iter = (struct hash_s*)data;
+ if(!iter) { return; }
+ (iter->cb)(key, value, iter->data);
+}
+
+void qof_log_module_foreach(QofLogCB cb, gpointer data)
+{
+ struct hash_s iter;
+
+ if(!cb) { return; }
+ iter.cb = cb;
+ iter.data = data;
+ g_hash_table_foreach(log_table, hash_cb, (gpointer)&iter);
+}
+
+gint qof_log_module_count(void)
+{
+ if(!log_table) { return 0; }
+ return g_hash_table_size(log_table);
+}
+
+/** @} */
+
+/************************* END OF FILE ******************************\
+\********************************************************************/
Added: gnucash/trunk/lib/libqof/qof/gnc-trace.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/gnc-trace.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/gnc-trace.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,270 @@
+/********************************************************************\
+ * gnc-trace.h -- QOF error logging and tracing facility *
+ * Copyright (C) 1998-2003 Linas Vepstas <linas at linas.org> *
+ * Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ * Author: Linas Vepstas (linas at linas.org) *
+\********************************************************************/
+
+/** @addtogroup Trace
+ @{ */
+
+/** @file gnc-trace.h
+ * @brief QOF error logging and tracing facility
+ * @author Neil Williams <linux at codehelp.co.uk>
+ */
+
+#ifndef GNC_TRACE_H
+#define GNC_TRACE_H
+
+#include <glib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include "qof.h"
+#include "gnc-engine-util.h"
+
+#define QOF_MOD_ENGINE "qof-engine"
+
+#define LOG_LEVEL_LIST(_) \
+ _(GNC_LOG_FATAL, = 0) \
+ _(GNC_LOG_ERROR, = 1) \
+ _(GNC_LOG_WARNING, = 2) \
+ _(GNC_LOG_INFO, = 3) \
+ _(GNC_LOG_DEBUG, = 4) \
+ _(GNC_LOG_DETAIL, = 5) \
+ _(GNC_LOG_TRACE, = 6)
+
+DEFINE_ENUM (gncLogLevel, LOG_LEVEL_LIST)
+
+/** Convert gncLogLevel to a string.
+
+The macro correlates the enum value and an
+exact copy as a string, removing the need to
+keep two separate lists in sync.
+*/
+AS_STRING_DEC(gncLogLevel, LOG_LEVEL_LIST)
+
+/** Convert the log_string to a gncLogLevel
+
+Only for use as a partner to ::gncLogLevelasString
+*/
+FROM_STRING_DEC(gncLogLevel, LOG_LEVEL_LIST)
+
+#define GNC_TRACE_INDENT_WIDTH 4
+
+/** Initialize the error logging subsystem
+
+\note Applications should call gnc_set_logfile
+to set the output, otherwise the
+default of \a /tmp/qof.trace will be used.
+
+As an alternative, use qof_log_init_filename
+which sets the filename and initialises the
+logging subsystem in one operation.
+*/
+void gnc_log_init (void);
+
+/** Set the logging level of the given log_module. */
+void gnc_set_log_level(QofLogModule module, gncLogLevel level);
+
+/** Set the logging level for all known log_modules.
+
+\note Unless a log_module has been registered using
+gnc_set_log_level, it will be unaffected by this change.
+
+*/
+void gnc_set_log_level_global(gncLogLevel level);
+
+/** Specify an alternate log output, to pipe or file. By default,
+ * all logging goes to /tmp/qof.trace
+
+ Needs to be called \b before gnc_log_init()
+*/
+void gnc_set_logfile (FILE *outfile);
+
+/** Specify a filename for log output.
+
+Calls gnc_log_init() for you.
+*/
+void qof_log_init_filename (const gchar* logfilename);
+
+/** Be nice, close the logfile is possible. */
+void qof_log_shutdown (void);
+
+/** gnc_log_prettify() cleans up subroutine names. AIX/xlC has the habit
+ * of printing signatures not names; clean this up. On other operating
+ * systems, truncate name to 30 chars. Note this routine is not thread
+ * safe. Note we wouldn't need this routine if AIX did something more
+ * reasonable. Hope thread safety doesn't poke us in eye. */
+const char * gnc_log_prettify (const char *name);
+
+/** Do not log log_modules that have not been enabled.
+
+ Whether to log cannot be decided inline because a hashtable is
+ now used. This is the price of extending logging to non-Gnucash
+ log_modules.
+
+*/
+gboolean gnc_should_log(QofLogModule log_module, gncLogLevel log_level);
+
+/** Set the default QOF log_modules to the log level. */
+void qof_log_set_default(gncLogLevel log_level);
+
+typedef void (*QofLogCB) (QofLogModule log_module, gncLogLevel* log_level, gpointer user_data);
+
+/** Iterate over each known log_module
+
+Only log_modules with log_levels set will
+be available.
+*/
+void qof_log_module_foreach(QofLogCB cb, gpointer data);
+
+/** Number of log_modules registered*/
+gint qof_log_module_count(void);
+
+#define FUNK gnc_log_prettify(__FUNCTION__)
+
+/** Log error/waring/info messages to stderr or to other pipe.
+ * This logging infrastructure is meant for validating the
+ * correctness of the execution of the code. 'Info' level
+ * messages help trace program flow. 'Error' messages are
+ * meant to indicate internal data inconsistencies.
+ *
+ * Messages can be logged to stdout, stderr, or to any desired
+ * FILE * file handle. Use fdopen() to get a file handle from a
+ * file descriptor. Use gnc_set_logfile to set the logging file
+ * handle.
+ */
+
+/** Log an fatal error */
+#define FATAL(format, args...) { \
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, \
+ "Fatal Error: %s(): " format, FUNK , ## args); \
+}
+
+/** Log an serious error */
+#define PERR(format, args...) { \
+ if (gnc_should_log (log_module, GNC_LOG_ERROR)) { \
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, \
+ "Error: %s(): " format, FUNK , ## args); \
+ } \
+}
+
+/** Log an warning */
+#define PWARN(format, args...) { \
+ if (gnc_should_log (log_module, GNC_LOG_WARNING)) { \
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, \
+ "Warning: %s(): " format, FUNK , ## args); \
+ } \
+}
+
+/** Print an informational note */
+#define PINFO(format, args...) { \
+ if (gnc_should_log (log_module, GNC_LOG_INFO)) { \
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, \
+ "Info: %s(): " format, \
+ FUNK , ## args); \
+ } \
+}
+
+/** Print an debugging message */
+#define DEBUG(format, args...) { \
+ if (gnc_should_log (log_module, GNC_LOG_DEBUG)) { \
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
+ "Debug: %s(): " format, \
+ FUNK , ## args); \
+ } \
+}
+
+/** Print an function entry debugging message */
+#define ENTER(format, args...) { \
+ extern gint gnc_trace_num_spaces; \
+ if (gnc_should_log (log_module, GNC_LOG_DEBUG)) { \
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
+ "Enter in %s: %s()" format, __FILE__, \
+ FUNK , ## args); \
+ gnc_trace_num_spaces += GNC_TRACE_INDENT_WIDTH;\
+ } \
+}
+
+/** Print an function exit debugging message */
+#define LEAVE(format, args...) { \
+ extern gint gnc_trace_num_spaces; \
+ if (gnc_should_log (log_module, GNC_LOG_DEBUG)) { \
+ gnc_trace_num_spaces -= GNC_TRACE_INDENT_WIDTH;\
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
+ "Leave: %s()" format, \
+ FUNK , ## args); \
+ } \
+}
+
+/** Print an function trace debugging message */
+#define TRACE(format, args...) { \
+ if (gnc_should_log (log_module, GNC_LOG_TRACE)) { \
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
+ "Trace: %s(): " format, FUNK , ## args); \
+ } \
+}
+
+#define DEBUGCMD(x) { if (gnc_should_log (log_module, GNC_LOG_DEBUG)) { (x); }}
+
+/* -------------------------------------------------------- */
+/** Infrastructure to make timing measurements for critical peices
+ * of code. Used for only for performance tuning & debugging.
+ */
+
+void gnc_start_clock (int clockno, QofLogModule log_module, gncLogLevel log_level,
+ const char *function_name, const char *format, ...);
+
+void gnc_report_clock (int clockno,
+ QofLogModule log_module,
+ gncLogLevel log_level,
+ const char *function_name,
+ const char *format, ...);
+
+void gnc_report_clock_total (int clockno,
+ QofLogModule log_module,
+ gncLogLevel log_level,
+ const char *function_name,
+ const char *format, ...);
+
+/** start a particular timer */
+#define START_CLOCK(clockno,format, args...) { \
+ if (gnc_should_log (log_module, GNC_LOG_INFO)) \
+ gnc_start_clock (clockno, log_module, GNC_LOG_INFO, \
+ __FUNCTION__, format , ## args); \
+}
+
+/** report elapsed time since last report on a particular timer */
+#define REPORT_CLOCK(clockno,format, args...) { \
+ if (gnc_should_log (log_module, GNC_LOG_INFO)) \
+ gnc_report_clock (clockno, log_module, GNC_LOG_INFO, \
+ __FUNCTION__, format , ## args); \
+}
+
+/** report total elapsed time since timer started */
+#define REPORT_CLOCK_TOTAL(clockno,format, args...) { \
+ if (gnc_should_log (log_module, GNC_LOG_INFO)) \
+ gnc_report_clock_total (clockno, log_module, GNC_LOG_INFO, \
+ __FUNCTION__, format , ## args); \
+}
+
+#endif /* GNC_TRACE_H */
+/* @} */
Added: gnucash/trunk/lib/libqof/qof/guid.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/guid.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/guid.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,680 @@
+/********************************************************************\
+ * guid.c -- globally unique ID implementation *
+ * Copyright (C) 2000 Dave Peticolas <peticola at cs.ucdavis.edu> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#define _GNU_SOURCE
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/times.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "guid.h"
+#include "md5.h"
+#include "qofid.h"
+#include "gnc-trace.h"
+
+# ifndef P_tmpdir
+# define P_tmpdir "/tmp"
+# endif
+
+/** Constants *******************************************************/
+#define DEBUG_GUID 0
+#define BLOCKSIZE 4096
+#define THRESHOLD (2 * BLOCKSIZE)
+
+
+/** Static global variables *****************************************/
+static gboolean guid_initialized = FALSE;
+static struct md5_ctx guid_context;
+static GMemChunk *guid_memchunk = NULL;
+
+/* This static indicates the debugging module that this .o belongs to. */
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+/** Memory management routines ***************************************/
+static void
+guid_memchunk_init (void)
+{
+ if (!guid_memchunk)
+ guid_memchunk = g_mem_chunk_create (GUID, 512, G_ALLOC_AND_FREE);
+}
+
+static void
+guid_memchunk_shutdown (void)
+{
+ if (guid_memchunk)
+ {
+ g_mem_chunk_destroy (guid_memchunk);
+ guid_memchunk = NULL;
+ }
+}
+
+GUID *
+guid_malloc (void)
+{
+ if (!guid_memchunk) guid_memchunk_init();
+ return g_chunk_new (GUID, guid_memchunk);
+}
+
+void
+guid_free (GUID *guid)
+{
+ if (!guid)
+ return;
+
+ g_chunk_free (guid, guid_memchunk);
+}
+
+
+const GUID *
+guid_null(void)
+{
+ static int null_inited = 0;
+ static GUID null_guid;
+
+ if (!null_inited)
+ {
+ int i;
+ char *tmp = "NULLGUID.EMPTY.";
+
+ for (i = 0; i < 16; i++)
+ null_guid.data[i] = tmp[i];
+
+ null_inited = 1;
+ }
+
+ return &null_guid;
+}
+
+/** Function implementations ****************************************/
+
+/* This code is based on code in md5.c in GNU textutils. */
+static size_t
+init_from_stream(FILE *stream, size_t max_size)
+{
+ char buffer[BLOCKSIZE + 72];
+ size_t sum, block_size, total;
+
+ if (max_size <= 0)
+ return 0;
+
+ total = 0;
+
+ /* Iterate over file contents. */
+ while (1)
+ {
+ /* We read the file in blocks of BLOCKSIZE bytes. One call of the
+ * computation function processes the whole buffer so that with the
+ * next round of the loop another block can be read. */
+ size_t n;
+ sum = 0;
+
+ if (max_size < BLOCKSIZE)
+ block_size = max_size;
+ else
+ block_size = BLOCKSIZE;
+
+ /* Read block. Take care for partial reads. */
+ do
+ {
+ n = fread (buffer + sum, 1, block_size - sum, stream);
+
+ sum += n;
+ }
+ while (sum < block_size && n != 0);
+
+ max_size -= sum;
+
+ if (n == 0 && ferror (stream))
+ return total;
+
+ /* If end of file or max_size is reached, end the loop. */
+ if ((n == 0) || (max_size == 0))
+ break;
+
+ /* Process buffer with BLOCKSIZE bytes. Note that
+ * BLOCKSIZE % 64 == 0 */
+ md5_process_block (buffer, BLOCKSIZE, &guid_context);
+
+ total += sum;
+ }
+
+ /* Add the last bytes if necessary. */
+ if (sum > 0)
+ {
+ md5_process_bytes (buffer, sum, &guid_context);
+ total += sum;
+ }
+
+ return total;
+}
+
+static size_t
+init_from_file(const char *filename, size_t max_size)
+{
+ struct stat stats;
+ size_t total = 0;
+ size_t file_bytes;
+ FILE *fp;
+
+ memset(&stats, 0, sizeof(stats));
+ if (stat(filename, &stats) != 0)
+ return 0;
+
+ md5_process_bytes(&stats, sizeof(stats), &guid_context);
+ total += sizeof(stats);
+
+ if (max_size <= 0)
+ return total;
+
+ fp = fopen (filename, "r");
+ if (fp == NULL)
+ return total;
+
+ file_bytes = init_from_stream(fp, max_size);
+
+ PINFO ("guid_init got %llu bytes from %s", (unsigned long long int) file_bytes,
+ filename);
+
+ total += file_bytes;
+
+ fclose(fp);
+
+ return total;
+}
+
+static size_t
+init_from_dir(const char *dirname, unsigned int max_files)
+{
+ char filename[1024];
+ struct dirent *de;
+ struct stat stats;
+ size_t total;
+ int result;
+ DIR *dir;
+
+ if (max_files <= 0)
+ return 0;
+
+ dir = opendir (dirname);
+ if (dir == NULL)
+ return 0;
+
+ total = 0;
+
+ do
+ {
+ de = readdir(dir);
+ if (de == NULL)
+ break;
+
+ md5_process_bytes(de, sizeof(struct dirent), &guid_context);
+ total += sizeof(struct dirent);
+
+ result = snprintf(filename, sizeof(filename),
+ "%s/%s", dirname, de->d_name);
+ if ((result < 0) || (result >= (int)sizeof(filename)))
+ continue;
+
+ memset(&stats, 0, sizeof(stats));
+ if (stat(filename, &stats) != 0)
+ continue;
+ md5_process_bytes(&stats, sizeof(stats), &guid_context);
+ total += sizeof(stats);
+
+ max_files--;
+ } while (max_files > 0);
+
+ closedir(dir);
+
+ return total;
+}
+
+static size_t
+init_from_time(void)
+{
+ size_t total;
+ time_t t_time;
+ clock_t clocks;
+ struct tms tms_buf;
+
+ total = 0;
+
+ t_time = time(NULL);
+ md5_process_bytes(&t_time, sizeof(t_time), &guid_context);
+ total += sizeof(t_time);
+
+ clocks = times(&tms_buf);
+ md5_process_bytes(&clocks, sizeof(clocks), &guid_context);
+ md5_process_bytes(&tms_buf, sizeof(tms_buf), &guid_context);
+ total += sizeof(clocks) + sizeof(tms_buf);
+
+ return total;
+}
+
+static size_t
+init_from_int(int val)
+{
+ md5_process_bytes(&val, sizeof(val), &guid_context);
+ return sizeof(int);
+}
+
+static size_t
+init_from_buff(unsigned char * buf, size_t buflen)
+{
+ md5_process_bytes(buf, buflen, &guid_context);
+ return buflen;
+}
+
+void
+guid_init(void)
+{
+ size_t bytes = 0;
+
+ /* Not needed; taken care of on first malloc.
+ * guid_memchunk_init(); */
+
+ md5_init_ctx(&guid_context);
+
+ /* entropy pool */
+ bytes += init_from_file ("/dev/urandom", 512);
+
+ /* files */
+ {
+ const char * files[] =
+ { "/etc/passwd",
+ "/proc/loadavg",
+ "/proc/meminfo",
+ "/proc/net/dev",
+ "/proc/rtc",
+ "/proc/self/environ",
+ "/proc/self/stat",
+ "/proc/stat",
+ "/proc/uptime",
+ NULL
+ };
+ int i;
+
+ for (i = 0; files[i] != NULL; i++)
+ bytes += init_from_file(files[i], BLOCKSIZE);
+ }
+
+ /* directories */
+ {
+ const char * dirname;
+ const char * dirs[] =
+ {
+ "/proc",
+ P_tmpdir,
+ "/var/lock",
+ "/var/log",
+ "/var/mail",
+ "/var/spool/mail",
+ "/var/run",
+ NULL
+ };
+ int i;
+
+ for (i = 0; dirs[i] != NULL; i++)
+ bytes += init_from_dir(dirs[i], 32);
+
+ dirname = getenv("HOME");
+ if (dirname != NULL)
+ bytes += init_from_dir(dirname, 32);
+ }
+
+ /* process and parent ids */
+ {
+ pid_t pid;
+
+ pid = getpid();
+ md5_process_bytes(&pid, sizeof(pid), &guid_context);
+ bytes += sizeof(pid);
+
+ pid = getppid();
+ md5_process_bytes(&pid, sizeof(pid), &guid_context);
+ bytes += sizeof(pid);
+ }
+
+ /* user info */
+ {
+ uid_t uid;
+ gid_t gid;
+ char *s;
+
+ s = getlogin();
+ if (s != NULL)
+ {
+ md5_process_bytes(s, strlen(s), &guid_context);
+ bytes += strlen(s);
+ }
+
+ uid = getuid();
+ md5_process_bytes(&uid, sizeof(uid), &guid_context);
+ bytes += sizeof(uid);
+
+ gid = getgid();
+ md5_process_bytes(&gid, sizeof(gid), &guid_context);
+ bytes += sizeof(gid);
+ }
+
+ /* host info */
+ {
+ char string[1024];
+
+ memset(string, 0, sizeof(string));
+ gethostname(string, sizeof(string));
+ md5_process_bytes(string, sizeof(string), &guid_context);
+ bytes += sizeof(string);
+ }
+
+ /* plain old random */
+ {
+ int n, i;
+
+ srand((unsigned int) time(NULL));
+
+ for (i = 0; i < 32; i++)
+ {
+ n = rand();
+
+ md5_process_bytes(&n, sizeof(n), &guid_context);
+ bytes += sizeof(n);
+ }
+ }
+
+ /* time in secs and clock ticks */
+ bytes += init_from_time();
+
+ PINFO ("got %llu bytes", (unsigned long long int) bytes);
+
+ if (bytes < THRESHOLD)
+ PWARN("only got %llu bytes.\n"
+ "The identifiers might not be very random.\n",
+ (unsigned long long int)bytes);
+
+ guid_initialized = TRUE;
+}
+
+void
+guid_init_with_salt(const void *salt, size_t salt_len)
+{
+ guid_init();
+
+ md5_process_bytes(salt, salt_len, &guid_context);
+}
+
+void
+guid_init_only_salt(const void *salt, size_t salt_len)
+{
+ md5_init_ctx(&guid_context);
+
+ md5_process_bytes(salt, salt_len, &guid_context);
+
+ guid_initialized = TRUE;
+}
+
+void
+guid_shutdown (void)
+{
+ guid_memchunk_shutdown();
+}
+
+#define GUID_PERIOD 5000
+
+void
+guid_new(GUID *guid)
+{
+ static int counter = 0;
+ struct md5_ctx ctx;
+
+ if (guid == NULL)
+ return;
+
+ if (!guid_initialized)
+ guid_init();
+
+ /* make the id */
+ ctx = guid_context;
+ md5_finish_ctx(&ctx, guid->data);
+
+ /* update the global context */
+ init_from_time();
+
+ /* Make it a little extra salty. I think init_from_time was buggy,
+ * or something, since duplicate id's actually happened. Or something
+ * like that. I think this is because init_from_time kept returning
+ * the same values too many times in a row. So we'll do some 'block
+ * chaining', and feed in the old guid as new random data.
+ *
+ * Anyway, I think the whole fact that I saw a bunch of duplicate
+ * id's at one point, but can't reproduce the bug is rather alarming.
+ * Something must be broken somewhere, and merely adding more salt
+ * is just hiding the problem, not fixing it.
+ */
+ init_from_int (433781*counter);
+ init_from_buff (guid->data, 16);
+
+ if (counter == 0)
+ {
+ FILE *fp;
+
+ fp = fopen ("/dev/urandom", "r");
+ if (fp == NULL)
+ return;
+
+ init_from_stream(fp, 32);
+
+ fclose(fp);
+
+ counter = GUID_PERIOD;
+ }
+
+ counter--;
+}
+
+GUID
+guid_new_return(void)
+{
+ GUID guid;
+
+ guid_new (&guid);
+
+ return guid;
+}
+
+/* needs 32 bytes exactly, doesn't print a null char */
+static void
+encode_md5_data(const unsigned char *data, char *buffer)
+{
+ size_t count;
+
+ for (count = 0; count < 16; count++, buffer += 2)
+ sprintf(buffer, "%02x", data[count]);
+}
+
+/* returns true if the first 32 bytes of buffer encode
+ * a hex number. returns false otherwise. Decoded number
+ * is packed into data in little endian order. */
+static gboolean
+decode_md5_string(const char *string, unsigned char *data)
+{
+ unsigned char n1, n2;
+ size_t count = -1;
+ char c1, c2;
+
+ if (NULL == data) return FALSE;
+ if (NULL == string) goto badstring;
+
+ for (count = 0; count < 16; count++)
+ {
+ /* check for a short string e.g. null string ... */
+ if ((0==string[2*count]) || (0==string[2*count+1])) goto badstring;
+
+ c1 = tolower(string[2 * count]);
+ if (!isxdigit(c1)) goto badstring;
+
+ c2 = tolower(string[2 * count + 1]);
+ if (!isxdigit(c2)) goto badstring;
+
+ if (isdigit(c1))
+ n1 = c1 - '0';
+ else
+ n1 = c1 - 'a' + 10;
+
+ if (isdigit(c2))
+ n2 = c2 - '0';
+ else
+ n2 = c2 - 'a' + 10;
+
+ data[count] = (n1 << 4) | n2;
+ }
+ return TRUE;
+
+badstring:
+ for (count = 0; count < 16; count++)
+ {
+ data[count] = 0;
+ }
+ return FALSE;
+}
+
+/* Allocate the key */
+
+const char *
+guid_to_string(const GUID * guid)
+{
+#ifdef G_THREADS_ENABLED
+ static GStaticPrivate guid_buffer_key = G_STATIC_PRIVATE_INIT;
+ gchar *string;
+
+ string = g_static_private_get (&guid_buffer_key);
+ if (string == NULL) {
+ string = malloc(GUID_ENCODING_LENGTH+1);
+ g_static_private_set (&guid_buffer_key, string, g_free);
+ }
+#else
+ static char string[64];
+#endif
+
+ encode_md5_data(guid->data, string);
+ string[GUID_ENCODING_LENGTH] = '\0';
+
+ return string;
+}
+
+char *
+guid_to_string_buff(const GUID * guid, char *string)
+{
+ if (!string || !guid) return NULL;
+
+ encode_md5_data(guid->data, string);
+
+ string[GUID_ENCODING_LENGTH] = '\0';
+ return &string[GUID_ENCODING_LENGTH];
+}
+
+gboolean
+string_to_guid(const char * string, GUID * guid)
+{
+ return decode_md5_string(string, (guid != NULL) ? guid->data : NULL);
+}
+
+gboolean
+guid_equal(const GUID *guid_1, const GUID *guid_2)
+{
+ if (guid_1 && guid_2)
+ return (memcmp(guid_1, guid_2, sizeof(GUID)) == 0);
+ else
+ return FALSE;
+}
+
+gint
+guid_compare(const GUID *guid_1, const GUID *guid_2)
+{
+ if (guid_1 == guid_2)
+ return 0;
+
+ /* nothing is always less than something */
+ if (!guid_1 && guid_2)
+ return -1;
+
+ if (guid_1 && !guid_2)
+ return 1;
+
+ return memcmp (guid_1, guid_2, sizeof (GUID));
+}
+
+guint
+guid_hash_to_guint (gconstpointer ptr)
+{
+ const GUID *guid = ptr;
+
+ if (!guid)
+ {
+ PERR ("received NULL guid pointer.");
+ return 0;
+ }
+
+ if (sizeof(guint) <= sizeof(guid->data))
+ {
+ return (*((guint *) guid->data));
+ }
+ else
+ {
+ guint hash = 0;
+ unsigned int i, j;
+
+ for (i = 0, j = 0; i < sizeof(guint); i++, j++) {
+ if (j == 16) j = 0;
+
+ hash <<= 4;
+ hash |= guid->data[j];
+ }
+
+ return hash;
+ }
+}
+
+static gint
+guid_g_hash_table_equal (gconstpointer guid_a, gconstpointer guid_b)
+{
+ return guid_equal (guid_a, guid_b);
+}
+
+GHashTable *
+guid_hash_table_new (void)
+{
+ return g_hash_table_new (guid_hash_to_guint, guid_g_hash_table_equal);
+}
Added: gnucash/trunk/lib/libqof/qof/guid.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/guid.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/guid.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,204 @@
+/********************************************************************\
+ * guid.h -- globally unique ID User API *
+ * Copyright (C) 2000 Dave Peticolas <peticola at cs.ucdavis.edu> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#ifndef GUID_H
+#define GUID_H
+
+#include <glib.h>
+#include <stddef.h>
+
+/** @addtogroup Entity
+ @{ */
+/** @addtogroup GUID
+ Globally Unique ID's provide a way to uniquely identify
+ some thing. A GUID is a unique, cryptographically
+ random 128-bit value. The identifier is so random that
+ it is safe to assume that there is no other such item
+ on the planet Earth, and indeed, not even in the Galaxy
+ or beyond.
+
+ QOF GUID's can be used independently of any other subsystem
+ in QOF. In particular, they do not require the use of
+ other parts of the object subsystem.
+
+ @{ */
+/** @file guid.h
+ @brief globally unique ID User API
+ @author Copyright (C) 2000 Dave Peticolas <peticola at cs.ucdavis.edu>
+*/
+
+/** The type used to store guids */
+typedef union _GUID
+{
+ unsigned char data[16];
+
+ int __align_me; /* this just ensures that GUIDs are 32-bit
+ * aligned on systems that need them to be. */
+} GUID;
+
+
+/** number of characters needed to encode a guid as a string
+ * not including the null terminator. */
+#define GUID_ENCODING_LENGTH 32
+
+
+/** Initialize the id generator with a variety of random
+ * sources.
+ *
+ * @note Only one of guid_init(), guid_init_with_salt() and
+ * guid_init_only_salt() should be called. Calling any
+ * initialization function a second time will reset the generator and
+ * erase the effect of the first call.
+ */
+void guid_init(void);
+
+/** Initialize the id generator with a variety of random sources. and
+ * with the data given in the salt argument. This argument can be
+ * used to add additional randomness to the generated ids.
+ *
+ * @param salt The additional random values to add to the generator.
+ *
+ * @param salt_len The length of the additional random values.
+ *
+ * @note Only one of guid_init(), guid_init_with_salt() and
+ * guid_init_only_salt() should be called. Calling any
+ * initialization function a second time will reset the generator and
+ * erase the effect of the first call.
+ */
+void guid_init_with_salt(const void *salt, size_t salt_len);
+
+/** Initialize the id generator with the data given in the salt
+ * argument, but not with any other source. Calling this function with
+ * a specific argument will reliably produce a specific sequence of
+ * ids.
+ *
+ * @param salt The additional random values to add to the generator.
+ *
+ * @param salt_len The length of the additional random values.
+ *
+ * @note Only one of guid_init(), guid_init_with_salt() and
+ * guid_init_only_salt() should be called. Calling any
+ * initialization function a second time will reset the generator and
+ * erase the effect of the first call.
+ */
+void guid_init_only_salt(const void *salt, size_t salt_len);
+
+/** Release the memory chunk associated with gui storage. Use this
+ * only when shutting down the program, as it invalidates *all*
+ * GUIDs at once. */
+void guid_shutdown (void);
+
+/** Generate a new id. If no initialization function has been called,
+ * guid_init() will be called before the id is created.
+ *
+ * @param guid A pointer to an existing guid data structure. The
+ * existing value will be replaced with a new value.
+ *
+ * This routine uses the md5 algorithm to build strong random guids.
+ * Note that while guid's are generated randomly, the odds of this
+ * routine returning a non-unique id are astronomically small.
+ * (Literally astronomically: If you had Cray's on every solar
+ * system in the universe running for the entire age of the universe,
+ * you'd still have less than a one-in-a-million chance of coming up
+ * with a duplicate id. 2^128 == 10^38 is a really really big number.)
+ */
+void guid_new(GUID *guid);
+
+/** Generate a new id. If no initialization function has been called,
+ * guid_init() will be called before the id is created.
+ *
+ * @return guid A pointer to a data structure containing a new GUID.
+ * The memory pointed to is owned by this routine and the guid must
+ * be copied out.
+ *
+ * CAS: huh? make that: @return guid A data structure containing a newly
+ * allocated GUID. Caller is responsible for calling guid_free().
+ */
+GUID guid_new_return(void);
+
+/** Returns a GUID which is guaranteed to never reference any entity. */
+/* CAS: AFAICT: this isn't really guaranteed, but it's only as likely
+ as any other md5 collision. This could be guaranteed if GUID
+ contained a validity flag. */
+const GUID * guid_null (void);
+
+/** Efficiently allocate & free memory for GUIDs */
+GUID * guid_malloc (void);
+
+/* Return a guid set to all zero's */
+void guid_free (GUID *guid);
+
+/** The guid_to_string() routine returns a null-terminated string
+ * encoding of the id. String encodings of identifiers are hex
+ * numbers printed only with the characters '0' through '9' and
+ * 'a' through 'f'. The encoding will always be GUID_ENCODING_LENGTH
+ * characters long.
+ *
+ * XXX This routine is not thread safe and is deprecated. Please
+ * use the routine guid_to_string_buff() instead.
+ *
+ * @param guid The guid to print.
+ *
+ * @return A pointer to the starting character of the string. The
+ * returned memory is owned by this routine and may not be freed by
+ * the caller.
+ */
+const char * guid_to_string (const GUID * guid);
+
+/** The guid_to_string_buff() routine puts a null-terminated string
+ * encoding of the id into the memory pointed at by buff. The
+ * buffer must be at least GUID_ENCODING_LENGTH+1 characters long.
+ * This routine is handy for avoiding a malloc/free cycle. It
+ * returns a pointer to the >>end<< of what was written. (i.e. it
+ * can be used like 'stpcpy' during string concatenation)
+ *
+ * @param guid The guid to print.
+ *
+ * @param buff The buffer to print it into.
+ *
+ * @return A pointer to the terminating null character of the string.
+ */
+char * guid_to_string_buff (const GUID * guid, char *buff);
+
+
+/** Given a string, decode the id into the guid if guid is non-NULL.
+ * The function returns TRUE if the string was a valid 32 character
+ * hexadecimal number. This function accepts both upper and lower case
+ * hex digits. If the return value is FALSE, the effect on guid is
+ * undefined. */
+gboolean string_to_guid(const char * string, GUID * guid);
+
+
+/** Given two GUIDs, return TRUE if they are non-NULL and equal.
+ * Return FALSE, otherwise. */
+gboolean guid_equal(const GUID *guid_1, const GUID *guid_2);
+gint guid_compare(const GUID *g1, const GUID *g2);
+
+/** Given a GUID *, hash it to a guint */
+guint guid_hash_to_guint(gconstpointer ptr);
+
+GHashTable *guid_hash_table_new(void);
+
+/* @} */
+/* @} */
+#endif
Added: gnucash/trunk/lib/libqof/qof/kvp-util-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/kvp-util-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/kvp-util-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,108 @@
+/********************************************************************\
+ * kvp-util-p.h -- misc odd-job kvp utils (private file) *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#ifndef XACC_KVP_UTIL_P_H
+#define XACC_KVP_UTIL_P_H
+
+#include "config.h"
+
+#include "guid.h"
+#include "kvp_frame.h"
+
+/** @addtogroup KVP
+ @{
+*/
+/** @file kvp-util-p.h
+ * @brief misc odd-job kvp utils engine-private routines
+ * @author Copyright (C) 2001, 2003 Linas Vepstas <linas at linas.org> *
+*/
+/** @name KvpBag Bags of GUID Pointers
+ @{
+*/
+
+/** The gnc_kvp_bag_add() routine is used to maintain a collection
+ * of pointers in a kvp tree.
+ *
+ * The thing being pointed at is uniquely identified by its GUID.
+ * This routine is typically used to create a linked list, and/or
+ * a collection of pointers to objects that are 'related' to each
+ * other in some way.
+ *
+ * The var-args should be pairs of strings (const char *) followed by
+ * the corresponding GUID pointer (const GUID *). Terminate the varargs
+ * with a NULL as the last string argument.
+ *
+ * The actual 'pointer' is stored in a subdirectory in a bag located at
+ * the node directory 'path'. A 'bag' is merely a collection of
+ * (unamed) values. The name of our bag is 'path'. A bag can contain
+ * any kind of values, including frames. This routine will create a
+ * frame, and put it in the bag. The frame will contain named data
+ * from the subroutine arguments. Thus, for example:
+ *
+ * gnc_kvp_array (kvp, "foo", secs, "acct_guid", aguid,
+ * "book_guid", bguid, NULL);
+ *
+ * will create a frame containing "/acct_guid" and "/book_guid", whose
+ * values are aguid and bguid respecitvely. The frame will also
+ * contain "/date", whose value will be secs. This frame will be
+ * placed into the bag located at "foo".
+ *
+ * This routine returns a pointer to the frame that was created, or
+ * NULL if an error occured.
+ */
+
+KvpFrame * gnc_kvp_bag_add (KvpFrame *kvp_root, const char *path, time_t secs,
+ const char *first_name, ...);
+
+
+/** The gnc_kvp_bag_merge() routine will move the bag contents from
+ * the 'kvp_from', to the 'into' bag. It will then delete the
+ * 'from' bag from the kvp tree.
+ */
+void gnc_kvp_bag_merge (KvpFrame *kvp_into, const char *intopath,
+ KvpFrame *kvp_from, const char *frompath);
+
+/** The gnc_kvp_bag_find_by_guid() routine examines the bag pointed
+ * located at root. It looks for a frame in that bag that has the
+ * guid value of "desired_guid" filed under the key name "guid_name".
+ * If it finds that matching guid, then it returns a pointer to
+ * the KVP frame that contains it. If it is not found, or if there
+ * is any other error, NULL is returned.
+ */
+
+KvpFrame * gnc_kvp_bag_find_by_guid (KvpFrame *root, const char * path,
+ const char *guid_name, GUID *desired_guid);
+
+
+/** Remove the given frame from the bag. The frame is removed,
+ * however, it is not deleted. Note that the frame pointer must
+ * be a pointer to the actual frame (for example, as returned by
+ * gnc_kvp_bag_find_by_guid() for by gnc_kvp_bag_add()), and not
+ * some copy of the frame.
+ */
+
+void gnc_kvp_bag_remove_frame (KvpFrame *root, const char *path,
+ KvpFrame *fr);
+
+/** @} */
+/** @} */
+#endif /* XACC_KVP_UTIL_P_H */
Added: gnucash/trunk/lib/libqof/qof/kvp-util.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/kvp-util.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/kvp-util.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,235 @@
+/********************************************************************\
+ * kvp_util.c -- misc odd-job kvp utils *
+ * Copyright (C) 2001 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#include "config.h"
+
+#include <glib.h>
+#include <stdio.h>
+
+#include "kvp_frame.h"
+#include "kvp-util.h"
+#include "kvp-util-p.h"
+
+/* ================================================================ */
+
+static KvpFrame *
+gnc_kvp_array_va (KvpFrame *kvp_root, const char * path,
+ time_t secs, const char * first_name, va_list ap)
+{
+ KvpFrame *cwd;
+ Timespec ts;
+ const char *name;
+
+ if (!kvp_root) return NULL;
+ if (!first_name) return NULL;
+
+ /* Create subdirectory and put the actual data */
+ cwd = kvp_frame_new();
+
+ /* Record the time */
+ ts.tv_sec = secs;
+ ts.tv_nsec = 0;
+ kvp_frame_set_timespec (cwd, "date", ts);
+
+ /* Loop over the args */
+ name = first_name;
+ while (name)
+ {
+ const GUID *guid;
+ guid = va_arg (ap, const GUID *);
+
+ kvp_frame_set_guid (cwd, name, guid);
+
+ name = va_arg (ap, const char *);
+ }
+
+ /* Attach cwd into the array */
+ kvp_frame_add_frame_nc (kvp_root, path, cwd);
+ return cwd;
+}
+
+/* ================================================================ */
+
+KvpFrame *
+gnc_kvp_bag_add (KvpFrame *pwd, const char * path,
+ time_t secs, const char *first_name, ...)
+{
+ KvpFrame *cwd;
+ va_list ap;
+ va_start (ap, first_name);
+ cwd = gnc_kvp_array_va (pwd, path, secs, first_name, ap);
+ va_end (ap);
+ return cwd;
+}
+
+/* ================================================================ */
+
+#define MATCH_GUID(elt) { \
+ KvpFrame *fr = kvp_value_get_frame (elt); \
+ if (fr) { \
+ GUID *guid = kvp_frame_get_guid (fr, guid_name); \
+ if (guid && guid_equal (desired_guid, guid)) return fr; \
+ } \
+}
+
+KvpFrame *
+gnc_kvp_bag_find_by_guid (KvpFrame *root, const char * path,
+ const char *guid_name, GUID *desired_guid)
+{
+ KvpValue *arr;
+ KvpValueType valtype;
+ GList *node;
+
+ arr = kvp_frame_get_value (root, path);
+ valtype = kvp_value_get_type (arr);
+ if (KVP_TYPE_FRAME == valtype)
+ {
+ MATCH_GUID (arr);
+ return NULL;
+ }
+
+ /* Its gotta be a single isolated frame, or a list of them. */
+ if (KVP_TYPE_GLIST != valtype) return NULL;
+
+ for (node = kvp_value_get_glist(arr); node; node=node->next)
+ {
+ KvpValue *va = node->data;
+ MATCH_GUID (va);
+ }
+ return NULL;
+}
+
+/* ================================================================ */
+
+void
+gnc_kvp_bag_remove_frame (KvpFrame *root, const char *path, KvpFrame *fr)
+{
+ KvpValue *arr;
+ KvpValueType valtype;
+ GList *node, *listhead;
+
+ arr = kvp_frame_get_value (root, path);
+ valtype = kvp_value_get_type (arr);
+ if (KVP_TYPE_FRAME == valtype)
+ {
+ if (fr == kvp_value_get_frame (arr))
+ {
+ KvpValue *old_val = kvp_frame_replace_value_nc (root, path, NULL);
+ kvp_value_replace_frame_nc (old_val, NULL);
+ kvp_value_delete (old_val);
+ }
+ return;
+ }
+
+ /* Its gotta be a single isolated frame, or a list of them. */
+ if (KVP_TYPE_GLIST != valtype) return;
+
+ listhead = kvp_value_get_glist(arr);
+ for (node = listhead; node; node=node->next)
+ {
+ KvpValue *va = node->data;
+ if (fr == kvp_value_get_frame (va))
+ {
+ listhead = g_list_remove_link (listhead, node);
+ g_list_free_1 (node);
+ kvp_value_replace_glist_nc (arr, listhead);
+ kvp_value_replace_frame_nc (va, NULL);
+ kvp_value_delete (va);
+ return;
+ }
+ }
+}
+
+/* ================================================================ */
+
+static KvpFrame *
+gnc_kvp_bag_get_first (KvpFrame *root, const char * path)
+{
+ KvpValue *arr, *va;
+ KvpValueType valtype;
+ GList *node;
+
+ arr = kvp_frame_get_value (root, path);
+ valtype = kvp_value_get_type (arr);
+ if (KVP_TYPE_FRAME == valtype)
+ {
+ return kvp_value_get_frame(arr);
+ }
+
+ /* Its gotta be a single isolated frame, or a list of them. */
+ if (KVP_TYPE_GLIST != valtype) return NULL;
+
+ node = kvp_value_get_glist(arr);
+ if (NULL == node) return NULL;
+
+ va = node->data;
+ return kvp_value_get_frame(va);
+}
+
+void
+gnc_kvp_bag_merge (KvpFrame *kvp_into, const char *intopath,
+ KvpFrame *kvp_from, const char *frompath)
+{
+ KvpFrame *fr;
+
+ fr = gnc_kvp_bag_get_first (kvp_from, frompath);
+ while (fr)
+ {
+ gnc_kvp_bag_remove_frame (kvp_from, frompath, fr);
+ kvp_frame_add_frame_nc (kvp_into, intopath, fr);
+ fr = gnc_kvp_bag_get_first (kvp_from, frompath);
+ }
+}
+
+/* ================================================================ */
+/*
+ * See header for docs.
+ */
+
+static void
+kv_pair_helper(gpointer key, gpointer val, gpointer user_data)
+{
+ GSList **result = (GSList **) user_data;
+ GHashTableKVPair *kvp = g_new(GHashTableKVPair, 1);
+
+ kvp->key = key;
+ kvp->value = val;
+ *result = g_slist_prepend(*result, kvp);
+}
+
+GSList *
+g_hash_table_key_value_pairs(GHashTable *table)
+{
+ GSList *result_list = NULL;
+ g_hash_table_foreach(table, kv_pair_helper, &result_list);
+ return result_list;
+}
+
+void
+g_hash_table_kv_pair_free_gfunc(gpointer data, gpointer user_data)
+{
+ GHashTableKVPair *kvp = (GHashTableKVPair *) data;
+ g_free(kvp);
+}
+
+/*======================== END OF FILE =============================*/
Added: gnucash/trunk/lib/libqof/qof/kvp-util.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/kvp-util.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/kvp-util.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,64 @@
+/********************************************************************\
+ * kvp-util.h -- misc KVP utilities *
+ * Copyright (C) 2003 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+\********************************************************************/
+
+/** @addtogroup KVP
+ @{
+*/
+/** @file kvp-util.h
+ @brief GnuCash KVP utility functions
+ */
+/** @name Hash Utilities
+ @{
+*/
+
+#ifndef GNC_KVP_UTIL_H
+#define GNC_KVP_UTIL_H
+
+#include "config.h"
+
+typedef struct {
+ gpointer key;
+ gpointer value;
+} GHashTableKVPair;
+
+/**
+ Returns a GSList* of all the
+ keys and values in a given hash table. Data elements of lists are
+ actual hash elements, so be careful, and deallocation of the
+ GHashTableKVPairs in the result list are the caller's
+ responsibility. A typical sequence might look like this:
+
+ GSList *kvps = g_hash_table_key_value_pairs(hash);
+ ... use kvps->data->key and kvps->data->val, etc. here ...
+ g_slist_foreach(kvps, g_hash_table_kv_pair_free_gfunc, NULL);
+ g_slist_free(kvps);
+
+*/
+
+GSList *g_hash_table_key_value_pairs(GHashTable *table);
+void g_hash_table_kv_pair_free_gfunc(gpointer data, gpointer user_data);
+
+/***********************************************************************/
+
+/** @} */
+/** @} */
+#endif /* GNC_KVP_UTIL_H */
Added: gnucash/trunk/lib/libqof/qof/kvp_frame.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/kvp_frame.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/kvp_frame.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1838 @@
+/********************************************************************
+ * kvp_frame.c -- Implements a key-value frame system *
+ * Copyright (C) 2000 Bill Gribble *
+ * Copyright (C) 2001,2003 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ ********************************************************************/
+
+#include "config.h"
+
+#define _GNU_SOURCE
+#include <glib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "gnc-date.h"
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "gnc-numeric.h"
+#include "guid.h"
+#include "kvp_frame.h"
+
+
+ /* Note that we keep the keys for this hash table in a GCache
+ * (gnc_string_cache), as it is very likely we will see the
+ * same keys over and over again */
+
+struct _KvpFrame
+{
+ GHashTable * hash;
+};
+
+
+typedef struct
+{
+ void *data;
+ int datasize;
+} KvpValueBinaryData;
+
+struct _KvpValue
+{
+ KvpValueType type;
+ union {
+ gint64 int64;
+ double dbl;
+ gnc_numeric numeric;
+ gchar *str;
+ GUID *guid;
+ Timespec timespec;
+ KvpValueBinaryData binary;
+ GList *list;
+ KvpFrame *frame;
+ } value;
+};
+
+/* This static indicates the debugging module that this .o belongs to. */
+static QofLogModule log_module = QOF_MOD_KVP;
+
+/********************************************************************
+ * KvpFrame functions
+ ********************************************************************/
+
+static guint
+kvp_hash_func(gconstpointer v)
+{
+ return g_str_hash(v);
+}
+
+static gint
+kvp_comp_func(gconstpointer v, gconstpointer v2)
+{
+ return g_str_equal(v, v2);
+}
+
+static gboolean
+init_frame_body_if_needed(KvpFrame *f)
+{
+ if(!f->hash)
+ {
+ f->hash = g_hash_table_new(&kvp_hash_func, &kvp_comp_func);
+ }
+ return(f->hash != NULL);
+}
+
+KvpFrame *
+kvp_frame_new(void)
+{
+ KvpFrame * retval = g_new0(KvpFrame, 1);
+
+ /* Save space until the frame is actually used */
+ retval->hash = NULL;
+ return retval;
+}
+
+static void
+kvp_frame_delete_worker(gpointer key, gpointer value, gpointer user_data)
+{
+ g_cache_remove(gnc_engine_get_string_cache(), key);
+ kvp_value_delete((KvpValue *)value);
+}
+
+void
+kvp_frame_delete(KvpFrame * frame)
+{
+ if (!frame) return;
+
+ if(frame->hash)
+ {
+ /* free any allocated resource for frame or its children */
+ g_hash_table_foreach(frame->hash, & kvp_frame_delete_worker,
+ (gpointer)frame);
+
+ /* delete the hash table */
+ g_hash_table_destroy(frame->hash);
+ frame->hash = NULL;
+ }
+ g_free(frame);
+}
+
+gboolean
+kvp_frame_is_empty(KvpFrame * frame)
+{
+ if (!frame) return TRUE;
+ if (!frame->hash) return TRUE;
+ return FALSE;
+}
+
+static void
+kvp_frame_copy_worker(gpointer key, gpointer value, gpointer user_data)
+{
+ KvpFrame * dest = (KvpFrame *)user_data;
+ g_hash_table_insert(dest->hash,
+ (gpointer)g_cache_insert(gnc_engine_get_string_cache(), key),
+ (gpointer)kvp_value_copy(value));
+}
+
+KvpFrame *
+kvp_frame_copy(const KvpFrame * frame)
+{
+ KvpFrame * retval = kvp_frame_new();
+
+ if (!frame) return retval;
+
+ if(frame->hash)
+ {
+ if(!init_frame_body_if_needed(retval)) return(NULL);
+ g_hash_table_foreach(frame->hash,
+ & kvp_frame_copy_worker,
+ (gpointer)retval);
+ }
+ return retval;
+}
+
+/* Replace the old value with the new value. Return the old value.
+ * Passing in a null value into this routine has the effect of
+ * removing the key from the KVP tree.
+ */
+KvpValue *
+kvp_frame_replace_slot_nc (KvpFrame * frame, const char * slot,
+ KvpValue * new_value)
+{
+ gpointer orig_key;
+ gpointer orig_value = NULL;
+ int key_exists;
+
+ if (!frame || !slot) return NULL;
+ if (!init_frame_body_if_needed(frame)) return NULL; /* Error ... */
+
+ key_exists = g_hash_table_lookup_extended(frame->hash, slot,
+ & orig_key, & orig_value);
+ if(key_exists)
+ {
+ g_hash_table_remove(frame->hash, slot);
+ g_cache_remove(gnc_engine_get_string_cache(), orig_key);
+ }
+ else
+ {
+ orig_value = NULL;
+ }
+
+ if(new_value)
+ {
+ g_hash_table_insert(frame->hash,
+ g_cache_insert(gnc_engine_get_string_cache(),
+ (gpointer) slot),
+ new_value);
+ }
+
+ return (KvpValue *) orig_value;
+}
+
+/* Passing in a null value into this routine has the effect
+ * of deleting the old value stored at this slot.
+ */
+static inline void
+kvp_frame_set_slot_destructively(KvpFrame * frame, const char * slot,
+ KvpValue * new_value)
+{
+ KvpValue * old_value;
+ old_value = kvp_frame_replace_slot_nc (frame, slot, new_value);
+ kvp_value_delete (old_value);
+}
+
+/* ============================================================ */
+/* Get the named frame, or create it if it doesn't exist.
+ * gcc -O3 should inline it. It performs no error checks,
+ * the caller is responsible of passing good keys and frames.
+ */
+static inline KvpFrame *
+get_or_make (KvpFrame *fr, const char * key)
+{
+ KvpFrame *next_frame;
+ KvpValue *value;
+
+ value = kvp_frame_get_slot (fr, key);
+ if (value)
+ {
+ next_frame = kvp_value_get_frame (value);
+ }
+ else
+ {
+ next_frame = kvp_frame_new ();
+ kvp_frame_set_slot_nc (fr, key,
+ kvp_value_new_frame_nc (next_frame));
+ }
+ return next_frame;
+}
+
+/* Get pointer to last frame in path. If teh path doesn't exist,
+ * it is created. The string stored in keypath will be hopelessly
+ * mangled .
+ */
+static inline KvpFrame *
+kvp_frame_get_frame_slash_trash (KvpFrame *frame, char *key_path)
+{
+ char *key, *next;
+ if (!frame || !key_path) return frame;
+
+ key = key_path;
+ key --;
+
+ while (key)
+ {
+ key ++;
+ while ('/' == *key) { key++; }
+ if (0x0 == *key) break; /* trailing slash */
+ next = strchr (key, '/');
+ if (next) *next = 0x0;
+
+ frame = get_or_make (frame, key);
+ if (!frame) break; /* error - should never happen */
+
+ key = next;
+ }
+ return frame;
+}
+
+/* ============================================================ */
+/* Get pointer to last frame in path, or NULL if the path doesn't
+ * exist. The string stored in keypath will be hopelessly mangled .
+ */
+static inline const KvpFrame *
+kvp_frame_get_frame_or_null_slash_trash (const KvpFrame *frame, char *key_path)
+{
+ KvpValue *value;
+ char *key, *next;
+ if (!frame || !key_path) return NULL;
+
+ key = key_path;
+ key --;
+
+ while (key)
+ {
+ key ++;
+ while ('/' == *key) { key++; }
+ if (0x0 == *key) break; /* trailing slash */
+ next = strchr (key, '/');
+ if (next) *next = 0x0;
+
+ value = kvp_frame_get_slot (frame, key);
+ if (!value) return NULL;
+ frame = kvp_value_get_frame (value);
+ if (!frame) return NULL;
+
+ key = next;
+ }
+ return frame;
+}
+
+/* Return pointer to last frame in path, and also store the
+ * last dangling part of path in 'end_key'. If path doesn't
+ * exist, it is created.
+ */
+
+static inline KvpFrame *
+get_trailer_make (KvpFrame * frame, const char * key_path, char **end_key)
+{
+ char *last_key;
+
+ if (!frame || !key_path || (0 == key_path[0])) return NULL;
+
+ last_key = strrchr (key_path, '/');
+ if (NULL == last_key)
+ {
+ last_key = (char *) key_path;
+ }
+ else if (last_key == key_path)
+ {
+ last_key ++;
+ }
+ else if (0 == last_key[1])
+ {
+ return NULL;
+ }
+ else
+ {
+ char *root, *lkey;
+ root = g_strdup (key_path);
+ lkey = strrchr (root, '/');
+ *lkey = 0;
+ frame = kvp_frame_get_frame_slash_trash (frame, root);
+ g_free(root);
+
+ last_key ++;
+ }
+
+ *end_key = last_key;
+ return frame;
+}
+
+
+/* Return pointer to last frame in path, or NULL if the path
+ * doesn't exist. Also store the last dangling part of path
+ * in 'end_key'.
+ */
+
+static inline const KvpFrame *
+get_trailer_or_null (const KvpFrame * frame, const char * key_path, char **end_key)
+{
+ char *last_key;
+
+ if (!frame || !key_path || (0 == key_path[0])) return NULL;
+
+ last_key = strrchr (key_path, '/');
+ if (NULL == last_key)
+ {
+ last_key = (char *) key_path;
+ }
+ else if (last_key == key_path)
+ {
+ last_key ++;
+ }
+ else if (0 == last_key[1])
+ {
+ return NULL;
+ }
+ else
+ {
+ char *root, *lkey;
+ root = g_strdup (key_path);
+ lkey = strrchr (root, '/');
+ *lkey = 0;
+ frame = kvp_frame_get_frame_or_null_slash_trash (frame, root);
+ g_free(root);
+
+ last_key ++;
+ }
+
+ *end_key = last_key;
+ return frame;
+}
+
+/* ============================================================ */
+
+void
+kvp_frame_set_gint64(KvpFrame * frame, const char * path, gint64 ival)
+{
+ KvpValue *value;
+ value = kvp_value_new_gint64 (ival);
+ frame = kvp_frame_set_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_double(KvpFrame * frame, const char * path, double dval)
+{
+ KvpValue *value;
+ value = kvp_value_new_double (dval);
+ frame = kvp_frame_set_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_numeric(KvpFrame * frame, const char * path, gnc_numeric nval)
+{
+ KvpValue *value;
+ value = kvp_value_new_gnc_numeric (nval);
+ frame = kvp_frame_set_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_string(KvpFrame * frame, const char * path, const char* str)
+{
+ KvpValue *value;
+ value = kvp_value_new_string (str);
+ frame = kvp_frame_set_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_guid(KvpFrame * frame, const char * path, const GUID *guid)
+{
+ KvpValue *value;
+ value = kvp_value_new_guid (guid);
+ frame = kvp_frame_set_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_timespec(KvpFrame * frame, const char * path, Timespec ts)
+{
+ KvpValue *value;
+ value = kvp_value_new_timespec (ts);
+ frame = kvp_frame_set_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_frame(KvpFrame * frame, const char * path, KvpFrame *fr)
+{
+ KvpValue *value;
+ value = kvp_value_new_frame (fr);
+ frame = kvp_frame_set_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_set_frame_nc(KvpFrame * frame, const char * path, KvpFrame *fr)
+{
+ KvpValue *value;
+ value = kvp_value_new_frame_nc (fr);
+ frame = kvp_frame_set_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+/* ============================================================ */
+
+KvpFrame *
+kvp_frame_set_value_nc (KvpFrame * frame, const char * key_path,
+ KvpValue * value)
+{
+ char *last_key;
+
+ frame = get_trailer_make (frame, key_path, &last_key);
+ if (!frame) return NULL;
+ kvp_frame_set_slot_destructively(frame, last_key, value);
+ return frame;
+}
+
+KvpFrame *
+kvp_frame_set_value (KvpFrame * frame, const char * key_path,
+ const KvpValue * value)
+{
+ KvpValue *new_value = NULL;
+ char *last_key;
+
+ frame = get_trailer_make (frame, key_path, &last_key);
+ if (!frame) return NULL;
+
+ if (value) new_value = kvp_value_copy(value);
+ kvp_frame_set_slot_destructively(frame, last_key, new_value);
+ return frame;
+}
+
+KvpValue *
+kvp_frame_replace_value_nc (KvpFrame * frame, const char * key_path,
+ KvpValue * new_value)
+{
+ KvpValue * old_value;
+ char *last_key;
+
+ last_key = NULL;
+ if (new_value)
+ {
+ frame = get_trailer_make (frame, key_path, &last_key);
+ }
+ else
+ {
+ frame = (KvpFrame *) get_trailer_or_null (frame, key_path, &last_key);
+ }
+ if (!frame) return NULL;
+
+ old_value = kvp_frame_replace_slot_nc (frame, last_key, new_value);
+ return old_value;
+}
+
+/* ============================================================ */
+
+KvpFrame *
+kvp_frame_add_value_nc(KvpFrame * frame, const char * path, KvpValue *value)
+{
+ char *key = NULL;
+ KvpValue *oldvalue;
+
+ frame = (KvpFrame *) get_trailer_or_null (frame, path, &key);
+ oldvalue = kvp_frame_get_slot (frame, key);
+
+ ENTER ("old frame=%s", kvp_frame_to_string(frame));
+ if (oldvalue)
+ {
+ /* If already a glist here, just append */
+ if (KVP_TYPE_GLIST == oldvalue->type)
+ {
+ GList * vlist = oldvalue->value.list;
+ vlist = g_list_append (vlist, value);
+ oldvalue->value.list = vlist;
+ }
+ else
+ /* If some other value, convert it to a glist */
+ {
+ KvpValue *klist;
+ GList *vlist = NULL;
+
+ vlist = g_list_append (vlist, oldvalue);
+ vlist = g_list_append (vlist, value);
+ klist = kvp_value_new_glist_nc (vlist);
+
+ kvp_frame_replace_slot_nc (frame, key, klist);
+ }
+ LEAVE ("new frame=%s", kvp_frame_to_string(frame));
+ return frame;
+ }
+
+ /* Hmm, if we are here, the path doesn't exist. We need to
+ * create the path, add the value to it. */
+ frame = kvp_frame_set_value_nc (frame, path, value);
+ LEAVE ("new frame=%s", kvp_frame_to_string(frame));
+ return frame;
+}
+
+KvpFrame *
+kvp_frame_add_value(KvpFrame * frame, const char * path, KvpValue *value)
+{
+ value = kvp_value_copy (value);
+ frame = kvp_frame_add_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+ return frame;
+}
+
+void
+kvp_frame_add_gint64(KvpFrame * frame, const char * path, gint64 ival)
+{
+ KvpValue *value;
+ value = kvp_value_new_gint64 (ival);
+ frame = kvp_frame_add_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_double(KvpFrame * frame, const char * path, double dval)
+{
+ KvpValue *value;
+ value = kvp_value_new_double (dval);
+ frame = kvp_frame_add_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_numeric(KvpFrame * frame, const char * path, gnc_numeric nval)
+{
+ KvpValue *value;
+ value = kvp_value_new_gnc_numeric (nval);
+ frame = kvp_frame_add_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_string(KvpFrame * frame, const char * path, const char* str)
+{
+ KvpValue *value;
+ value = kvp_value_new_string (str);
+ frame = kvp_frame_add_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_guid(KvpFrame * frame, const char * path, const GUID *guid)
+{
+ KvpValue *value;
+ value = kvp_value_new_guid (guid);
+ frame = kvp_frame_add_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_timespec(KvpFrame * frame, const char * path, Timespec ts)
+{
+ KvpValue *value;
+ value = kvp_value_new_timespec (ts);
+ frame = kvp_frame_add_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_frame(KvpFrame * frame, const char * path, KvpFrame *fr)
+{
+ KvpValue *value;
+ value = kvp_value_new_frame (fr);
+ frame = kvp_frame_add_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+void
+kvp_frame_add_frame_nc(KvpFrame * frame, const char * path, KvpFrame *fr)
+{
+ KvpValue *value;
+ value = kvp_value_new_frame_nc (fr);
+ frame = kvp_frame_add_value_nc (frame, path, value);
+ if (!frame) kvp_value_delete (value);
+}
+
+/* ============================================================ */
+
+void
+kvp_frame_set_slot(KvpFrame * frame, const char * slot,
+ const KvpValue * value)
+{
+ KvpValue *new_value = NULL;
+
+ if (!frame) return;
+
+ g_return_if_fail (slot && *slot != '\0');
+
+ if(value) new_value = kvp_value_copy(value);
+ kvp_frame_set_slot_destructively(frame, slot, new_value);
+}
+
+void
+kvp_frame_set_slot_nc(KvpFrame * frame, const char * slot,
+ KvpValue * value)
+{
+ if (!frame) return;
+
+ g_return_if_fail (slot && *slot != '\0');
+
+ kvp_frame_set_slot_destructively(frame, slot, value);
+}
+
+KvpValue *
+kvp_frame_get_slot(const KvpFrame * frame, const char * slot)
+{
+ KvpValue *v;
+ if (!frame) return NULL;
+ if (!frame->hash) return NULL; /* Error ... */
+ v = g_hash_table_lookup(frame->hash, slot);
+ return v;
+}
+
+/* ============================================================ */
+
+void
+kvp_frame_set_slot_path (KvpFrame *frame,
+ const KvpValue *new_value,
+ const char *first_key, ...)
+{
+ va_list ap;
+ const char *key;
+
+ if (!frame) return;
+
+ g_return_if_fail (first_key && *first_key != '\0');
+
+ va_start (ap, first_key);
+
+ key = first_key;
+
+ while (TRUE)
+ {
+ KvpValue *value;
+ const char *next_key;
+
+ next_key = va_arg (ap, const char *);
+ if (!next_key)
+ {
+ kvp_frame_set_slot (frame, key, new_value);
+ break;
+ }
+
+ g_return_if_fail (*next_key != '\0');
+
+ value = kvp_frame_get_slot (frame, key);
+ if (!value) {
+ KvpFrame *new_frame = kvp_frame_new ();
+ KvpValue *frame_value = kvp_value_new_frame (new_frame);
+
+ kvp_frame_set_slot_nc (frame, key, frame_value);
+
+ value = kvp_frame_get_slot (frame, key);
+ if (!value) break;
+ }
+
+ frame = kvp_value_get_frame (value);
+ if (!frame) break;
+
+ key = next_key;
+ }
+
+ va_end (ap);
+}
+
+void
+kvp_frame_set_slot_path_gslist (KvpFrame *frame,
+ const KvpValue *new_value,
+ GSList *key_path)
+{
+ if (!frame || !key_path) return;
+
+ while (TRUE)
+ {
+ const char *key = key_path->data;
+ KvpValue *value;
+
+ if (!key)
+ return;
+
+ g_return_if_fail (*key != '\0');
+
+ key_path = key_path->next;
+ if (!key_path)
+ {
+ kvp_frame_set_slot (frame, key, new_value);
+ return;
+ }
+
+ value = kvp_frame_get_slot (frame, key);
+ if (!value)
+ {
+ KvpFrame *new_frame = kvp_frame_new ();
+ KvpValue *frame_value = kvp_value_new_frame (new_frame);
+
+ kvp_frame_set_slot_nc (frame, key, frame_value);
+
+ value = kvp_frame_get_slot (frame, key);
+ if (!value)
+ return;
+ }
+
+ frame = kvp_value_get_frame (value);
+ if (!frame)
+ return;
+ }
+}
+
+/* ============================================================ */
+/* decode url-encoded string, do it in place
+ * + == space
+ * %xx == asci char where xx is hexadecimal ascii value
+ */
+
+static void
+decode (char *enc)
+{
+ char * p, *w;
+
+ /* Loop, convert +'s to blanks */
+ p = strchr (enc, '+');
+ while (p)
+ {
+ *p = ' ';
+ p = strchr (p, '+');
+ }
+
+ p = strchr (enc, '%');
+ w = p;
+
+ while (p)
+ {
+ int ch,cl;
+ p++;
+ ch = *p - 0x30; /* ascii 0 = 0x30 */
+ if (9 < ch) ch -= 0x11 - 10; /* uppercase A = 0x41 */
+ if (16 < ch) ch -= 0x20; /* lowercase a = 0x61 */
+
+ p++;
+ cl = *p - 0x30; /* ascii 0 = 0x30 */
+ if (9 < cl) cl -= 0x11 - 10; /* uppercase A = 0x41 */
+ if (16 < cl) cl -= 0x20; /* lowercase a = 0x61 */
+
+ *w = (char) (ch<<4 | cl);
+
+ do
+ {
+ ++w; ++p;
+ *w = *p;
+ if (0x0 == *p) { p = 0; break; }
+ if ('%' == *p) { break; }
+ } while (*p);
+ }
+}
+
+void
+kvp_frame_add_url_encoding (KvpFrame *frame, const char *enc)
+{
+ char *buff, *p;
+ if (!frame || !enc) return;
+
+ /* Loop over all key-value pairs in the encoded string */
+ buff = g_strdup (enc);
+ p = buff;
+ while (*p)
+ {
+ char *n, *v;
+ n = strchr (p, '&'); /* n = next key-value */
+ if (n) *n = 0x0;
+
+ v = strchr (p, '='); /* v = pointer to value */
+ if (!v) break;
+ *v = 0x0;
+ v ++;
+
+ decode (p);
+ decode (v);
+ kvp_frame_set_slot_nc (frame, p, kvp_value_new_string(v));
+
+ if (!n) break; /* no next key, we are done */
+ p = ++n;
+ }
+
+ g_free(buff);
+}
+
+/* ============================================================ */
+
+
+gint64
+kvp_frame_get_gint64(const KvpFrame *frame, const char *path)
+{
+ char *key = NULL;
+ frame = get_trailer_or_null (frame, path, &key);
+ return kvp_value_get_gint64(kvp_frame_get_slot (frame, key));
+}
+
+double
+kvp_frame_get_double(const KvpFrame *frame, const char *path)
+{
+ char *key = NULL;
+ frame = get_trailer_or_null (frame, path, &key);
+ return kvp_value_get_double(kvp_frame_get_slot (frame, key));
+}
+
+gnc_numeric
+kvp_frame_get_numeric(const KvpFrame *frame, const char *path)
+{
+ char *key = NULL;
+ frame = get_trailer_or_null (frame, path, &key);
+ return kvp_value_get_numeric(kvp_frame_get_slot (frame, key));
+}
+
+char *
+kvp_frame_get_string(const KvpFrame *frame, const char *path)
+{
+ char *key = NULL;
+ frame = get_trailer_or_null (frame, path, &key);
+ return kvp_value_get_string(kvp_frame_get_slot (frame, key));
+}
+
+GUID *
+kvp_frame_get_guid(const KvpFrame *frame, const char *path)
+{
+ char *key = NULL;
+ frame = get_trailer_or_null (frame, path, &key);
+ return kvp_value_get_guid(kvp_frame_get_slot (frame, key));
+}
+
+void *
+kvp_frame_get_binary(const KvpFrame *frame, const char *path,
+ guint64 * size_return)
+{
+ char *key = NULL;
+ frame = get_trailer_or_null (frame, path, &key);
+ return kvp_value_get_binary(kvp_frame_get_slot (frame, key), size_return);
+}
+
+Timespec
+kvp_frame_get_timespec(const KvpFrame *frame, const char *path)
+{
+ char *key = NULL;
+ frame = get_trailer_or_null (frame, path, &key);
+ return kvp_value_get_timespec(kvp_frame_get_slot (frame, key));
+}
+
+KvpFrame *
+kvp_frame_get_frame(const KvpFrame *frame, const char *path)
+{
+ char *key = NULL;
+ frame = get_trailer_or_null (frame, path, &key);
+ return kvp_value_get_frame(kvp_frame_get_slot (frame, key));
+}
+
+KvpValue *
+kvp_frame_get_value(const KvpFrame *frame, const char *path)
+{
+ char *key = NULL;
+ frame = get_trailer_or_null (frame, path, &key);
+ return kvp_frame_get_slot (frame, key);
+}
+
+/* ============================================================ */
+
+KvpFrame *
+kvp_frame_get_frame_gslist (KvpFrame *frame, GSList *key_path)
+{
+ if (!frame) return frame;
+
+ while (key_path)
+ {
+ const char *key = key_path->data;
+
+ if (!key) return frame; /* an unusual but valid exit for this routine. */
+
+ frame = get_or_make (frame, key);
+ if (!frame) return frame; /* this should never happen */
+
+ key_path = key_path->next;
+ }
+ return frame; /* this is the normal exit for this func */
+}
+
+KvpFrame *
+kvp_frame_get_frame_path (KvpFrame *frame, const char *key, ...)
+{
+ va_list ap;
+ if (!frame || !key) return frame;
+
+ va_start (ap, key);
+
+ while (key)
+ {
+ frame = get_or_make (frame, key);
+ if (!frame) break; /* error, should never occur */
+ key = va_arg (ap, const char *);
+ }
+
+ va_end (ap);
+ return frame;
+}
+
+KvpFrame *
+kvp_frame_get_frame_slash (KvpFrame *frame, const char *key_path)
+{
+ char *root;
+ if (!frame || !key_path) return frame;
+
+ root = g_strdup (key_path);
+ frame = kvp_frame_get_frame_slash_trash (frame, root);
+ g_free(root);
+ return frame;
+}
+
+/* ============================================================ */
+
+KvpValue *
+kvp_frame_get_slot_path (KvpFrame *frame,
+ const char *first_key, ...)
+{
+ va_list ap;
+ KvpValue *value;
+ const char *key;
+
+ if (!frame || !first_key) return NULL;
+
+ va_start (ap, first_key);
+
+ key = first_key;
+ value = NULL;
+
+ while (TRUE)
+ {
+ value = kvp_frame_get_slot (frame, key);
+ if (!value) break;
+
+ key = va_arg (ap, const char *);
+ if (!key) break;
+
+ frame = kvp_value_get_frame (value);
+ if (!frame)
+ {
+ value = NULL;
+ break;
+ }
+ }
+
+ va_end (ap);
+
+ return value;
+}
+
+KvpValue *
+kvp_frame_get_slot_path_gslist (KvpFrame *frame,
+ GSList *key_path)
+{
+ if (!frame || !key_path) return NULL;
+
+ while (TRUE)
+ {
+ const char *key = key_path->data;
+ KvpValue *value;
+
+ if (!key) return NULL;
+
+ value = kvp_frame_get_slot (frame, key);
+ if (!value) return NULL;
+
+ key_path = key_path->next;
+ if (!key_path) return value;
+
+ frame = kvp_value_get_frame (value);
+ if (!frame) return NULL;
+ }
+}
+
+/********************************************************************
+ * kvp glist functions
+ ********************************************************************/
+
+void
+kvp_glist_delete(GList * list)
+{
+ GList *node;
+ if(!list) return;
+
+ /* Delete the data in the list */
+ for (node=list; node; node=node->next)
+ {
+ KvpValue *val = node->data;
+ kvp_value_delete(val);
+ }
+
+ /* Free the backbone */
+ g_list_free(list);
+}
+
+GList *
+kvp_glist_copy(const GList * list)
+{
+ GList * retval = NULL;
+ GList * lptr;
+
+ if (!list) return retval;
+
+ /* Duplicate the backbone of the list (this duplicates the POINTERS
+ * to the values; we need to deep-copy the values separately) */
+ retval = g_list_copy((GList *) list);
+
+ /* This step deep-copies the values */
+ for(lptr = retval; lptr; lptr = lptr->next)
+ {
+ lptr->data = kvp_value_copy(lptr->data);
+ }
+
+ return retval;
+}
+
+gint
+kvp_glist_compare(const GList * list1, const GList * list2)
+{
+ const GList *lp1;
+ const GList *lp2;
+
+ if(list1 == list2) return 0;
+
+ /* Nothing is always less than something */
+ if(!list1 && list2) return -1;
+ if(list1 && !list2) return 1;
+
+ lp1 = list1;
+ lp2 = list2;
+ while(lp1 && lp2)
+ {
+ KvpValue *v1 = (KvpValue *) lp1->data;
+ KvpValue *v2 = (KvpValue *) lp2->data;
+ gint vcmp = kvp_value_compare(v1, v2);
+ if(vcmp != 0) return vcmp;
+ lp1 = lp1->next;
+ lp2 = lp2->next;
+ }
+ if(!lp1 && lp2) return -1;
+ if(!lp2 && lp1) return 1;
+ return 0;
+}
+
+/********************************************************************
+ * KvpValue functions
+ ********************************************************************/
+
+KvpValue *
+kvp_value_new_gint64(gint64 value)
+{
+ KvpValue * retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_GINT64;
+ retval->value.int64 = value;
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_double(double value)
+{
+ KvpValue * retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_DOUBLE;
+ retval->value.dbl = value;
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_numeric(gnc_numeric value)
+{
+ KvpValue * retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_NUMERIC;
+ retval->value.numeric = value;
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_string(const char * value)
+{
+ KvpValue * retval;
+ if (!value) return NULL;
+
+ retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_STRING;
+ retval->value.str = g_strdup(value);
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_guid(const GUID * value)
+{
+ KvpValue * retval;
+ if (!value) return NULL;
+
+ retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_GUID;
+ retval->value.guid = g_new0(GUID, 1);
+ memcpy(retval->value.guid, value, sizeof(GUID));
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_timespec(Timespec value)
+{
+ KvpValue * retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_TIMESPEC;
+ retval->value.timespec = value;
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_binary(const void * value, guint64 datasize)
+{
+ KvpValue * retval;
+ if (!value) return NULL;
+
+ retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_BINARY;
+ retval->value.binary.data = g_new0(char, datasize);
+ retval->value.binary.datasize = datasize;
+ memcpy(retval->value.binary.data, value, datasize);
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_binary_nc(void * value, guint64 datasize)
+{
+ KvpValue * retval;
+ if (!value) return NULL;
+
+ retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_BINARY;
+ retval->value.binary.data = value;
+ retval->value.binary.datasize = datasize;
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_glist(const GList * value)
+{
+ KvpValue * retval;
+ if (!value) return NULL;
+
+ retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_GLIST;
+ retval->value.list = kvp_glist_copy(value);
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_glist_nc(GList * value)
+{
+ KvpValue * retval;
+ if (!value) return NULL;
+
+ retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_GLIST;
+ retval->value.list = value;
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_frame(const KvpFrame * value)
+{
+ KvpValue * retval;
+ if (!value) return NULL;
+
+ retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_FRAME;
+ retval->value.frame = kvp_frame_copy(value);
+ return retval;
+}
+
+KvpValue *
+kvp_value_new_frame_nc(KvpFrame * value)
+{
+ KvpValue * retval;
+ if (!value) return NULL;
+
+ retval = g_new0(KvpValue, 1);
+ retval->type = KVP_TYPE_FRAME;
+ retval->value.frame = value;
+ return retval;
+}
+
+void
+kvp_value_delete(KvpValue * value)
+{
+ if(!value) return;
+
+ switch(value->type)
+ {
+ case KVP_TYPE_STRING:
+ g_free(value->value.str);
+ break;
+ case KVP_TYPE_GUID:
+ g_free(value->value.guid);
+ break;
+ case KVP_TYPE_BINARY:
+ g_free(value->value.binary.data);
+ break;
+ case KVP_TYPE_GLIST:
+ kvp_glist_delete(value->value.list);
+ break;
+ case KVP_TYPE_FRAME:
+ kvp_frame_delete(value->value.frame);
+ break;
+
+ case KVP_TYPE_GINT64:
+ case KVP_TYPE_DOUBLE:
+ case KVP_TYPE_NUMERIC:
+ default:
+ break;
+ }
+ g_free(value);
+}
+
+KvpValueType
+kvp_value_get_type(const KvpValue * value)
+{
+ if (!value) return -1;
+ return value->type;
+}
+
+gint64
+kvp_value_get_gint64(const KvpValue * value)
+{
+ if (!value) return 0;
+ if(value->type == KVP_TYPE_GINT64) {
+ return value->value.int64;
+ }
+ else {
+ return 0;
+ }
+}
+
+double
+kvp_value_get_double(const KvpValue * value)
+{
+ if (!value) return 0.0;
+ if(value->type == KVP_TYPE_DOUBLE) {
+ return value->value.dbl;
+ }
+ else {
+ return 0.0;
+ }
+}
+
+gnc_numeric
+kvp_value_get_numeric(const KvpValue * value)
+{
+ if (!value) return gnc_numeric_zero ();
+ if(value->type == KVP_TYPE_NUMERIC) {
+ return value->value.numeric;
+ }
+ else {
+ return gnc_numeric_zero ();
+ }
+}
+
+char *
+kvp_value_get_string(const KvpValue * value)
+{
+ if (!value) return NULL;
+ if(value->type == KVP_TYPE_STRING) {
+ return value->value.str;
+ }
+ else {
+ return NULL;
+ }
+}
+
+GUID *
+kvp_value_get_guid(const KvpValue * value)
+{
+ if (!value) return NULL;
+ if(value->type == KVP_TYPE_GUID) {
+ return value->value.guid;
+ }
+ else {
+ return NULL;
+ }
+}
+
+Timespec
+kvp_value_get_timespec(const KvpValue * value)
+{
+ Timespec ts; ts.tv_sec = 0; ts.tv_nsec = 0;
+ if (!value) return ts;
+ if (value->type == KVP_TYPE_TIMESPEC)
+ return value->value.timespec;
+ else
+ return ts;
+}
+
+void *
+kvp_value_get_binary(const KvpValue * value, guint64 * size_return)
+{
+ if (!value)
+ {
+ if (size_return)
+ *size_return = 0;
+ return NULL;
+ }
+
+ if(value->type == KVP_TYPE_BINARY) {
+ if (size_return)
+ *size_return = value->value.binary.datasize;
+ return value->value.binary.data;
+ }
+ else {
+ if (size_return)
+ *size_return = 0;
+ return NULL;
+ }
+}
+
+GList *
+kvp_value_get_glist(const KvpValue * value)
+{
+ if (!value) return NULL;
+ if(value->type == KVP_TYPE_GLIST) {
+ return value->value.list;
+ }
+ else {
+ return NULL;
+ }
+}
+
+KvpFrame *
+kvp_value_get_frame(const KvpValue * value)
+{
+ if (!value) return NULL;
+ if(value->type == KVP_TYPE_FRAME) {
+ return value->value.frame;
+ }
+ else {
+ return NULL;
+ }
+}
+
+KvpFrame *
+kvp_value_replace_frame_nc(KvpValue *value, KvpFrame * newframe)
+{
+ KvpFrame *oldframe;
+ if (!value) return NULL;
+ if (KVP_TYPE_FRAME != value->type) return NULL;
+
+ oldframe = value->value.frame;
+ value->value.frame = newframe;
+ return oldframe;
+}
+
+GList *
+kvp_value_replace_glist_nc(KvpValue * value, GList *newlist)
+{
+ GList *oldlist;
+ if (!value) return NULL;
+ if (KVP_TYPE_GLIST != value->type) return NULL;
+
+ oldlist = value->value.list;
+ value->value.list = newlist;
+ return oldlist;
+}
+
+/* manipulators */
+
+KvpValue *
+kvp_value_copy(const KvpValue * value)
+{
+ if(!value) return NULL;
+
+ switch(value->type) {
+ case KVP_TYPE_GINT64:
+ return kvp_value_new_gint64(value->value.int64);
+ break;
+ case KVP_TYPE_DOUBLE:
+ return kvp_value_new_double(value->value.dbl);
+ break;
+ case KVP_TYPE_NUMERIC:
+ return kvp_value_new_gnc_numeric(value->value.numeric);
+ break;
+ case KVP_TYPE_STRING:
+ return kvp_value_new_string(value->value.str);
+ break;
+ case KVP_TYPE_GUID:
+ return kvp_value_new_guid(value->value.guid);
+ break;
+ case KVP_TYPE_TIMESPEC:
+ return kvp_value_new_timespec(value->value.timespec);
+ break;
+ case KVP_TYPE_BINARY:
+ return kvp_value_new_binary(value->value.binary.data,
+ value->value.binary.datasize);
+ break;
+ case KVP_TYPE_GLIST:
+ return kvp_value_new_glist(value->value.list);
+ break;
+ case KVP_TYPE_FRAME:
+ return kvp_value_new_frame(value->value.frame);
+ break;
+ }
+ return NULL;
+}
+
+void
+kvp_frame_for_each_slot(KvpFrame *f,
+ void (*proc)(const char *key,
+ KvpValue *value,
+ gpointer data),
+ gpointer data)
+{
+ if(!f) return;
+ if(!proc) return;
+ if(!(f->hash)) return;
+
+ g_hash_table_foreach(f->hash, (GHFunc) proc, data);
+}
+
+gint
+double_compare(double d1, double d2)
+{
+ if(isnan(d1) && isnan(d2)) return 0;
+ if(d1 < d2) return -1;
+ if(d1 > d2) return 1;
+ return 0;
+}
+
+gint
+kvp_value_compare(const KvpValue * kva, const KvpValue * kvb)
+{
+ if(kva == kvb) return 0;
+ /* nothing is always less than something */
+ if(!kva && kvb) return -1;
+ if(kva && !kvb) return 1;
+
+ if(kva->type < kvb->type) return -1;
+ if(kva->type > kvb->type) return 1;
+
+ switch(kva->type) {
+ case KVP_TYPE_GINT64:
+ if(kva->value.int64 < kvb->value.int64) return -1;
+ if(kva->value.int64 > kvb->value.int64) return 1;
+ return 0;
+ break;
+ case KVP_TYPE_DOUBLE:
+ return double_compare(kva->value.dbl, kvb->value.dbl);
+ break;
+ case KVP_TYPE_NUMERIC:
+ return gnc_numeric_compare (kva->value.numeric, kvb->value.numeric);
+ break;
+ case KVP_TYPE_STRING:
+ return strcmp(kva->value.str, kvb->value.str);
+ break;
+ case KVP_TYPE_GUID:
+ return guid_compare(kva->value.guid, kvb->value.guid);
+ break;
+ case KVP_TYPE_TIMESPEC:
+ return timespec_cmp(&(kva->value.timespec), &(kvb->value.timespec));
+ break;
+ case KVP_TYPE_BINARY:
+ /* I don't know that this is a good compare. Ab is bigger than Acef.
+ But I'm not sure that actually matters here. */
+ if(kva->value.binary.datasize < kvb->value.binary.datasize) return -1;
+ if(kva->value.binary.datasize > kvb->value.binary.datasize) return 1;
+ return memcmp(kva->value.binary.data,
+ kvb->value.binary.data,
+ kva->value.binary.datasize);
+ break;
+ case KVP_TYPE_GLIST:
+ return kvp_glist_compare(kva->value.list, kvb->value.list);
+ break;
+ case KVP_TYPE_FRAME:
+ return kvp_frame_compare(kva->value.frame, kvb->value.frame);
+ break;
+ }
+ PERR ("reached unreachable code.");
+ return FALSE;
+}
+
+typedef struct {
+ gint compare;
+ KvpFrame *other_frame;
+} kvp_frame_cmp_status;
+
+static void
+kvp_frame_compare_helper(const char *key, KvpValue * val, gpointer data)
+{
+ kvp_frame_cmp_status *status = (kvp_frame_cmp_status *) data;
+ if(status->compare == 0) {
+ KvpFrame *other_frame = status->other_frame;
+ KvpValue *other_val = kvp_frame_get_slot(other_frame, key);
+
+ if(other_val) {
+ status->compare = kvp_value_compare(val, other_val);
+ } else {
+ status->compare = 1;
+ }
+ }
+}
+
+gint
+kvp_frame_compare(const KvpFrame *fa, const KvpFrame *fb)
+{
+ kvp_frame_cmp_status status;
+
+ if(fa == fb) return 0;
+ /* nothing is always less than something */
+ if(!fa && fb) return -1;
+ if(fa && !fb) return 1;
+
+ /* nothing is always less than something */
+ if(!fa->hash && fb->hash) return -1;
+ if(fa->hash && !fb->hash) return 1;
+
+ status.compare = 0;
+ status.other_frame = (KvpFrame *) fb;
+
+ kvp_frame_for_each_slot((KvpFrame *) fa, kvp_frame_compare_helper, &status);
+
+ if (status.compare != 0)
+ return status.compare;
+
+ status.other_frame = (KvpFrame *) fa;
+
+ kvp_frame_for_each_slot((KvpFrame *) fb, kvp_frame_compare_helper, &status);
+
+ return(-status.compare);
+}
+
+gchar*
+binary_to_string(const void *data, guint32 size)
+{
+ GString *output;
+ guint32 i;
+ guchar *data_str = (guchar*)data;
+
+ output = g_string_sized_new(size * sizeof(char));
+
+ for(i = 0; i < size; i++)
+ {
+ g_string_append_printf(output, "%02x", (unsigned int) (data_str[i]));
+ }
+
+ return output->str;
+}
+
+gchar*
+kvp_value_glist_to_string(const GList *list)
+{
+ gchar *tmp1;
+ gchar *tmp2;
+ const GList *cursor;
+
+ tmp1 = g_strdup_printf("[ ");
+
+ for(cursor = list; cursor; cursor = cursor->next)
+ {
+ gchar *tmp3;
+
+ tmp3 = kvp_value_to_string((KvpValue *)cursor->data);
+ tmp2 = g_strdup_printf("%s %s,", tmp1, tmp3 ? tmp3 : "");
+ g_free(tmp1);
+ g_free(tmp3);
+ tmp1 = tmp2;
+ }
+
+ tmp2 = g_strdup_printf("%s ]", tmp1);
+ g_free(tmp1);
+
+ return tmp2;
+}
+
+gchar*
+kvp_value_to_bare_string(const KvpValue *val)
+{
+ gchar *tmp1;
+ gchar *tmp2;
+ const gchar *ctmp;
+
+ g_return_val_if_fail(val, NULL);
+
+ switch(kvp_value_get_type(val))
+ {
+ case KVP_TYPE_GINT64:
+ return g_strdup_printf("%" G_GINT64_FORMAT, kvp_value_get_gint64(val));
+ break;
+
+ case KVP_TYPE_DOUBLE:
+ return g_strdup_printf("(%g)", kvp_value_get_double(val));
+ break;
+
+ case KVP_TYPE_NUMERIC:
+ tmp1 = gnc_numeric_to_string(kvp_value_get_numeric(val));
+ tmp2 = g_strdup_printf("%s", tmp1 ? tmp1 : "");
+ g_free(tmp1);
+ return tmp2;
+ break;
+
+ case KVP_TYPE_STRING:
+ tmp1 = kvp_value_get_string (val);
+ return g_strdup_printf("%s", tmp1 ? tmp1 : "");
+ break;
+
+ case KVP_TYPE_GUID:
+ ctmp = guid_to_string(kvp_value_get_guid(val));
+ tmp2 = g_strdup_printf("%s", ctmp ? ctmp : "");
+ return tmp2;
+ break;
+
+ case KVP_TYPE_TIMESPEC:
+ tmp1 = g_new0 (char, 40);
+ gnc_timespec_to_iso8601_buff (kvp_value_get_timespec (val), tmp1);
+ tmp2 = g_strdup_printf("%s", tmp1);
+ g_free(tmp1);
+ return tmp2;
+ break;
+
+ case KVP_TYPE_BINARY:
+ {
+ guint64 len;
+ void *data;
+ data = kvp_value_get_binary(val, &len);
+ tmp1 = binary_to_string(data, len);
+ return g_strdup_printf("%s", tmp1 ? tmp1 : "");
+ }
+ break;
+
+ case KVP_TYPE_GLIST:
+ tmp1 = kvp_value_glist_to_string(kvp_value_get_glist(val));
+ tmp2 = g_strdup_printf("%s", tmp1 ? tmp1 : "");
+ g_free(tmp1);
+ return tmp2;
+ break;
+
+ case KVP_TYPE_FRAME:
+ tmp1 = kvp_frame_to_string(kvp_value_get_frame(val));
+ tmp2 = g_strdup_printf("%s", tmp1 ? tmp1 : "");
+ g_free(tmp1);
+ return tmp2;
+ break;
+
+ default:
+ return g_strdup_printf(" ");
+ break;
+ }
+}
+
+gchar*
+kvp_value_to_string(const KvpValue *val)
+{
+ gchar *tmp1;
+ gchar *tmp2;
+ const gchar *ctmp;
+
+ g_return_val_if_fail(val, NULL);
+
+ switch(kvp_value_get_type(val))
+ {
+ case KVP_TYPE_GINT64:
+ return g_strdup_printf("KVP_VALUE_GINT64(%" G_GINT64_FORMAT ")",
+ kvp_value_get_gint64(val));
+ break;
+
+ case KVP_TYPE_DOUBLE:
+ return g_strdup_printf("KVP_VALUE_DOUBLE(%g)",
+ kvp_value_get_double(val));
+ break;
+
+ case KVP_TYPE_NUMERIC:
+ tmp1 = gnc_numeric_to_string(kvp_value_get_numeric(val));
+ tmp2 = g_strdup_printf("KVP_VALUE_NUMERIC(%s)", tmp1 ? tmp1 : "");
+ g_free(tmp1);
+ return tmp2;
+ break;
+
+ case KVP_TYPE_STRING:
+ tmp1 = kvp_value_get_string (val);
+ return g_strdup_printf("KVP_VALUE_STRING(%s)", tmp1 ? tmp1 : "");
+ break;
+
+ case KVP_TYPE_GUID:
+ ctmp = guid_to_string(kvp_value_get_guid(val));
+ tmp2 = g_strdup_printf("KVP_VALUE_GUID(%s)", ctmp ? ctmp : "");
+ return tmp2;
+ break;
+
+ case KVP_TYPE_TIMESPEC:
+ tmp1 = g_new0 (char, 40);
+ gnc_timespec_to_iso8601_buff (kvp_value_get_timespec (val), tmp1);
+ tmp2 = g_strdup_printf("KVP_VALUE_TIMESPEC(%s)", tmp1);
+ g_free(tmp1);
+ return tmp2;
+ break;
+
+ case KVP_TYPE_BINARY:
+ {
+ guint64 len;
+ void *data;
+ data = kvp_value_get_binary(val, &len);
+ tmp1 = binary_to_string(data, len);
+ return g_strdup_printf("KVP_VALUE_BINARY(%s)", tmp1 ? tmp1 : "");
+ }
+ break;
+
+ case KVP_TYPE_GLIST:
+ tmp1 = kvp_value_glist_to_string(kvp_value_get_glist(val));
+ tmp2 = g_strdup_printf("KVP_VALUE_GLIST(%s)", tmp1 ? tmp1 : "");
+ g_free(tmp1);
+ return tmp2;
+ break;
+
+ case KVP_TYPE_FRAME:
+ tmp1 = kvp_frame_to_string(kvp_value_get_frame(val));
+ tmp2 = g_strdup_printf("KVP_VALUE_FRAME(%s)", tmp1 ? tmp1 : "");
+ g_free(tmp1);
+ return tmp2;
+ break;
+
+ default:
+ return g_strdup_printf(" ");
+ break;
+ }
+}
+
+static void
+kvp_frame_to_string_helper(gpointer key, gpointer value, gpointer data)
+{
+ gchar *tmp_val;
+ gchar **str = (gchar**)data;
+ gchar *old_data = *str;
+
+ tmp_val = kvp_value_to_string((KvpValue *)value);
+
+ *str = g_strdup_printf("%s %s => %s,\n",
+ *str ? *str : "",
+ key ? (char *) key : "",
+ tmp_val ? tmp_val : "");
+
+ g_free(old_data);
+ g_free(tmp_val);
+}
+
+gchar*
+kvp_frame_to_string(const KvpFrame *frame)
+{
+ gchar *tmp1;
+
+ g_return_val_if_fail (frame != NULL, NULL);
+
+ tmp1 = g_strdup_printf("{\n");
+
+ if (frame->hash)
+ g_hash_table_foreach(frame->hash, kvp_frame_to_string_helper, &tmp1);
+
+ {
+ gchar *tmp2;
+ tmp2 = g_strdup_printf("%s}\n", tmp1);
+ g_free(tmp1);
+ tmp1 = tmp2;
+ }
+
+ return tmp1;
+}
+
+GHashTable*
+kvp_frame_get_hash(const KvpFrame *frame)
+{
+ g_return_val_if_fail (frame != NULL, NULL);
+ return frame->hash;
+}
+
+/* ========================== END OF FILE ======================= */
Added: gnucash/trunk/lib/libqof/qof/kvp_frame.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/kvp_frame.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/kvp_frame.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,694 @@
+/********************************************************************\
+ * kvp_frame.h -- Implements a key-value frame system *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup KVP
+
+ * A KvpFrame is a set of associations between character strings
+ * (keys) and KvpValue structures. A KvpValue is a union with
+ * possible types enumerated in the KvpValueType enum, and includes,
+ * among other things, ints, doubles, strings, guid's, lists, time
+ * and numeric values. KvpValues may also be other frames, so
+ * KVP is inherently hierarchical.
+ *
+ * Values are stored in a 'slot' associated with a key.
+ * Pointers passed as arguments into set_slot and get_slot are the
+ * responsibility of the caller. Pointers returned by get_slot are
+ * owned by the kvp_frame. Make copies as needed.
+ *
+ * A 'path' is a sequence of keys that can be followed to a value.
+ * Paths may be specified as varargs (variable number of arguments
+ * to a subrutine, NULL-terminated), as a GSList, or as a standard
+ * URL-like path name. The later is parsed and treated in the same
+ * way as file paths would be: / separates keys, /./ is treated as /
+ * and /../ means backup one level. Repeated slashes are treated
+ * as one slash.
+ *
+ * Note that although, in principle, keys may contain the / and . and
+ * .. characters, doing so may lead to confusion, and will make
+ * path-string parsing routines fail. In other words, don't use
+ * a key such as 'some/key' or 'some/./other/../key' because you
+ * may get unexpected results.
+ *
+ * To set a value into a frame, you will want to use one of the
+ * kvp_frame_set_xxx() routines. Most of the other routines provide
+ * only low-level access that you probably shouldn't use.
+
+@{
+*/
+/** @file kvp_frame.h
+ @brief A key-value frame system
+ @author Copyright (C) 2000 Bill Gribble
+ @author Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+*/
+
+#ifndef KVP_FRAME_H
+#define KVP_FRAME_H
+
+#include <glib.h>
+
+#include "gnc-date.h"
+#include "gnc-numeric.h"
+#include "guid.h"
+
+#define QOF_MOD_KVP "qof-kvp"
+
+/** Opaque frame structure */
+typedef struct _KvpFrame KvpFrame;
+
+/** A KvpValue is a union with possible types enumerated in the
+ * KvpValueType enum. */
+typedef struct _KvpValue KvpValue;
+
+/** \brief possible types in the union KvpValue
+ * \todo : People have asked for boolean values,
+ * e.g. in xaccAccountSetAutoInterestXfer
+ *
+ * \todo In the long run, this should be synchronized with the
+ * core QOF types, which in turn should be synced to the g_types
+ * in GLib. Unfortunately, this requires writing a pile of code
+ * to handle all of the different cases.
+ * An alternative might be to make kvp values inherit from the
+ * core g_types (i.e. add new core g_types) ??
+ */
+typedef enum {
+ KVP_TYPE_GINT64=1, /**< QOF_TYPE_INT64 gint64 */
+ KVP_TYPE_DOUBLE, /**< QOF_TYPE_DOUBLE gdouble */
+ KVP_TYPE_NUMERIC, /**< QOF_TYPE_NUMERIC */
+ KVP_TYPE_STRING, /**< QOF_TYPE_STRING gchar* */
+ KVP_TYPE_GUID, /**< QOF_TYPE_GUID */
+ KVP_TYPE_TIMESPEC, /**< QOF_TYPE_DATE */
+ KVP_TYPE_BINARY, /**< no QOF equivalent. */
+ KVP_TYPE_GLIST, /**< no QOF equivalent. */
+ KVP_TYPE_FRAME /**< no QOF equivalent. */
+} KvpValueType;
+
+/** \deprecated Deprecated backwards compat tokens
+
+do \b not use these in new code.
+*/
+#define kvp_frame KvpFrame
+#define kvp_value KvpValue
+#define kvp_value_t KvpValueType
+
+/** @name KvpFrame Constructors
+ @{
+*/
+
+/** Return a new empty instance of KvpFrame */
+KvpFrame * kvp_frame_new(void);
+
+/** Perform a deep (recursive) delete of the frame and any subframes.
+
+ * kvp_frame_delete and kvp_value_delete are deep (recursive) deletes.
+ * kvp_frame_copy and kvp_value_copy are deep value copies.
+ */
+void kvp_frame_delete(KvpFrame * frame);
+
+/** Perform a deep (recursive) value copy, copying the fraame,
+ * subframes, and the values as well. */
+KvpFrame * kvp_frame_copy(const KvpFrame * frame);
+
+/** Return TRUE if the KvpFrame is empty */
+gboolean kvp_frame_is_empty(KvpFrame * frame);
+
+/** @} */
+
+/** @name KvpFrame Basic Value Storing
+@{
+*/
+
+/** store the value of the
+ * gint64 at the indicated path. If not all frame components of
+ * the path exist, they are created.
+ *
+ */
+void kvp_frame_set_gint64(KvpFrame * frame, const char * path, gint64 ival);
+/**
+ * store the value of the
+ * double at the indicated path. If not all frame components of
+ * the path exist, they are created.
+*/
+void kvp_frame_set_double(KvpFrame * frame, const char * path, double dval);
+
+/** \deprecated
+
+Use kvp_frame_set_numeric instead of kvp_frame_set_gnc_numeric
+*/
+#define kvp_frame_set_gnc_numeric kvp_frame_set_numeric
+/** store the value of the
+ * gnc_numeric at the indicated path.
+ * If not all frame components of
+ * the path exist, they are created.
+ */
+void kvp_frame_set_numeric(KvpFrame * frame, const char * path, gnc_numeric nval);
+/** store the value of the
+ * Timespec at the indicated path.
+ * If not all frame components of
+ * the path exist, they are created.
+ */
+void kvp_frame_set_timespec(KvpFrame * frame, const char * path, Timespec ts);
+
+/** \deprecated
+
+Use kvp_frame_set_string instead of kvp_frame_set_str
+*/
+#define kvp_frame_set_str kvp_frame_set_string
+
+/** \brief Store a copy of the string at the indicated path.
+
+ * If not all frame components of the path
+ * exist, they are created. If there was another string previously
+ * stored at that path, the old copy is deleted.
+ *
+ * Similarly, the set_guid and set_frame will make copies and
+ * store those. Old copies, if any, are deleted.
+ *
+ * The kvp_frame_set_frame_nc() routine works as above, but does
+ * *NOT* copy the frame.
+ */
+void kvp_frame_set_string(KvpFrame * frame, const char * path, const char* str);
+void kvp_frame_set_guid(KvpFrame * frame, const char * path, const GUID *guid);
+
+void kvp_frame_set_frame(KvpFrame *frame, const char *path, KvpFrame *chld);
+void kvp_frame_set_frame_nc(KvpFrame *frame, const char *path, KvpFrame *chld);
+
+/** The kvp_frame_set_value() routine copies the value into the frame,
+ * at the location 'path'. If the path contains slashes '/', these
+ * are assumed to represent a sequence of keys. The returned value
+ * is a pointer to the actual frame into which the value was inserted;
+ * it is NULL if the frame couldn't be found (and thus the value wasn't
+ * inserted). The old value at this location, if any, is destroyed.
+ *
+ * Pointers passed as arguments into this routine are the responsibility
+ * of the caller; the pointers are *not* taken over or managed.
+ */
+KvpFrame * kvp_frame_set_value(KvpFrame * frame,
+ const char * path, const KvpValue * value);
+/**
+ * The kvp_frame_set_value_nc() routine puts the value (without copying
+ * it) into the frame, putting it at the location 'path'. If the path
+ * contains slashes '/', these are assumed to represent a sequence of keys.
+ * The returned value is a pointer to the actual frame into which the value
+ * was inserted; it is NULL if the frame couldn't be found (and thus the
+ * value wasn't inserted). The old value at this location, if any,
+ * is destroyed.
+ *
+ * This routine is handy for avoiding excess memory allocations & frees.
+ * Note that because the KvpValue was grabbed, you can't just delete
+ * unless you remove the key as well (or unless you replace the value).
+ */
+KvpFrame * kvp_frame_set_value_nc(KvpFrame * frame,
+ const char * path, KvpValue * value);
+
+/** The kvp_frame_replace_value_nc() routine places the new value
+ * at the indicated path. It returns the old value, if any.
+ * It returns NULL if there was an error, or if there was no
+ * old value. If the path doesn't exist, it is created, unless
+ * new_value is NULL. Passing in a NULL new_value has the
+ * effect of deleting the trailing slot (i.e. the trailing path
+ * element).
+ */
+KvpValue * kvp_frame_replace_value_nc (KvpFrame * frame, const char * slot,
+ KvpValue * new_value);
+/** @} */
+/** @name KvpFrame URL handling
+ @{
+*/
+/** The kvp_frame_add_url_encoding() routine will parse the
+ * value string, assuming it to be URL-encoded in the standard way,
+ * turning it into a set of key-value pairs, and adding those to the
+ * indicated frame. URL-encoded strings are the things that are
+ * returned by web browsers when a form is filled out. For example,
+ * 'start-date=June&end-date=November' consists of two keys,
+ * 'start-date' and 'end-date', which have the values 'June' and
+ * 'November', respectively. This routine also handles % encoding.
+ *
+ * This routine treats all values as strings; it does *not* attempt
+ * to perform any type-conversion.
+ * */
+void kvp_frame_add_url_encoding (KvpFrame *frame, const char *enc);
+/** @} */
+
+/** @name KvpFrame Glist Bag Storing
+ @{
+*/
+
+/** The kvp_frame_add_gint64() routine will add the value of the
+ * gint64 to the glist bag of values at the indicated path.
+ * If not all frame components of the path exist, they are
+ * created. If the value previously stored at this path was
+ * not a glist bag, then a bag will be formed there, the old
+ * value placed in the bag, and the new value added to the bag.
+ *
+ * Similarly, the add_double, add_numeric, and add_timespec
+ * routines perform the same function, for each of the respective
+ * types.
+ */
+void kvp_frame_add_gint64(KvpFrame * frame, const char * path, gint64 ival);
+void kvp_frame_add_double(KvpFrame * frame, const char * path, double dval);
+/** \deprecated
+
+Use kvp_frame_add_numeric instead of kvp_frame_add_gnc_numeric
+*/
+#define kvp_frame_add_gnc_numeric kvp_frame_add_numeric
+
+void kvp_frame_add_numeric(KvpFrame * frame, const char * path, gnc_numeric nval);
+void kvp_frame_add_timespec(KvpFrame * frame, const char * path, Timespec ts);
+
+/** \deprecated
+
+Use kvp_frame_add_string instead of kvp_frame_add_str
+*/
+#define kvp_frame_add_str kvp_frame_add_string
+
+/** \brief Copy of the string to the glist bag at the indicated path.
+
+ * If not all frame components
+ * of the path exist, they are created. If there was another
+ * item previously stored at that path, then the path is converted
+ * to a bag, and the old value, along with the new value, is added
+ * to the bag.
+ *
+ * Similarly, the add_guid and add_frame will make copies and
+ * add those.
+ *
+ * The kvp_frame_add_frame_nc() routine works as above, but does
+ * *NOT* copy the frame.
+ */
+void kvp_frame_add_string(KvpFrame * frame, const char * path, const char* str);
+void kvp_frame_add_guid(KvpFrame * frame, const char * path, const GUID *guid);
+
+void kvp_frame_add_frame(KvpFrame *frame, const char *path, KvpFrame *chld);
+void kvp_frame_add_frame_nc(KvpFrame *frame, const char *path, KvpFrame *chld);
+
+/* The kvp_frame_add_value() routine will add a copy of the value
+ * to the glist bag at the indicated path. If not all frame components
+ * of the path exist, they are created. If there was another
+ * item previously stored at that path, then the path is converted
+ * to a bag, and the old value, along with the new value, is added
+ * to the bag. This routine returns the pointer to the last frame
+ * (the actual frame to which the value was added), or NULL if there
+ * was an error of any sort (typically, a parse error in the path).
+ *
+ * The *_nc() routine is analogous, except that it doesn't copy the
+ * value.
+ */
+KvpFrame * kvp_frame_add_value(KvpFrame * frame, const char * path, KvpValue *value);
+KvpFrame * kvp_frame_add_value_nc(KvpFrame * frame, const char * path, KvpValue *value);
+
+
+/** @} */
+
+/** @name KvpFrame Value Fetching
+
+ Value accessors. These all take a unix-style slash-separated
+ path as an argument, and return the value stored at that location.
+ If the object at the end of that path is not of the type that was
+ asked for, then a NULL or a zero is returned. So, for example,
+ asking for a string when the path stored an int will return a NULL.
+ In some future date, this may be changed to a looser type system,
+ such as perl's automatic re-typing (e.g. an integer value might be
+ converted to a printed string representing that value).
+
+ If any part of the path does not exist, then NULL or zero will be
+ returned.
+
+ The values returned for GUID, binary, GList, KvpFrame and string
+ are "non-copying" -- the returned item is the actual item stored.
+ Do not delete this item unless you take the required care to avoid
+ possible bad pointer derefrences (i.e. core dumps). Also, be
+ careful hanging on to those references if you are also storing
+ at the same path names: the referenced item will be freed during
+ the store.
+
+ That is, if you get a string value (or guid, binary or frame),
+ and then store something else at that path, the string that you've
+ gotten will be freed during the store (internally, by the set_*()
+ routines), and you will be left hanging onto an invalid pointer.
+@{
+*/
+
+gint64 kvp_frame_get_gint64(const KvpFrame *frame, const char *path);
+double kvp_frame_get_double(const KvpFrame *frame, const char *path);
+gnc_numeric kvp_frame_get_numeric(const KvpFrame *frame, const char *path);
+char * kvp_frame_get_string(const KvpFrame *frame, const char *path);
+GUID * kvp_frame_get_guid(const KvpFrame *frame, const char *path);
+void * kvp_frame_get_binary(const KvpFrame *frame, const char *path,
+ guint64 * size_return);
+Timespec kvp_frame_get_timespec(const KvpFrame *frame, const char *path);
+KvpValue * kvp_frame_get_value(const KvpFrame *frame, const char *path);
+
+/** Value accessor. Takes a unix-style slash-separated path as an
+ * argument, and return the KvpFrame stored at that location. If the
+ * KvpFrame does not exist, then a NULL is returned.
+ *
+ * @note The semantics here have changed: In gnucash-1.8, if the
+ * KvpFrame did not exist, this function automatically created one
+ * and returned it. However, now this function will return NULL in
+ * this case and the caller has to create a KvpFrame on his own. The
+ * old functionality is now implemented by
+ * kvp_frame_get_frame_path(). This happened on 2003-09-14, revision
+ * 1.31. FIXME: Is it really a good idea to change the semantics of
+ * an existing function and move the old semantics to a new
+ * function??! It would save us a lot of trouble if the new semantics
+ * would have been available in a new function!
+ *
+ * @return The KvpFrame at the specified path, or NULL if it doesn't
+ * exist.
+*/
+KvpFrame * kvp_frame_get_frame(const KvpFrame *frame, const char *path);
+
+/** This routine returns the last frame of the path.
+ * If the frame path doesn't exist, it is created.
+ * Note that this is *VERY DIFFERENT FROM* like kvp_frame_get_frame()
+ *
+ * @note The semantics of this function implemented the gnucash-1.8
+ * behaviour of kvp_frame_get_frame: In gnucash-1.8, if the KvpFrame
+ * did not exist, kvp_frame_get_frame automatically created one and
+ * returned it. However, now that one will return NULL in this case
+ * and the caller has to create a KvpFrame on his own. The old
+ * functionality is implemented by this
+ * kvp_frame_get_frame_path(). This happened on 2003-09-14, revision
+ * 1.31.
+ */
+KvpFrame * kvp_frame_get_frame_path (KvpFrame *frame, const char *,...);
+
+/** This routine returns the last frame of the path.
+ * If the frame path doesn't exist, it is created.
+ * Note that this is *VERY DIFFERENT FROM* kvp_frame_get_frame()
+ */
+KvpFrame * kvp_frame_get_frame_gslist (KvpFrame *frame,
+ GSList *key_path);
+
+/** This routine returns the last frame of the path.
+ * If the frame path doesn't exist, it is created.
+ * Note that this is *VERY DIFFERENT FROM* kvp_frame_get_frame()
+ *
+ * The kvp_frame_get_frame_slash() routine takes a single string
+ * where the keys are separated by slashes; thus, for example:
+ * /this/is/a/valid/path and///so//is////this/
+ * Multiple slashes are compresed. Leading slash is optional.
+ * The pointers . and .. are *not* currently followed/obeyed.
+ * (This is a bug that needs fixing).
+ */
+KvpFrame * kvp_frame_get_frame_slash (KvpFrame *frame,
+ const char *path);
+
+/** @} */
+/** @name KvpFrame KvpValue low-level storing routines.
+
+You probably shouldn't be using these low-level routines
+
+ All of the kvp_frame_set_slot_*() routines set the slot values
+ "destructively", in that if there was an old value there, that
+ old value is destroyed (and the memory freed). Thus, one
+ should not hang on to value pointers, as these will get
+ trashed if set_slot is called on the corresponding key.
+
+ If you want the old value, use kvp_frame_replace_slot().
+ @{
+*/
+
+/** The kvp_frame_replace_slot_nc() routine places the new value into
+ * the indicated frame, for the given key. It returns the old
+ * value, if any. It returns NULL if the slot doesn't exist,
+ * if there was some other an error, or if there was no old value.
+ * Passing in a NULL new_value has the effect of deleting that
+ * slot.
+ */
+KvpValue * kvp_frame_replace_slot_nc (KvpFrame * frame, const char * slot,
+ KvpValue * new_value);
+
+
+/** The kvp_frame_set_slot() routine copies the value into the frame,
+ * associating it with a copy of 'key'. Pointers passed as arguments
+ * into kvp_frame_set_slot are the responsibility of the caller;
+ * the pointers are *not* taken over or managed. The old value at
+ * this location, if any, is destroyed.
+ */
+void kvp_frame_set_slot(KvpFrame * frame,
+ const char * key, const KvpValue * value);
+/**
+ * The kvp_frame_set_slot_nc() routine puts the value (without copying
+ * it) into the frame, associating it with a copy of 'key'. This
+ * routine is handy for avoiding excess memory allocations & frees.
+ * Note that because the KvpValue was grabbed, you can't just delete
+ * unless you remove the key as well (or unless you replace the value).
+ * The old value at this location, if any, is destroyed.
+ */
+void kvp_frame_set_slot_nc(KvpFrame * frame,
+ const char * key, KvpValue * value);
+
+/** The kvp_frame_set_slot_path() routine walks the hierarchy,
+ * using the key values to pick each branch. When the terminal
+ * node is reached, the value is copied into it. The old value
+ * at this location, if any, is destroyed.
+ */
+void kvp_frame_set_slot_path (KvpFrame *frame,
+ const KvpValue *value,
+ const char *first_key, ...);
+
+/** The kvp_frame_set_slot_path_gslist() routine walks the hierarchy,
+ * using the key values to pick each branch. When the terminal node
+ * is reached, the value is copied into it. The old value at this
+ * location, if any, is destroyed.
+ */
+void kvp_frame_set_slot_path_gslist (KvpFrame *frame,
+ const KvpValue *value,
+ GSList *key_path);
+
+/** @} */
+
+/** @name KvpFrame KvpValue Low-Level Retrieval Routines
+
+ You probably shouldn't be using these low-level routines
+
+ Returns the KvpValue in the given KvpFrame 'frame' that is
+ associated with 'key'. If there is no key in the frame, NULL
+ is returned. If the value associated with the key is NULL,
+ NULL is returned.
+
+ Pointers passed as arguments into get_slot are the responsibility
+ of the caller. Pointers returned by get_slot are owned by the
+ kvp_frame. Make copies as needed.
+ @{
+*/
+KvpValue * kvp_frame_get_slot(const KvpFrame * frame, const char * key);
+
+/** This routine return the value at the end of the
+ * path, or NULL if any portion of the path doesn't exist.
+ */
+KvpValue * kvp_frame_get_slot_path (KvpFrame *frame,
+ const char *first_key, ...);
+
+/** This routine return the value at the end of the
+ * path, or NULL if any portion of the path doesn't exist.
+ */
+KvpValue * kvp_frame_get_slot_path_gslist (KvpFrame *frame,
+ GSList *key_path);
+
+/**
+ * Similar returns as strcmp.
+ */
+gint kvp_frame_compare(const KvpFrame *fa, const KvpFrame *fb);
+
+gint double_compare(double v1, double v2);
+/** @} */
+/** @name KvpValue List Convenience Functions
+
+ You probably shouldn't be using these low-level routines
+
+ kvp_glist_compare() compares <b>GLists of kvp_values</b> (not to
+ be confused with GLists of something else): it iterates over
+ the list elements, performing a kvp_value_compare on each.
+ @{
+*/
+gint kvp_glist_compare(const GList * list1, const GList * list2);
+
+/** kvp_glist_copy() performs a deep copy of a <b>GList of
+ * kvp_values</b> (not to be confused with GLists of something
+ * else): same as mapping kvp_value_copy() over the elements and
+ * then copying the spine.
+ */
+GList * kvp_glist_copy(const GList * list);
+
+/** kvp_glist_delete() performs a deep delete of a <b>GList of
+ * kvp_values</b> (not to be confused with GLists of something
+ * else): same as mapping * kvp_value_delete() over the elements
+ * and then deleting the GList.
+ */
+void kvp_glist_delete(GList * list);
+/** @} */
+
+
+/** @name KvpValue Constructors
+
+ You probably shouldn't be using these low-level routines
+
+ The following routines are constructors for kvp_value.
+ Those with pointer arguments copy in the value.
+ The *_nc() versions do *not* copy in thier values,
+ but use them directly.
+ @{
+ */
+KvpValue * kvp_value_new_gint64(gint64 value);
+KvpValue * kvp_value_new_double(double value);
+
+/** \deprecated
+
+Use kvp_value_new_numeric instead of kvp_value_new_gnc_numeric
+*/
+#define kvp_value_new_gnc_numeric kvp_value_new_numeric
+KvpValue * kvp_value_new_numeric(gnc_numeric value);
+KvpValue * kvp_value_new_string(const char * value);
+KvpValue * kvp_value_new_guid(const GUID * guid);
+KvpValue * kvp_value_new_timespec(Timespec timespec);
+KvpValue * kvp_value_new_binary(const void * data, guint64 datasize);
+KvpValue * kvp_value_new_frame(const KvpFrame * value);
+
+/** Creates a KvpValue from a <b>GList of kvp_value's</b>! (Not to be
+ * confused with GList's of something else!) */
+KvpValue * kvp_value_new_glist(const GList * value);
+
+/** value constructors (non-copying - KvpValue takes pointer ownership)
+ values *must* have been allocated via glib allocators! (gnew, etc.) */
+KvpValue * kvp_value_new_binary_nc(void * data, guint64 datasize);
+
+/** Creates a KvpValue from a <b>GList of kvp_value's</b>! (Not to be
+ * confused with GList's of something else!)
+ *
+ * This value constructor is non-copying (KvpValue takes pointer
+ * ownership). The values *must* have been allocated via glib
+ * allocators! (gnew, etc.) */
+KvpValue * kvp_value_new_glist_nc(GList *lst);
+
+/** value constructors (non-copying - KvpValue takes pointer ownership)
+ values *must* have been allocated via glib allocators! (gnew, etc.) */
+KvpValue * kvp_value_new_frame_nc(KvpFrame * value);
+
+/** This is a deep (recursive) delete. */
+void kvp_value_delete(KvpValue * value);
+
+/** This is a deep value copy. */
+KvpValue * kvp_value_copy(const KvpValue * value);
+
+/** Replace old frame value with new, return old frame */
+KvpFrame * kvp_value_replace_frame_nc(KvpValue *value, KvpFrame * newframe);
+
+/** Replace old glist value with new, return old glist */
+GList * kvp_value_replace_glist_nc(KvpValue *value, GList *newlist);
+
+/** @} */
+
+
+/** @name KvpValue Value access
+
+ You probably shouldn't be using these low-level routines
+ @{
+*/
+
+KvpValueType kvp_value_get_type(const KvpValue * value);
+
+
+/** Value accessors. Those for GUID, binary, GList, KvpFrame and
+ * string are non-copying -- the caller can modify the value
+ * directly. Just don't free it, or you screw up everything.
+ * Note that if another value is stored at the key location
+ * that this value came from, then this value will be
+ * uncermoniously deleted, and you will be left pointing to
+ * garbage. So don't store values at the same time you are
+ * examining their contents.
+ */
+
+gint64 kvp_value_get_gint64(const KvpValue * value);
+double kvp_value_get_double(const KvpValue * value);
+gnc_numeric kvp_value_get_numeric(const KvpValue * value);
+
+/** Value accessor. This one is non-copying -- the caller can modify
+ * the value directly. */
+char * kvp_value_get_string(const KvpValue * value);
+
+/** Value accessor. This one is non-copying -- the caller can modify
+ * the value directly. */
+GUID * kvp_value_get_guid(const KvpValue * value);
+
+/** Value accessor. This one is non-copying -- the caller can modify
+ * the value directly. */
+void * kvp_value_get_binary(const KvpValue * value,
+ guint64 * size_return);
+
+/** Returns the GList of kvp_frame's (not to be confused with GList's
+ * of something else!) from the given kvp_frame. This one is
+ * non-copying -- the caller can modify the value directly. */
+GList * kvp_value_get_glist(const KvpValue * value);
+
+/** Value accessor. This one is non-copying -- the caller can modify
+ * the value directly. */
+KvpFrame * kvp_value_get_frame(const KvpValue * value);
+Timespec kvp_value_get_timespec(const KvpValue * value);
+
+/**
+ * Similar returns as strcmp.
+ **/
+gint kvp_value_compare(const KvpValue *va, const KvpValue *vb);
+
+/** @} */
+
+/** \brief General purpose function to convert any KvpValue to a string.
+
+Only the bare string is returned, there is no debugging information.
+*/
+gchar* kvp_value_to_bare_string(const KvpValue *val);
+
+/** \brief Debug version of kvp_value_to_string
+
+This version is used only by ::qof_query_printValueForParam,
+itself a debugging and development utility function.
+*/
+gchar* kvp_value_to_string(const KvpValue *val);
+
+/** Manipulator:
+ *
+ * copying - but more efficient than creating a new KvpValue manually. */
+gboolean kvp_value_binary_append(KvpValue *v, void *data, guint64 size);
+
+/** @name Iterators
+@{
+*/
+/** Traverse all of the slots in the given kvp_frame. This function
+ does not descend recursively to traverse any kvp_frames stored as
+ slot values. You must handle that in proc, with a suitable
+ recursive call if desired. */
+void kvp_frame_for_each_slot(KvpFrame *f,
+ void (*proc)(const char *key,
+ KvpValue *value,
+ gpointer data),
+ gpointer data);
+
+/** @} */
+
+/** Internal helper routines, you probably shouldn't be using these. */
+gchar* kvp_frame_to_string(const KvpFrame *frame);
+gchar* binary_to_string(const void *data, guint32 size);
+gchar* kvp_value_glist_to_string(const GList *list);
+GHashTable* kvp_frame_get_hash(const KvpFrame *frame);
+
+/** @} */
+#endif
Added: gnucash/trunk/lib/libqof/qof/md5.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/md5.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/md5.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,445 @@
+/* md5.c - Functions to compute MD5 message digest of files or memory blocks
+ according to the definition of MD5 in RFC 1321 from April 1992.
+ Copyright (C) 1995, 1996 Free Software Foundation, Inc.
+ NOTE: The canonical source of this file is maintained with the GNU C
+ Library. Bugs can be reported to bug-glibc at prep.ai.mit.edu.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+/* Written by Ulrich Drepper <drepper at gnu.ai.mit.edu>, 1995. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+
+#if STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+# include <string.h>
+#else
+# ifndef HAVE_MEMCPY
+#include <string.h>
+# define memcpy(d, s, n) bcopy ((s), (d), (n))
+# endif
+#endif
+
+#include "md5.h"
+
+#ifdef _LIBC
+# include <endian.h>
+# if __BYTE_ORDER == __BIG_ENDIAN
+# define WORDS_BIGENDIAN 1
+# endif
+#endif
+
+#ifdef WORDS_BIGENDIAN
+# define SWAP(n) \
+ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
+#else
+# define SWAP(n) (n)
+#endif
+
+
+/* This array contains the bytes used to pad the buffer to the next
+ 64-byte boundary. (RFC 1321, 3.1: Step 1) */
+static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
+
+
+/* Initialize structure containing state of computation.
+ (RFC 1321, 3.3: Step 3) */
+void
+md5_init_ctx (ctx)
+ struct md5_ctx *ctx;
+{
+ ctx->A = 0x67452301;
+ ctx->B = 0xefcdab89;
+ ctx->C = 0x98badcfe;
+ ctx->D = 0x10325476;
+
+ ctx->total[0] = ctx->total[1] = 0;
+ ctx->buflen = 0;
+}
+
+/* Put result from CTX in first 16 bytes following RESBUF. The result
+ must be in little endian byte order.
+
+ IMPORTANT: On some systems it is required that RESBUF is correctly
+ aligned for a 32 bits value. */
+void *
+md5_read_ctx (ctx, resbuf)
+ const struct md5_ctx *ctx;
+ void *resbuf;
+{
+ ((md5_uint32 *) resbuf)[0] = SWAP (ctx->A);
+ ((md5_uint32 *) resbuf)[1] = SWAP (ctx->B);
+ ((md5_uint32 *) resbuf)[2] = SWAP (ctx->C);
+ ((md5_uint32 *) resbuf)[3] = SWAP (ctx->D);
+
+ return resbuf;
+}
+
+/* Process the remaining bytes in the internal buffer and the usual
+ prolog according to the standard and write the result to RESBUF.
+
+ IMPORTANT: On some systems it is required that RESBUF is correctly
+ aligned for a 32 bits value. */
+void *
+md5_finish_ctx (ctx, resbuf)
+ struct md5_ctx *ctx;
+ void *resbuf;
+{
+ /* Take yet unprocessed bytes into account. */
+ md5_uint32 bytes = ctx->buflen;
+ size_t pad;
+
+ /* Now count remaining bytes. */
+ ctx->total[0] += bytes;
+ if (ctx->total[0] < bytes)
+ ++ctx->total[1];
+
+ pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
+ memcpy (&ctx->buffer[bytes], fillbuf, pad);
+
+ /* Put the 64-bit file length in *bits* at the end of the buffer. */
+ *(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP (ctx->total[0] << 3);
+ *(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP ((ctx->total[1] << 3) |
+ (ctx->total[0] >> 29));
+
+ /* Process last bytes. */
+ md5_process_block (ctx->buffer, bytes + pad + 8, ctx);
+
+ return md5_read_ctx (ctx, resbuf);
+}
+
+/* Compute MD5 message digest for bytes read from STREAM. The
+ resulting message digest number will be written into the 16 bytes
+ beginning at RESBLOCK. */
+int
+md5_stream (stream, resblock)
+ FILE *stream;
+ void *resblock;
+{
+ /* Important: BLOCKSIZE must be a multiple of 64. */
+#define BLOCKSIZE 4096
+ struct md5_ctx ctx;
+ char buffer[BLOCKSIZE + 72];
+ size_t sum;
+
+ /* Initialize the computation context. */
+ md5_init_ctx (&ctx);
+
+ /* Iterate over full file contents. */
+ while (1)
+ {
+ /* We read the file in blocks of BLOCKSIZE bytes. One call of the
+ computation function processes the whole buffer so that with the
+ next round of the loop another block can be read. */
+ size_t n;
+ sum = 0;
+
+ /* Read block. Take care for partial reads. */
+ do
+ {
+ n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream);
+
+ sum += n;
+ }
+ while (sum < BLOCKSIZE && n != 0);
+ if (n == 0 && ferror (stream))
+ return 1;
+
+ /* If end of file is reached, end the loop. */
+ if (n == 0)
+ break;
+
+ /* Process buffer with BLOCKSIZE bytes. Note that
+ BLOCKSIZE % 64 == 0
+ */
+ md5_process_block (buffer, BLOCKSIZE, &ctx);
+ }
+
+ /* Add the last bytes if necessary. */
+ if (sum > 0)
+ md5_process_bytes (buffer, sum, &ctx);
+
+ /* Construct result in desired memory. */
+ md5_finish_ctx (&ctx, resblock);
+ return 0;
+}
+
+/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The
+ result is always in little endian byte order, so that a byte-wise
+ output yields to the wanted ASCII representation of the message
+ digest. */
+void *
+md5_buffer (buffer, len, resblock)
+ const char *buffer;
+ size_t len;
+ void *resblock;
+{
+ struct md5_ctx ctx;
+
+ /* Initialize the computation context. */
+ md5_init_ctx (&ctx);
+
+ /* Process whole buffer but last len % 64 bytes. */
+ md5_process_bytes (buffer, len, &ctx);
+
+ /* Put result in desired memory area. */
+ return md5_finish_ctx (&ctx, resblock);
+}
+
+
+void
+md5_process_bytes (buffer, len, ctx)
+ const void *buffer;
+ size_t len;
+ struct md5_ctx *ctx;
+{
+#define NUM_MD5_WORDS 1024
+ size_t add = 0;
+
+ /* When we already have some bits in our internal buffer concatenate
+ both inputs first. */
+ if (ctx->buflen != 0)
+ {
+ size_t left_over = ctx->buflen;
+
+ add = 128 - left_over > len ? len : 128 - left_over;
+
+ memcpy (&ctx->buffer[left_over], buffer, add);
+ ctx->buflen += add;
+
+ if (left_over + add > 64)
+ {
+ md5_process_block (ctx->buffer, (left_over + add) & ~63, ctx);
+ /* The regions in the following copy operation cannot overlap. */
+ memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
+ (left_over + add) & 63);
+ ctx->buflen = (left_over + add) & 63;
+ }
+
+ buffer = (const char *) buffer + add;
+ len -= add;
+ }
+
+ /* Process available complete blocks. */
+ if (len > 64)
+ {
+ if ((add & 3) == 0) /* buffer is still 32-bit aligned */
+ {
+ md5_process_block (buffer, len & ~63, ctx);
+ buffer = (const char *) buffer + (len & ~63);
+ }
+ else /* buffer is not 32-bit aligned */
+ {
+ md5_uint32 md5_buffer[NUM_MD5_WORDS];
+ size_t num_bytes;
+ size_t buf_bytes;
+
+ num_bytes = len & ~63;
+ while (num_bytes > 0)
+ {
+ buf_bytes = (num_bytes < sizeof(md5_buffer)) ?
+ num_bytes : sizeof(md5_buffer);
+ memcpy (md5_buffer, buffer, buf_bytes);
+ md5_process_block (md5_buffer, buf_bytes, ctx);
+ num_bytes -= buf_bytes;
+ buffer = (const char *) buffer + buf_bytes;
+ }
+ }
+
+ len &= 63;
+ }
+
+ /* Move remaining bytes in internal buffer. */
+ if (len > 0)
+ {
+ memcpy (ctx->buffer, buffer, len);
+ ctx->buflen = len;
+ }
+}
+
+
+/* These are the four functions used in the four steps of the MD5 algorithm
+ and defined in the RFC 1321. The first function is a little bit optimized
+ (as found in Colin Plumbs public domain implementation). */
+/* #define FF(b, c, d) ((b & c) | (~b & d)) */
+#define FF(b, c, d) (d ^ (b & (c ^ d)))
+#define FG(b, c, d) FF (d, b, c)
+#define FH(b, c, d) (b ^ c ^ d)
+#define FI(b, c, d) (c ^ (b | ~d))
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+ It is assumed that LEN % 64 == 0. */
+
+void
+md5_process_block (buffer, len, ctx)
+ const void *buffer;
+ size_t len;
+ struct md5_ctx *ctx;
+{
+ md5_uint32 correct_words[16];
+ const md5_uint32 *words = buffer;
+ size_t nwords = len / sizeof (md5_uint32);
+ const md5_uint32 *endp = words + nwords;
+ md5_uint32 A = ctx->A;
+ md5_uint32 B = ctx->B;
+ md5_uint32 C = ctx->C;
+ md5_uint32 D = ctx->D;
+
+ /* First increment the byte count. RFC 1321 specifies the possible
+ length of the file up to 2^64 bits. Here we only compute the
+ number of bytes. Do a double word increment. */
+ ctx->total[0] += len;
+ if (ctx->total[0] < len)
+ ++ctx->total[1];
+
+ /* Process all bytes in the buffer with 64 bytes in each round of
+ the loop. */
+ while (words < endp)
+ {
+ md5_uint32 *cwp = correct_words;
+ md5_uint32 A_save = A;
+ md5_uint32 B_save = B;
+ md5_uint32 C_save = C;
+ md5_uint32 D_save = D;
+
+ /* First round: using the given function, the context and a constant
+ the next context is computed. Because the algorithms processing
+ unit is a 32-bit word and it is determined to work on words in
+ little endian byte order we perhaps have to change the byte order
+ before the computation. To reduce the work for the next steps
+ we store the swapped words in the array CORRECT_WORDS. */
+
+#define OP(a, b, c, d, s, T) \
+ do \
+ { \
+ a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \
+ ++words; \
+ CYCLIC (a, s); \
+ a += b; \
+ } \
+ while (0)
+
+ /* It is unfortunate that C does not provide an operator for
+ cyclic rotation. Hope the C compiler is smart enough. */
+#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
+
+ /* Before we start, one word to the strange constants.
+ They are defined in RFC 1321 as
+
+ T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
+ */
+
+ /* Round 1. */
+ OP (A, B, C, D, 7, 0xd76aa478);
+ OP (D, A, B, C, 12, 0xe8c7b756);
+ OP (C, D, A, B, 17, 0x242070db);
+ OP (B, C, D, A, 22, 0xc1bdceee);
+ OP (A, B, C, D, 7, 0xf57c0faf);
+ OP (D, A, B, C, 12, 0x4787c62a);
+ OP (C, D, A, B, 17, 0xa8304613);
+ OP (B, C, D, A, 22, 0xfd469501);
+ OP (A, B, C, D, 7, 0x698098d8);
+ OP (D, A, B, C, 12, 0x8b44f7af);
+ OP (C, D, A, B, 17, 0xffff5bb1);
+ OP (B, C, D, A, 22, 0x895cd7be);
+ OP (A, B, C, D, 7, 0x6b901122);
+ OP (D, A, B, C, 12, 0xfd987193);
+ OP (C, D, A, B, 17, 0xa679438e);
+ OP (B, C, D, A, 22, 0x49b40821);
+
+ /* For the second to fourth round we have the possibly swapped words
+ in CORRECT_WORDS. Redefine the macro to take an additional first
+ argument specifying the function to use. */
+#undef OP
+#define OP(f, a, b, c, d, k, s, T) \
+ do \
+ { \
+ a += f (b, c, d) + correct_words[k] + T; \
+ CYCLIC (a, s); \
+ a += b; \
+ } \
+ while (0)
+
+ /* Round 2. */
+ OP (FG, A, B, C, D, 1, 5, 0xf61e2562);
+ OP (FG, D, A, B, C, 6, 9, 0xc040b340);
+ OP (FG, C, D, A, B, 11, 14, 0x265e5a51);
+ OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa);
+ OP (FG, A, B, C, D, 5, 5, 0xd62f105d);
+ OP (FG, D, A, B, C, 10, 9, 0x02441453);
+ OP (FG, C, D, A, B, 15, 14, 0xd8a1e681);
+ OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8);
+ OP (FG, A, B, C, D, 9, 5, 0x21e1cde6);
+ OP (FG, D, A, B, C, 14, 9, 0xc33707d6);
+ OP (FG, C, D, A, B, 3, 14, 0xf4d50d87);
+ OP (FG, B, C, D, A, 8, 20, 0x455a14ed);
+ OP (FG, A, B, C, D, 13, 5, 0xa9e3e905);
+ OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8);
+ OP (FG, C, D, A, B, 7, 14, 0x676f02d9);
+ OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
+
+ /* Round 3. */
+ OP (FH, A, B, C, D, 5, 4, 0xfffa3942);
+ OP (FH, D, A, B, C, 8, 11, 0x8771f681);
+ OP (FH, C, D, A, B, 11, 16, 0x6d9d6122);
+ OP (FH, B, C, D, A, 14, 23, 0xfde5380c);
+ OP (FH, A, B, C, D, 1, 4, 0xa4beea44);
+ OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9);
+ OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60);
+ OP (FH, B, C, D, A, 10, 23, 0xbebfbc70);
+ OP (FH, A, B, C, D, 13, 4, 0x289b7ec6);
+ OP (FH, D, A, B, C, 0, 11, 0xeaa127fa);
+ OP (FH, C, D, A, B, 3, 16, 0xd4ef3085);
+ OP (FH, B, C, D, A, 6, 23, 0x04881d05);
+ OP (FH, A, B, C, D, 9, 4, 0xd9d4d039);
+ OP (FH, D, A, B, C, 12, 11, 0xe6db99e5);
+ OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8);
+ OP (FH, B, C, D, A, 2, 23, 0xc4ac5665);
+
+ /* Round 4. */
+ OP (FI, A, B, C, D, 0, 6, 0xf4292244);
+ OP (FI, D, A, B, C, 7, 10, 0x432aff97);
+ OP (FI, C, D, A, B, 14, 15, 0xab9423a7);
+ OP (FI, B, C, D, A, 5, 21, 0xfc93a039);
+ OP (FI, A, B, C, D, 12, 6, 0x655b59c3);
+ OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92);
+ OP (FI, C, D, A, B, 10, 15, 0xffeff47d);
+ OP (FI, B, C, D, A, 1, 21, 0x85845dd1);
+ OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f);
+ OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
+ OP (FI, C, D, A, B, 6, 15, 0xa3014314);
+ OP (FI, B, C, D, A, 13, 21, 0x4e0811a1);
+ OP (FI, A, B, C, D, 4, 6, 0xf7537e82);
+ OP (FI, D, A, B, C, 11, 10, 0xbd3af235);
+ OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb);
+ OP (FI, B, C, D, A, 9, 21, 0xeb86d391);
+
+ /* Add the starting values of the context. */
+ A += A_save;
+ B += B_save;
+ C += C_save;
+ D += D_save;
+ }
+
+ /* Put checksum in context given as argument. */
+ ctx->A = A;
+ ctx->B = B;
+ ctx->C = C;
+ ctx->D = D;
+}
Added: gnucash/trunk/lib/libqof/qof/md5.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/md5.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/md5.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,163 @@
+/* md5.h - Declaration of functions and data types used for MD5 sum
+ computing library functions.
+ Copyright (C) 1995, 1996 Free Software Foundation, Inc.
+ NOTE: The canonical source of this file is maintained with the GNU C
+ Library. Bugs can be reported to bug-glibc at prep.ai.mit.edu.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#ifndef _MD5_H
+#define _MD5_H 1
+
+#include <stddef.h>
+#include <stdio.h>
+
+#if defined HAVE_LIMITS_H || _LIBC
+# include <limits.h>
+#endif
+
+/* The following contortions are an attempt to use the C preprocessor
+ to determine an unsigned integral type that is 32 bits wide. An
+ alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but
+ doing that would require that the configure script compile and *run*
+ the resulting executable. Locally running cross-compiled executables
+ is usually not possible. */
+
+#ifdef _LIBC
+# include <sys/types.h>
+typedef u_int32_t md5_uint32;
+#else
+# if defined __STDC__ && __STDC__
+# define UINT_MAX_32_BITS 4294967295U
+# else
+# define UINT_MAX_32_BITS 0xFFFFFFFF
+# endif
+
+/* If UINT_MAX isn't defined, assume it's a 32-bit type.
+ This should be valid for all systems GNU cares about because
+ that doesn't include 16-bit systems, and only modern systems
+ (that certainly have <limits.h>) have 64+-bit integral types. */
+
+# ifndef UINT_MAX
+# define UINT_MAX UINT_MAX_32_BITS
+# endif
+
+# if UINT_MAX == UINT_MAX_32_BITS
+ typedef unsigned int md5_uint32;
+# else
+# if USHRT_MAX == UINT_MAX_32_BITS
+ typedef unsigned short md5_uint32;
+# else
+# if ULONG_MAX == UINT_MAX_32_BITS
+ typedef unsigned long md5_uint32;
+# else
+ /* The following line is intended to evoke an error.
+ Using #error is not portable enough. */
+ "Cannot determine unsigned 32-bit data type."
+# endif
+# endif
+# endif
+#endif
+
+#undef __P
+#if defined (__STDC__) && __STDC__
+#define __P(x) x
+#else
+#define __P(x) ()
+#endif
+
+/* Structure to save state of computation between the single steps. */
+struct md5_ctx
+{
+ md5_uint32 A;
+ md5_uint32 B;
+ md5_uint32 C;
+ md5_uint32 D;
+
+ md5_uint32 total[2];
+ md5_uint32 buflen;
+ char buffer[128];
+};
+
+/*
+ * The following three functions are build up the low level used in
+ * the functions `md5_stream' and `md5_buffer'.
+ */
+
+/* Initialize structure containing state of computation.
+ (RFC 1321, 3.3: Step 3) */
+extern void md5_init_ctx __P ((struct md5_ctx *ctx));
+
+
+/* Starting with the result of former calls of this function (or the
+ initialization function update the context for the next LEN bytes
+ starting at BUFFER.
+
+ It is necessary that LEN is a multiple of 64!!!
+
+ IMPORTANT: On some systems it is required that buffer be 32-bit
+ aligned. */
+extern void md5_process_block __P ((const void *buffer, size_t len,
+ struct md5_ctx *ctx));
+
+/* Starting with the result of former calls of this function (or the
+ initialization function) update the context for the next LEN bytes
+ starting at BUFFER.
+
+ It is NOT required that LEN is a multiple of 64.
+
+ IMPORTANT: On some systems it is required that buffer be 32-bit
+ aligned. */
+extern void md5_process_bytes __P ((const void *buffer, size_t len,
+ struct md5_ctx *ctx));
+
+/* Process the remaining bytes in the buffer and put result from CTX
+ in first 16 bytes following RESBUF. The result is always in little
+ endian byte order, so that a byte-wise output yields to the wanted
+ ASCII representation of the message digest.
+
+ IMPORTANT: On some systems it is required that RESBUF be correctly
+ aligned for a 32 bits value. */
+extern void *md5_finish_ctx __P ((struct md5_ctx *ctx, void *resbuf));
+
+
+/* Put result from CTX in first 16 bytes following RESBUF. The result is
+ always in little endian byte order, so that a byte-wise output yields
+ to the wanted ASCII representation of the message digest.
+
+ IMPORTANT: On some systems it is required that RESBUF be correctly
+ aligned for a 32 bits value. */
+extern void *md5_read_ctx __P ((const struct md5_ctx *ctx, void *resbuf));
+
+
+/* Compute MD5 message digest for bytes read from STREAM. The
+ resulting message digest number will be written into the 16 bytes
+ beginning at RESBLOCK.
+
+ IMPORTANT: On some systems it is required that resblock be 32-bit
+ aligned. */
+extern int md5_stream __P ((FILE *stream, void *resblock));
+
+
+/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The
+ result is always in little endian byte order, so that a byte-wise
+ output yields to the wanted ASCII representation of the message
+ digest.
+
+ IMPORTANT: On some systems it is required that buffer and resblock
+ be correctly 32-bit aligned. */
+extern void *md5_buffer __P ((const char *buffer, size_t len, void *resblock));
+
+#endif
Added: gnucash/trunk/lib/libqof/qof/qof-be-utils.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof-be-utils.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qof-be-utils.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,186 @@
+/********************************************************************\
+ * qof-be-utils.h: api for data storage backend *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup Object
+ @{ */
+/** @addtogroup Backend
+ @{ */
+/** @file qof-be-utils.h
+ @brief QOF Backend Utilities
+ @author Derek Atkins <derek at ihtfp.com>
+ @author Neil Williams <linux at codehelp.co.uk>
+
+ Common code used by objects to define begin_edit() and
+ commit_edit() functions.
+
+*/
+
+#ifndef QOF_BE_UTILS_H
+#define QOF_BE_UTILS_H
+
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "qofbackend-p.h"
+#include "qofbook.h"
+#include "qofinstance.h"
+
+/** begin_edit helper
+ *
+ * @param inst: an instance of QofInstance
+ *
+ * The caller should use this macro first and then perform any other operations.
+
+ Uses newly created functions to allow the macro to be used
+ when QOF is linked as a library. qofbackend-p.h is a private header.
+ */
+
+#define QOF_BEGIN_EDIT(inst) \
+ QofBackend * be; \
+ if (!(inst)) return; \
+ \
+ (inst)->editlevel++; \
+ if (1 < (inst)->editlevel) return; \
+ \
+ if (0 >= (inst)->editlevel) \
+ { \
+ PERR ("unbalanced call - resetting (was %d)", (inst)->editlevel); \
+ (inst)->editlevel = 1; \
+ } \
+ ENTER ("(inst=%p)", (inst)); \
+ \
+ /* See if there's a backend. If there is, invoke it. */ \
+ be = qof_book_get_backend ((inst)->book); \
+ if (be && qof_backend_begin_exists((be))) { \
+ qof_backend_run_begin((be), (inst)); \
+ } else { \
+ /* We tried and failed to start transaction! */ \
+ (inst)->dirty = TRUE; \
+ } \
+ LEAVE (" ");
+
+/** \brief function version of QOF_BEGIN_EDIT
+
+The macro cannot be used in a function that returns a value,
+this function can be used instead.
+*/
+gboolean qof_begin_edit(QofInstance *inst);
+
+/**
+ * commit_edit helpers
+ *
+ * The caller should call PART1 as the first thing, then
+ * perform any local operations prior to calling the backend.
+ * Then call PART2.
+ */
+
+/**
+ * part1 -- deal with the editlevel
+ *
+ * @param inst: an instance of QofInstance
+ */
+
+#define QOF_COMMIT_EDIT_PART1(inst) { \
+ if (!(inst)) return; \
+ \
+ (inst)->editlevel--; \
+ if (0 < (inst)->editlevel) return; \
+ \
+ /* The pricedb sufffers from delayed update... */ \
+ /* This may be setting a bad precedent for other types, I fear. */ \
+ /* Other types probably really should handle begin like this. */ \
+ if ((-1 == (inst)->editlevel) && (inst)->dirty) \
+ { \
+ QofBackend * be; \
+ be = qof_book_get_backend ((inst)->book); \
+ if (be && qof_backend_begin_exists((be))) { \
+ qof_backend_run_begin((be), (inst)); \
+ } \
+ (inst)->editlevel = 0; \
+ } \
+ if (0 > (inst)->editlevel) \
+ { \
+ PERR ("unbalanced call - resetting (was %d)", (inst)->editlevel); \
+ (inst)->editlevel = 0; \
+ } \
+ ENTER ("(inst=%p) dirty=%d do-free=%d", \
+ (inst), (inst)->dirty, (inst)->do_free); \
+}
+
+/** \brief function version of QOF_COMMIT_EDIT_PART1
+
+The macro cannot be used in a function that returns a value,
+this function can be used instead. Only Part1 is implemented.
+*/
+gboolean qof_commit_edit(QofInstance *inst);
+
+/**
+ * part2 -- deal with the backend
+ *
+ * @param inst: an instance of QofInstance
+ * @param on_error: a function called if there is a backend error.
+ * void (*on_error)(inst, QofBackendError)
+ * @param on_done: a function called after the commit is complete
+ * but before the instect is freed. Perform any other
+ * operations after the commit.
+ * void (*on_done)(inst)
+ * @param on_free: a function called if inst->do_free is TRUE.
+ * void (*on_free)(inst)
+ */
+#define QOF_COMMIT_EDIT_PART2(inst,on_error,on_done,on_free) { \
+ QofBackend * be; \
+ \
+ /* See if there's a backend. If there is, invoke it. */ \
+ be = qof_book_get_backend ((inst)->book); \
+ if (be && qof_backend_commit_exists((be))) \
+ { \
+ QofBackendError errcode; \
+ \
+ /* clear errors */ \
+ do { \
+ errcode = qof_backend_get_error (be); \
+ } while (ERR_BACKEND_NO_ERR != errcode); \
+ \
+ qof_backend_run_commit((be), (inst)); \
+ errcode = qof_backend_get_error (be); \
+ if (ERR_BACKEND_NO_ERR != errcode) \
+ { \
+ /* XXX Should perform a rollback here */ \
+ (inst)->do_free = FALSE; \
+ \
+ /* Push error back onto the stack */ \
+ qof_backend_set_error (be, errcode); \
+ (on_error)((inst), errcode); \
+ } \
+ /* XXX the backend commit code should clear dirty!! */ \
+ (inst)->dirty = FALSE; \
+ } \
+ (on_done)(inst); \
+ \
+ LEAVE ("inst=%p, dirty=%d do-free=%d", \
+ (inst), (inst)->dirty, (inst)->do_free); \
+ if ((inst)->do_free) { \
+ (on_free)(inst); \
+ return; \
+ } \
+}
+
+#endif /* QOF_BE_UTILS_H */
+/** @} */
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/qof.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qof.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,101 @@
+/********************************************************************\
+ * qof.h -- Master QOF public include file *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#ifndef QOF_H_
+#define QOF_H_
+/** @defgroup QOF Query Object Framework
+ @{
+*/
+
+/**
+ @addtogroup Date Date: Date and Time Printing, Parsing and Manipulation
+ @ingroup QOF
+*/
+/**
+ @addtogroup Entity Entity: Types, Identity and Instance Framework
+ @ingroup QOF
+
+*/
+/**
+ @addtogroup KVP KVP: Key-Value Pairs
+ @ingroup QOF
+*/
+/**
+ @addtogroup Math128 Math128: 128-bit Integer Math Library
+ @ingroup QOF
+*/
+/**
+ @addtogroup Numeric Numeric: Rational Number Handling w/ Rounding Error Control
+ @ingroup QOF
+*/
+/**
+ @addtogroup Object Object: Dynamic Object Class Framework
+ @ingroup QOF
+*/
+/** @addtogroup Choice Choice and collect : One to many links.
+ @ingroup QOF
+*/
+/**
+ @addtogroup Query Query: Querying for Objects
+ @ingroup QOF
+*/
+/**
+ @addtogroup Trace Trace: Error Reporting and Debugging
+ @ingroup QOF
+*/
+/** @addtogroup BookMerge Merging QofBook structures
+ @ingroup QOF
+*/
+/** @addtogroup Event Event: QOF event handlers.
+ @ingroup QOF
+*/
+/**
+ @addtogroup Utilities Misc Utilities
+ @ingroup QOF
+*/
+/** @} */
+
+#include "qofid.h"
+#include "gnc-trace.h"
+#include "gnc-date.h"
+#include "gnc-numeric.h"
+#include "gnc-event.h"
+#include "gnc-engine-util.h"
+#include "guid.h"
+#include "kvp_frame.h"
+#include "kvp-util.h"
+#include "kvp-util-p.h"
+#include "qofbackend.h"
+#include "qofid-p.h"
+#include "qofinstance-p.h"
+#include "qofbook.h"
+#include "qofclass.h"
+#include "qofobject.h"
+#include "qofquery.h"
+#include "qofquerycore.h"
+#include "qofsession.h"
+#include "qofsql.h"
+#include "qofchoice.h"
+#include "qof_book_merge.h"
+#include "qof-be-utils.h"
+#include "qofla-dir.h"
+
+#endif /* QOF_H_ */
Added: gnucash/trunk/lib/libqof/qof/qof_book_merge.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof_book_merge.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qof_book_merge.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,992 @@
+/*********************************************************************
+ * qof_book_merge.c -- api for QoFBook merge with collision handling *
+ * Copyright (C) 2004-2005 Neil Williams <linux at codehelp.co.uk> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ ********************************************************************/
+
+#include "gnc-trace.h"
+#include "qof_book_merge.h"
+#include "qofinstance-p.h"
+#include "qofchoice.h"
+#include "qofid-p.h"
+
+static QofLogModule log_module = QOF_MOD_MERGE;
+
+/* private rule iteration struct */
+struct qof_book_mergeRuleIterate {
+ qof_book_mergeRuleForeachCB fcn;
+ qof_book_mergeData *data;
+ qof_book_mergeRule *rule;
+ GList *ruleList;
+ guint remainder;
+};
+
+/* Make string type parameters 3 times more
+ important in the match than default types.
+ i.e. even if two other parameters differ,
+ a string match will still provide a better target
+ than when other types match and the string does not.
+*/
+#define DEFAULT_MERGE_WEIGHT 1
+#define QOF_STRING_WEIGHT 3
+#define QOF_DATE_STRING_LENGTH MAX_DATE_LENGTH
+
+static qof_book_mergeRule*
+qof_book_mergeUpdateRule(qof_book_mergeRule *currentRule, gboolean match, gint weight)
+{
+ gboolean absolute;
+
+ absolute = currentRule->mergeAbsolute;
+ if(absolute && match && currentRule->mergeResult == MERGE_UNDEF) {
+ currentRule->mergeResult = MERGE_ABSOLUTE;
+ }
+ if(absolute && !match) { currentRule->mergeResult = MERGE_UPDATE; }
+ if(!absolute && match &¤tRule->mergeResult == MERGE_UNDEF) {
+ currentRule->mergeResult = MERGE_DUPLICATE;
+ }
+ if(!absolute && !match) {
+ currentRule->difference += weight;
+ if(currentRule->mergeResult == MERGE_DUPLICATE) {
+ currentRule->mergeResult = MERGE_REPORT;
+ }
+ }
+ return currentRule;
+}
+
+struct collect_list_s
+{
+ GSList *linkedEntList;
+};
+
+static void
+collect_reference_cb (QofEntity *ent, gpointer user_data)
+{
+ struct collect_list_s *s;
+
+ s = (struct collect_list_s*)user_data;
+ if(!ent || !s) { return; }
+ s->linkedEntList = g_slist_prepend(s->linkedEntList, ent);
+}
+
+static int
+qof_book_mergeCompare( qof_book_mergeData *mergeData )
+{
+ qof_book_mergeRule *currentRule;
+ QofCollection *mergeColl, *targetColl;
+ gchar *stringImport, *stringTarget, *charImport, *charTarget;
+ QofEntity *mergeEnt, *targetEnt, *referenceEnt;
+ const GUID *guidImport, *guidTarget;
+ QofParam *qtparam;
+ KvpFrame *kvpImport, *kvpTarget;
+ QofIdType mergeParamName;
+ QofType mergeType;
+ GSList *paramList;
+ gboolean absolute, mergeError, knowntype, mergeMatch, booleanImport, booleanTarget,
+ (*boolean_getter) (QofEntity*, QofParam*);
+ Timespec tsImport, tsTarget, (*date_getter) (QofEntity*, QofParam*);
+ gnc_numeric numericImport, numericTarget, (*numeric_getter) (QofEntity*, QofParam*);
+ double doubleImport, doubleTarget, (*double_getter) (QofEntity*, QofParam*);
+ gint32 i32Import, i32Target, (*int32_getter) (QofEntity*, QofParam*);
+ gint64 i64Import, i64Target, (*int64_getter) (QofEntity*, QofParam*);
+
+ g_return_val_if_fail((mergeData != NULL), -1);
+ currentRule = mergeData->currentRule;
+ g_return_val_if_fail((currentRule != NULL), -1);
+ absolute = currentRule->mergeAbsolute;
+ mergeEnt = currentRule->importEnt;
+ targetEnt = currentRule->targetEnt;
+ paramList = currentRule->mergeParam;
+ currentRule->difference = 0;
+ currentRule->mergeResult = MERGE_UNDEF;
+ currentRule->linkedEntList = NULL;
+ g_return_val_if_fail((targetEnt)||(mergeEnt)||(paramList), -1);
+ kvpImport = kvp_frame_new();
+ kvpTarget = kvp_frame_new();
+ mergeError = FALSE;
+ while(paramList != NULL) {
+ mergeMatch = FALSE;
+ knowntype = FALSE;
+ qtparam = paramList->data;
+ mergeParamName = qtparam->param_name;
+ g_return_val_if_fail(mergeParamName != NULL, -1);
+ mergeType = qtparam->param_type;
+ if(safe_strcmp(mergeType, QOF_TYPE_STRING) == 0) {
+ stringImport = qtparam->param_getfcn(mergeEnt,qtparam);
+ stringTarget = qtparam->param_getfcn(targetEnt,qtparam);
+ /* very strict string matches may need to be relaxed. */
+ if(stringImport == NULL) { stringImport = ""; }
+ if(stringTarget == NULL) { stringTarget = ""; }
+ if(safe_strcmp(stringImport,stringTarget) == 0) { mergeMatch = TRUE; }
+ /* Give special weight to a string match */
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, QOF_STRING_WEIGHT);
+ stringImport = stringTarget = NULL;
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_DATE) == 0) {
+ date_getter = (Timespec (*)(QofEntity*, QofParam*))qtparam->param_getfcn;
+ tsImport = date_getter(mergeEnt, qtparam);
+ tsTarget = date_getter(targetEnt, qtparam);
+ if(timespec_cmp(&tsImport, &tsTarget) == 0) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ knowntype= TRUE;
+ }
+ if((safe_strcmp(mergeType, QOF_TYPE_NUMERIC) == 0) ||
+ (safe_strcmp(mergeType, QOF_TYPE_DEBCRED) == 0)) {
+ numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ numericImport = numeric_getter(mergeEnt,qtparam);
+ numericTarget = numeric_getter(targetEnt,qtparam);
+ if(gnc_numeric_compare (numericImport, numericTarget) == 0) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_GUID) == 0) {
+ guidImport = qtparam->param_getfcn(mergeEnt,qtparam);
+ guidTarget = qtparam->param_getfcn(targetEnt,qtparam);
+ if(guid_compare(guidImport, guidTarget) == 0) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_INT32) == 0) {
+ int32_getter = (gint32 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ i32Import = int32_getter(mergeEnt, qtparam);
+ i32Target = int32_getter(targetEnt, qtparam);
+ if(i32Target == i32Import) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_INT64) == 0) {
+ int64_getter = (gint64 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ i64Import = int64_getter(mergeEnt, qtparam);
+ i64Target = int64_getter(targetEnt, qtparam);
+ if(i64Target == i64Import) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_DOUBLE) == 0) {
+ double_getter = (double (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ doubleImport = double_getter(mergeEnt, qtparam);
+ doubleTarget = double_getter(mergeEnt, qtparam);
+ if(doubleImport == doubleTarget) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_BOOLEAN) == 0){
+ boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ booleanImport = boolean_getter(mergeEnt, qtparam);
+ booleanTarget = boolean_getter(targetEnt, qtparam);
+ if(booleanImport != FALSE && booleanImport != TRUE) { booleanImport = FALSE; }
+ if(booleanTarget != FALSE && booleanTarget != TRUE) { booleanTarget = FALSE; }
+ if(booleanImport == booleanTarget) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_KVP) == 0) {
+ kvpImport = kvp_frame_copy(qtparam->param_getfcn(mergeEnt,qtparam));
+ kvpTarget = kvp_frame_copy(qtparam->param_getfcn(targetEnt,qtparam));
+ if(kvp_frame_compare(kvpImport, kvpTarget) == 0) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ knowntype= TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_CHAR) == 0) {
+ charImport = qtparam->param_getfcn(mergeEnt,qtparam);
+ charTarget = qtparam->param_getfcn(targetEnt,qtparam);
+ if(charImport == charTarget) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ knowntype= TRUE;
+ }
+ /* No object should have QofSetterFunc defined for the book, but just to be safe, do nothing. */
+ if(safe_strcmp(mergeType, QOF_ID_BOOK) == 0) { knowntype= TRUE; }
+ if(safe_strcmp(mergeType, QOF_TYPE_COLLECT) == 0) {
+ struct collect_list_s s;
+ s.linkedEntList = NULL;
+ mergeColl = qtparam->param_getfcn(mergeEnt, qtparam);
+ targetColl = qtparam->param_getfcn(targetEnt, qtparam);
+ s.linkedEntList = g_slist_copy(currentRule->linkedEntList);
+ qof_collection_foreach(mergeColl, collect_reference_cb, &s);
+ currentRule->linkedEntList = g_slist_copy(s.linkedEntList);
+ if(0 == qof_collection_compare(mergeColl, targetColl)) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ knowntype = TRUE;
+ }
+ if(safe_strcmp(mergeType, QOF_TYPE_CHOICE) ==0) {
+ referenceEnt = qtparam->param_getfcn(mergeEnt, qtparam);
+ currentRule->linkedEntList = g_slist_prepend(currentRule->linkedEntList, referenceEnt);
+ if(referenceEnt == qtparam->param_getfcn(targetEnt, qtparam)) { mergeMatch = TRUE; }
+ knowntype = TRUE;
+ }
+ if(knowntype == FALSE) {
+ referenceEnt = qtparam->param_getfcn(mergeEnt, qtparam);
+ if((referenceEnt != NULL)
+ &&(safe_strcmp(referenceEnt->e_type, mergeType) == 0)) {
+ currentRule->linkedEntList = g_slist_prepend(currentRule->linkedEntList, referenceEnt);
+ if(referenceEnt == qtparam->param_getfcn(targetEnt, qtparam)) { mergeMatch = TRUE; }
+ currentRule = qof_book_mergeUpdateRule(currentRule, mergeMatch, DEFAULT_MERGE_WEIGHT);
+ }
+ }
+ paramList = g_slist_next(paramList);
+ }
+ mergeData->currentRule = currentRule;
+ g_free(kvpImport);
+ g_free(kvpTarget);
+ return 0;
+}
+
+static void
+qof_book_mergeCommitForeachCB(gpointer rule, gpointer arg)
+{
+ struct qof_book_mergeRuleIterate *iter;
+
+ g_return_if_fail(arg != NULL);
+ iter = (struct qof_book_mergeRuleIterate*)arg;
+ g_return_if_fail(iter->data != NULL);
+ iter->fcn (iter->data, (qof_book_mergeRule*)rule, iter->remainder);
+ iter->remainder--;
+}
+
+static void
+qof_book_mergeCommitForeach (
+ qof_book_mergeRuleForeachCB cb,
+ qof_book_mergeResult mergeResult,
+ qof_book_mergeData *mergeData)
+{
+ struct qof_book_mergeRuleIterate iter;
+ qof_book_mergeRule *currentRule;
+ GList *subList;
+
+ g_return_if_fail(cb != NULL);
+ g_return_if_fail(mergeData != NULL);
+ currentRule = mergeData->currentRule;
+ g_return_if_fail(currentRule != NULL);
+ g_return_if_fail(mergeResult > 0);
+ g_return_if_fail((mergeResult != MERGE_INVALID)||(mergeResult != MERGE_UNDEF)||(mergeResult != MERGE_REPORT));
+
+ iter.fcn = cb;
+ subList = NULL;
+ iter.ruleList = g_list_copy(mergeData->mergeList);
+ while(iter.ruleList!=NULL) {
+ currentRule = iter.ruleList->data;
+ if(currentRule->mergeResult == mergeResult) {
+ subList = g_list_prepend(subList, currentRule);
+ }
+ iter.ruleList = g_list_next(iter.ruleList);
+ }
+ iter.remainder = g_list_length(subList);
+ iter.data = mergeData;
+ g_list_foreach (subList, qof_book_mergeCommitForeachCB, &iter);
+}
+
+/* build the table of target comparisons
+
+This can get confusing, so bear with me. (!)
+
+Whilst iterating through the entities in the mergeBook, qof_book_mergeForeach assigns
+a targetEnt to each mergeEnt (until it runs out of targetEnt or mergeEnt). Each match
+is made against the one targetEnt that best matches the mergeEnt. Fine so far.
+
+Any mergeEnt is only ever assigned a targetEnt if the calculated difference between
+the two is less than the difference between that targetEnt and any previous mergeEnt
+match.
+
+The next mergeEnt may be a much better match for that targetEnt and the target_table
+is designed to solve the issues that result from this conflict. The previous match
+must be re-assigned because if two mergeEnt's are matched with only one targetEnt,
+data loss \b WILL follow. Equally, the current mergeEnt must replace the previous
+one as it is a better match. qof_entity_rating holds the details required to identify
+the correct mergeEnt to be re-assigned and these mergeEnt entities are therefore
+orphaned - to be re-matched later.
+
+Meanwhile, the current mergeEnt is entered into target_table with it's difference and
+rule data, in case an even better match is found later in the mergeBook.
+
+Finally, each mergeEnt in the orphan_list is now put through the comparison again.
+
+*/
+static gboolean
+qof_book_merge_rule_cmp(gconstpointer a, gconstpointer b)
+{
+ qof_book_mergeRule *ra = (qof_book_mergeRule *) a;
+ qof_book_mergeRule *rb = (qof_book_mergeRule *) b;
+ if (ra->difference == rb->difference) { return TRUE; }
+ else return FALSE;
+}
+
+static void
+qof_book_merge_orphan_check(double difference, qof_book_mergeRule *mergeRule, qof_book_mergeData *mergeData)
+{
+ /* Called when difference is lower than previous
+ Lookup target to find previous match
+ and re-assign mergeEnt to orphan_list */
+ qof_book_mergeRule *rule;
+
+ g_return_if_fail(mergeRule != NULL);
+ g_return_if_fail(mergeData != NULL);
+ if(g_hash_table_size(mergeData->target_table) == 0) { return; }
+ rule = (qof_book_mergeRule*)g_hash_table_lookup(mergeData->target_table, mergeRule->targetEnt);
+ /* If NULL, no match was found. */
+ if(rule == NULL) { return; }
+ /* Only orphan if this is a better match than already exists. */
+ if(difference >= rule->difference) { return; }
+ rule->targetEnt = NULL;
+ rule->mergeResult = MERGE_UNDEF;
+ mergeData->orphan_list = g_slist_append(mergeData->orphan_list, rule);
+}
+
+static void
+qof_book_merge_match_orphans(qof_book_mergeData *mergeData)
+{
+ GSList *orphans, *targets;
+ qof_book_mergeRule *rule, *currentRule;
+ QofEntity *best_matchEnt;
+ double difference;
+
+ g_return_if_fail(mergeData != NULL);
+ currentRule = mergeData->currentRule;
+ g_return_if_fail(currentRule != NULL);
+ /* This routine does NOT copy the orphan list, it
+ is used recursively until empty. */
+ orphans = mergeData->orphan_list;
+ targets = g_slist_copy(mergeData->targetList);
+ while(orphans != NULL) {
+ rule = orphans->data;
+ g_return_if_fail(rule != NULL);
+ difference = g_slist_length(mergeData->mergeObjectParams);
+ if(rule->targetEnt == NULL) {
+ rule->mergeResult = MERGE_NEW;
+ rule->difference = 0;
+ mergeData->mergeList = g_list_prepend(mergeData->mergeList,rule);
+ orphans = g_slist_next(orphans);
+ continue;
+ }
+ mergeData->currentRule = rule;
+ g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
+ if(difference > mergeData->currentRule->difference) {
+ best_matchEnt = currentRule->targetEnt;
+ difference = currentRule->difference;
+ rule = currentRule;
+ mergeData->mergeList = g_list_prepend(mergeData->mergeList,rule);
+ qof_book_merge_orphan_check(difference, rule, mergeData);
+ }
+ orphans = g_slist_next(orphans);
+ }
+ g_slist_free(mergeData->orphan_list);
+ g_slist_free(targets);
+}
+
+static void
+qof_book_mergeForeachTarget (QofEntity* targetEnt, gpointer user_data)
+{
+ qof_book_mergeData *mergeData;
+
+ g_return_if_fail(user_data != NULL);
+ mergeData = (qof_book_mergeData*)user_data;
+ g_return_if_fail(targetEnt != NULL);
+ mergeData->targetList = g_slist_prepend(mergeData->targetList,targetEnt);
+}
+
+static void
+qof_book_mergeForeachTypeTarget ( QofObject* merge_obj, gpointer user_data)
+{
+ qof_book_mergeData *mergeData;
+ qof_book_mergeRule *currentRule;
+
+ g_return_if_fail(user_data != NULL);
+ mergeData = (qof_book_mergeData*)user_data;
+ currentRule = mergeData->currentRule;
+ g_return_if_fail(currentRule != NULL);
+ g_return_if_fail(merge_obj != NULL);
+ if(safe_strcmp(merge_obj->e_type, currentRule->importEnt->e_type) == 0) {
+ qof_object_foreach(currentRule->importEnt->e_type, mergeData->targetBook,
+ qof_book_mergeForeachTarget, user_data);
+ }
+}
+
+static void
+qof_book_mergeForeach ( QofEntity* mergeEnt, gpointer user_data)
+{
+ qof_book_mergeRule *mergeRule, *currentRule;
+ qof_book_mergeData *mergeData;
+ QofEntity *targetEnt, *best_matchEnt;
+ GUID *g;
+ double difference;
+ GSList *c;
+
+ g_return_if_fail(user_data != NULL);
+ mergeData = (qof_book_mergeData*)user_data;
+ g_return_if_fail(mergeEnt != NULL);
+ currentRule = mergeData->currentRule;
+ g_return_if_fail(currentRule != NULL);
+ g = guid_malloc();
+ *g = mergeEnt->guid;
+ mergeRule = g_new(qof_book_mergeRule,1);
+ mergeRule->importEnt = mergeEnt;
+ mergeRule->difference = difference = 0;
+ mergeRule->mergeAbsolute = FALSE;
+ mergeRule->mergeResult = MERGE_UNDEF;
+ mergeRule->updated = FALSE;
+ mergeRule->mergeType = mergeEnt->e_type;
+ mergeRule->mergeLabel = qof_object_get_type_label(mergeEnt->e_type);
+ mergeRule->mergeParam = g_slist_copy(mergeData->mergeObjectParams);
+ mergeRule->linkedEntList = NULL;
+ mergeData->currentRule = mergeRule;
+ targetEnt = best_matchEnt = NULL;
+ targetEnt = qof_collection_lookup_entity (
+ qof_book_get_collection (mergeData->targetBook, mergeEnt->e_type), g);
+ if( targetEnt != NULL) {
+ mergeRule->mergeAbsolute = TRUE;
+ mergeRule->targetEnt = targetEnt;
+ g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
+ mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+ mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
+ return;
+ }
+ /* no absolute match exists */
+ g_slist_free(mergeData->targetList);
+ mergeData->targetList = NULL;
+ qof_object_foreach_type(qof_book_mergeForeachTypeTarget, mergeData);
+ if(g_slist_length(mergeData->targetList) == 0) {
+ mergeRule->mergeResult = MERGE_NEW;
+ }
+ difference = g_slist_length(mergeRule->mergeParam);
+ c = g_slist_copy(mergeData->targetList);
+ while(c != NULL) {
+ mergeRule->targetEnt = c->data;
+ currentRule = mergeRule;
+ /* compare two entities and sum the differences */
+ g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
+ if(mergeRule->difference == 0) {
+ /* check if this is a better match than one already assigned */
+ best_matchEnt = mergeRule->targetEnt;
+ mergeRule->mergeResult = MERGE_DUPLICATE;
+ difference = 0;
+ mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+ g_slist_free(c);
+ guid_free(g);
+ /* exact match, return */
+ return;
+ }
+ if(difference > mergeRule->difference) {
+ /* The chosen targetEnt determines the parenting of any child object */
+ /* check if this is a better match than one already assigned */
+ best_matchEnt = mergeRule->targetEnt;
+ difference = mergeRule->difference;
+ /* Use match to lookup the previous entity that matched this targetEnt (if any)
+ and remove targetEnt from the rule for that mergeEnt.
+ Add the previous mergeEnt to orphan_list.
+ */
+ qof_book_merge_orphan_check(difference, mergeRule, mergeData);
+ }
+ c = g_slist_next(c);
+ }
+ g_slist_free(c);
+ if(best_matchEnt != NULL ) {
+ mergeRule->targetEnt = best_matchEnt;
+ mergeRule->difference = difference;
+ /* Set this entity in the target_table in case a better match can be made
+ with the next mergeEnt. */
+ g_hash_table_insert(mergeData->target_table, mergeRule->targetEnt, mergeRule);
+ /* compare again with the best partial match */
+ g_return_if_fail(qof_book_mergeCompare(mergeData) != -1);
+ mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+ }
+ else {
+ mergeRule->targetEnt = NULL;
+ mergeRule->difference = 0;
+ mergeRule->mergeResult = MERGE_NEW;
+ mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList);
+ }
+ mergeData->mergeList = g_list_prepend(mergeData->mergeList,mergeRule);
+ guid_free(g);
+ /* return to qof_book_mergeInit */
+}
+
+static void
+qof_book_mergeForeachParam( QofParam* param, gpointer user_data)
+{
+ qof_book_mergeData *mergeData;
+
+ g_return_if_fail(user_data != NULL);
+ mergeData = (qof_book_mergeData*)user_data;
+ g_return_if_fail(param != NULL);
+ if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
+ mergeData->mergeObjectParams = g_slist_append(mergeData->mergeObjectParams, param);
+ }
+}
+
+static void
+qof_book_mergeForeachType ( QofObject* merge_obj, gpointer user_data)
+{
+ qof_book_mergeData *mergeData;
+
+ g_return_if_fail(user_data != NULL);
+ mergeData = (qof_book_mergeData*)user_data;
+ g_return_if_fail((merge_obj != NULL));
+ /* Skip unsupported objects */
+ if((merge_obj->create == NULL)||(merge_obj->foreach == NULL)){
+ DEBUG (" merge_obj QOF support failed %s", merge_obj->e_type);
+ return;
+ }
+ if(mergeData->mergeObjectParams != NULL) g_slist_free(mergeData->mergeObjectParams);
+ mergeData->mergeObjectParams = NULL;
+ qof_class_param_foreach(merge_obj->e_type, qof_book_mergeForeachParam , mergeData);
+ qof_object_foreach(merge_obj->e_type, mergeData->mergeBook, qof_book_mergeForeach, mergeData);
+}
+
+static void
+qof_book_mergeRuleCB(gpointer rule, gpointer arg)
+{
+ struct qof_book_mergeRuleIterate *iter;
+ qof_book_mergeData *mergeData;
+
+ g_return_if_fail(arg != NULL);
+ iter = (struct qof_book_mergeRuleIterate*)arg;
+ mergeData = iter->data;
+ g_return_if_fail(mergeData != NULL);
+ g_return_if_fail(mergeData->abort == FALSE);
+ iter->fcn (mergeData, (qof_book_mergeRule*)rule, iter->remainder);
+ iter->data = mergeData;
+ iter->remainder--;
+}
+
+static void
+qof_book_mergeCommitRuleLoop(
+ qof_book_mergeData *mergeData,
+ qof_book_mergeRule *rule,
+ guint remainder)
+{
+ QofInstance *inst;
+ gboolean registered_type;
+ QofEntity *referenceEnt;
+ /* cm_ prefix used for variables that hold the data to commit */
+ QofCollection *cm_coll;
+ QofParam *cm_param;
+ gchar *cm_string;
+ const GUID *cm_guid;
+ KvpFrame *cm_kvp;
+ /* function pointers and variables for parameter getters that don't use pointers normally */
+ gnc_numeric cm_numeric, (*numeric_getter) (QofEntity*, QofParam*);
+ double cm_double, (*double_getter) (QofEntity*, QofParam*);
+ gboolean cm_boolean, (*boolean_getter) (QofEntity*, QofParam*);
+ gint32 cm_i32, (*int32_getter) (QofEntity*, QofParam*);
+ gint64 cm_i64, (*int64_getter) (QofEntity*, QofParam*);
+ Timespec cm_date, (*date_getter) (QofEntity*, QofParam*);
+ char cm_char, (*char_getter) (QofEntity*, QofParam*);
+ /* function pointers to the parameter setters */
+ void (*string_setter) (QofEntity*, const char*);
+ void (*date_setter) (QofEntity*, Timespec);
+ void (*numeric_setter) (QofEntity*, gnc_numeric);
+ void (*guid_setter) (QofEntity*, const GUID*);
+ void (*double_setter) (QofEntity*, double);
+ void (*boolean_setter) (QofEntity*, gboolean);
+ void (*i32_setter) (QofEntity*, gint32);
+ void (*i64_setter) (QofEntity*, gint64);
+ void (*char_setter) (QofEntity*, char);
+ void (*kvp_frame_setter) (QofEntity*, KvpFrame*);
+ void (*reference_setter) (QofEntity*, QofEntity*);
+ void (*collection_setter)(QofEntity*, QofCollection*);
+
+ g_return_if_fail(rule != NULL);
+ g_return_if_fail(mergeData != NULL);
+ g_return_if_fail(mergeData->targetBook != NULL);
+ g_return_if_fail((rule->mergeResult != MERGE_NEW)||(rule->mergeResult != MERGE_UPDATE));
+ /* create a new object for MERGE_NEW */
+ /* The new object takes the GUID from the import to retain an absolute match */
+ if(rule->mergeResult == MERGE_NEW) {
+ inst = (QofInstance*)qof_object_new_instance(rule->importEnt->e_type, mergeData->targetBook);
+ g_return_if_fail(inst != NULL);
+ rule->targetEnt = &inst->entity;
+ qof_entity_set_guid(rule->targetEnt, qof_entity_get_guid(rule->importEnt));
+ }
+ /* currentRule->targetEnt is now set,
+ 1. by an absolute GUID match or
+ 2. by best_matchEnt and difference or
+ 3. by MERGE_NEW.
+ */
+ while(rule->mergeParam != NULL) {
+ registered_type = FALSE;
+ g_return_if_fail(rule->mergeParam->data);
+ cm_param = rule->mergeParam->data;
+ rule->mergeType = cm_param->param_type;
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_STRING) == 0) {
+ cm_string = cm_param->param_getfcn(rule->importEnt, cm_param);
+ string_setter = (void(*)(QofEntity*, const char*))cm_param->param_setfcn;
+ if(string_setter != NULL) { string_setter(rule->targetEnt, cm_string); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_DATE) == 0) {
+ date_getter = (Timespec (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+ cm_date = date_getter(rule->importEnt, cm_param);
+ date_setter = (void(*)(QofEntity*, Timespec))cm_param->param_setfcn;
+ if(date_setter != NULL) { date_setter(rule->targetEnt, cm_date); }
+ registered_type = TRUE;
+ }
+ if((safe_strcmp(rule->mergeType, QOF_TYPE_NUMERIC) == 0) ||
+ (safe_strcmp(rule->mergeType, QOF_TYPE_DEBCRED) == 0)) {
+ numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+ cm_numeric = numeric_getter(rule->importEnt, cm_param);
+ numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_param->param_setfcn;
+ if(numeric_setter != NULL) { numeric_setter(rule->targetEnt, cm_numeric); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_GUID) == 0) {
+ cm_guid = cm_param->param_getfcn(rule->importEnt, cm_param);
+ guid_setter = (void(*)(QofEntity*, const GUID*))cm_param->param_setfcn;
+ if(guid_setter != NULL) { guid_setter(rule->targetEnt, cm_guid); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_INT32) == 0) {
+ int32_getter = (gint32 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_i32 = int32_getter(rule->importEnt, cm_param);
+ i32_setter = (void(*)(QofEntity*, gint32))cm_param->param_setfcn;
+ if(i32_setter != NULL) { i32_setter(rule->targetEnt, cm_i32); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_INT64) == 0) {
+ int64_getter = (gint64 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_i64 = int64_getter(rule->importEnt, cm_param);
+ i64_setter = (void(*)(QofEntity*, gint64))cm_param->param_setfcn;
+ if(i64_setter != NULL) { i64_setter(rule->targetEnt, cm_i64); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_DOUBLE) == 0) {
+ double_getter = (double (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_double = double_getter(rule->importEnt, cm_param);
+ double_setter = (void(*)(QofEntity*, double))cm_param->param_setfcn;
+ if(double_setter != NULL) { double_setter(rule->targetEnt, cm_double); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_BOOLEAN) == 0){
+ boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_boolean = boolean_getter(rule->importEnt, cm_param);
+ boolean_setter = (void(*)(QofEntity*, gboolean))cm_param->param_setfcn;
+ if(boolean_setter != NULL) { boolean_setter(rule->targetEnt, cm_boolean); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_KVP) == 0) {
+ cm_kvp = kvp_frame_copy(cm_param->param_getfcn(rule->importEnt,cm_param));
+ kvp_frame_setter = (void(*)(QofEntity*, KvpFrame*))cm_param->param_setfcn;
+ if(kvp_frame_setter != NULL) { kvp_frame_setter(rule->targetEnt, cm_kvp); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_CHAR) == 0) {
+ char_getter = (char (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_char = char_getter(rule->importEnt,cm_param);
+ char_setter = (void(*)(QofEntity*, char))cm_param->param_setfcn;
+ if(char_setter != NULL) { char_setter(rule->targetEnt, cm_char); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_COLLECT) == 0) {
+ cm_coll = cm_param->param_getfcn(rule->importEnt, cm_param);
+ collection_setter = (void(*)(QofEntity*, QofCollection*))cm_param->param_setfcn;
+ if(collection_setter != NULL) { collection_setter(rule->targetEnt, cm_coll); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(rule->mergeType, QOF_TYPE_CHOICE) == 0) {
+ referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param);
+ reference_setter = (void(*)(QofEntity*, QofEntity*))cm_param->param_setfcn;
+ if(reference_setter != NULL)
+ {
+ reference_setter(rule->targetEnt, referenceEnt);
+ }
+ registered_type = TRUE;
+ }
+ if(registered_type == FALSE) {
+ referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param);
+ if(referenceEnt) {
+ reference_setter = (void(*)(QofEntity*, QofEntity*))cm_param->param_setfcn;
+ if(reference_setter != NULL)
+ {
+ reference_setter(rule->targetEnt, referenceEnt);
+ }
+ }
+ }
+ rule->mergeParam = g_slist_next(rule->mergeParam);
+ }
+}
+/* ================================================================ */
+/* API functions. */
+
+qof_book_mergeData*
+qof_book_mergeInit( QofBook *importBook, QofBook *targetBook)
+{
+ qof_book_mergeData *mergeData;
+ qof_book_mergeRule *currentRule;
+ GList *check;
+
+ g_return_val_if_fail((importBook != NULL)&&(targetBook != NULL), NULL);
+ mergeData = g_new(qof_book_mergeData, 1);
+ mergeData->abort = FALSE;
+ mergeData->mergeList = NULL;
+ mergeData->targetList = NULL;
+ mergeData->mergeBook = importBook;
+ mergeData->targetBook = targetBook;
+ mergeData->mergeObjectParams = NULL;
+ mergeData->orphan_list = NULL;
+ mergeData->target_table = g_hash_table_new( g_direct_hash, qof_book_merge_rule_cmp);
+ currentRule = g_new(qof_book_mergeRule, 1);
+ mergeData->currentRule = currentRule;
+ qof_object_foreach_type(qof_book_mergeForeachType, mergeData);
+ g_return_val_if_fail(mergeData->mergeObjectParams, NULL);
+ if(mergeData->orphan_list != NULL) {
+ qof_book_merge_match_orphans(mergeData);
+ }
+
+ check = g_list_copy(mergeData->mergeList);
+ while(check != NULL) {
+ currentRule = check->data;
+ if(currentRule->mergeResult == MERGE_INVALID) {
+ mergeData->abort = TRUE;
+ return(NULL);
+ }
+ check = g_list_next(check);
+ }
+ g_list_free(check);
+ return mergeData;
+}
+
+void
+qof_book_merge_abort (qof_book_mergeData *mergeData)
+{
+ qof_book_mergeRule *currentRule;
+
+ g_return_if_fail(mergeData != NULL);
+ while(mergeData->mergeList != NULL) {
+ currentRule = mergeData->mergeList->data;
+ g_slist_free(currentRule->linkedEntList);
+ g_slist_free(currentRule->mergeParam);
+ g_free(mergeData->mergeList->data);
+ if(currentRule) {
+ g_slist_free(currentRule->linkedEntList);
+ g_slist_free(currentRule->mergeParam);
+ g_free(currentRule);
+ }
+ mergeData->mergeList = g_list_next(mergeData->mergeList);
+ }
+ g_list_free(mergeData->mergeList);
+ g_slist_free(mergeData->mergeObjectParams);
+ g_slist_free(mergeData->targetList);
+ if(mergeData->orphan_list != NULL) { g_slist_free(mergeData->orphan_list); }
+ g_hash_table_destroy(mergeData->target_table);
+ g_free(mergeData);
+}
+
+/* The QOF_TYPE_DATE output format from
+qof_book_merge_param_as_string has been changed to QSF_XSD_TIME,
+a UTC formatted timestring: 2005-01-01T10:55:23Z
+If you change QOF_UTC_DATE_FORMAT, change
+backend/file/qsf-xml.c : qsf_entity_foreach to
+reformat to QSF_XSD_TIME or the QSF XML will
+FAIL the schema validation and QSF exports will become invalid.
+
+The QOF_TYPE_BOOLEAN is lowercase for the same reason.
+*/
+char*
+qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt)
+{
+ gchar *param_string, param_date[QOF_DATE_STRING_LENGTH];
+ char param_sa[GUID_ENCODING_LENGTH + 1];
+ QofType paramType;
+ const GUID *param_guid;
+ time_t param_t;
+ gnc_numeric param_numeric, (*numeric_getter) (QofEntity*, QofParam*);
+ Timespec param_ts, (*date_getter) (QofEntity*, QofParam*);
+ double param_double, (*double_getter) (QofEntity*, QofParam*);
+ gboolean param_boolean, (*boolean_getter) (QofEntity*, QofParam*);
+ gint32 param_i32, (*int32_getter) (QofEntity*, QofParam*);
+ gint64 param_i64, (*int64_getter) (QofEntity*, QofParam*);
+ char param_char, (*char_getter) (QofEntity*, QofParam*);
+
+ param_string = NULL;
+ paramType = qtparam->param_type;
+ if(safe_strcmp(paramType, QOF_TYPE_STRING) == 0) {
+ param_string = g_strdup(qtparam->param_getfcn(qtEnt,qtparam));
+ if(param_string == NULL) { param_string = ""; }
+ return param_string;
+ }
+ if(safe_strcmp(paramType, QOF_TYPE_DATE) == 0) {
+ date_getter = (Timespec (*)(QofEntity*, QofParam*))qtparam->param_getfcn;
+ param_ts = date_getter(qtEnt, qtparam);
+ param_t = timespecToTime_t(param_ts);
+ strftime(param_date, QOF_DATE_STRING_LENGTH, QOF_UTC_DATE_FORMAT, gmtime(¶m_t));
+ param_string = g_strdup(param_date);
+ return param_string;
+ }
+ if((safe_strcmp(paramType, QOF_TYPE_NUMERIC) == 0) ||
+ (safe_strcmp(paramType, QOF_TYPE_DEBCRED) == 0)) {
+ numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ param_numeric = numeric_getter(qtEnt,qtparam);
+ param_string = g_strdup(gnc_numeric_to_string(param_numeric));
+ return param_string;
+ }
+ if(safe_strcmp(paramType, QOF_TYPE_GUID) == 0) {
+ param_guid = qtparam->param_getfcn(qtEnt,qtparam);
+ guid_to_string_buff(param_guid, param_sa);
+ param_string = g_strdup(param_sa);
+ return param_string;
+ }
+ if(safe_strcmp(paramType, QOF_TYPE_INT32) == 0) {
+ int32_getter = (gint32 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ param_i32 = int32_getter(qtEnt, qtparam);
+ param_string = g_strdup_printf("%d", param_i32);
+ return param_string;
+ }
+ if(safe_strcmp(paramType, QOF_TYPE_INT64) == 0) {
+ int64_getter = (gint64 (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ param_i64 = int64_getter(qtEnt, qtparam);
+ param_string = g_strdup_printf("%" G_GINT64_FORMAT, param_i64);
+ return param_string;
+ }
+ if(safe_strcmp(paramType, QOF_TYPE_DOUBLE) == 0) {
+ double_getter = (double (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ param_double = double_getter(qtEnt, qtparam);
+ param_string = g_strdup_printf("%f", param_double);
+ return param_string;
+ }
+ if(safe_strcmp(paramType, QOF_TYPE_BOOLEAN) == 0){
+ boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ param_boolean = boolean_getter(qtEnt, qtparam);
+ /* Boolean values need to be lowercase for QSF validation. */
+ if(param_boolean == TRUE) { param_string = g_strdup("true"); }
+ else { param_string = g_strdup("false"); }
+ return param_string;
+ }
+ /* "kvp" contains repeating values, cannot be a single string for the frame. */
+ if(safe_strcmp(paramType, QOF_TYPE_KVP) == 0) { return param_string; }
+ if(safe_strcmp(paramType, QOF_TYPE_CHAR) == 0) {
+ char_getter = (char (*)(QofEntity*, QofParam*)) qtparam->param_getfcn;
+ param_char = char_getter(qtEnt, qtparam);
+ param_string = g_strdup_printf("%c", param_char);
+ return param_string;
+ }
+ return NULL;
+}
+
+qof_book_mergeData*
+qof_book_mergeUpdateResult(qof_book_mergeData *mergeData,
+ qof_book_mergeResult tag)
+{
+ qof_book_mergeRule *resolved;
+
+ g_return_val_if_fail((mergeData != NULL), NULL);
+ g_return_val_if_fail((tag > 0), NULL);
+ g_return_val_if_fail((tag != MERGE_REPORT), NULL);
+ resolved = mergeData->currentRule;
+ g_return_val_if_fail((resolved != NULL), NULL);
+ if((resolved->mergeAbsolute == TRUE)&&(tag == MERGE_DUPLICATE))
+ {
+ tag = MERGE_ABSOLUTE;
+ }
+ if((resolved->mergeAbsolute == TRUE)&&(tag == MERGE_NEW))
+ {
+ tag = MERGE_UPDATE;
+ }
+ if((resolved->mergeAbsolute == FALSE)&& (tag == MERGE_ABSOLUTE))
+ {
+ tag = MERGE_DUPLICATE;
+ }
+ if((resolved->mergeResult == MERGE_NEW)&&(tag == MERGE_UPDATE))
+ {
+ tag = MERGE_NEW;
+ }
+ if(resolved->updated == FALSE) { resolved->mergeResult = tag; }
+ resolved->updated = TRUE;
+ if(tag >= MERGE_INVALID) {
+ mergeData->abort = TRUE;
+ mergeData->currentRule = resolved;
+ return NULL;
+ }
+ mergeData->currentRule = resolved;
+ return mergeData;
+}
+
+int
+qof_book_mergeCommit( qof_book_mergeData *mergeData )
+{
+ qof_book_mergeRule *currentRule;
+ GList *check;
+
+ g_return_val_if_fail(mergeData != NULL, -1);
+ g_return_val_if_fail(mergeData->mergeList != NULL, -1);
+ g_return_val_if_fail(mergeData->targetBook != NULL, -1);
+ if(mergeData->abort == TRUE) return -1;
+ check = g_list_copy(mergeData->mergeList);
+ g_return_val_if_fail(check != NULL, -1);
+ while(check != NULL) {
+ currentRule = check->data;
+ if(currentRule->mergeResult == MERGE_INVALID) {
+ qof_book_merge_abort(mergeData);
+ return(-2);
+ }
+ if(currentRule->mergeResult == MERGE_REPORT) {
+ g_list_free(check);
+ return 1;
+ }
+ check = g_list_next(check);
+ }
+ qof_book_mergeCommitForeach( qof_book_mergeCommitRuleLoop, MERGE_NEW, mergeData);
+ qof_book_mergeCommitForeach( qof_book_mergeCommitRuleLoop, MERGE_UPDATE, mergeData);
+ /* Placeholder for QofObject merge_helper_cb - all objects and all parameters set */
+ while(mergeData->mergeList != NULL) {
+ currentRule = mergeData->mergeList->data;
+ g_slist_free(currentRule->mergeParam);
+ g_slist_free(currentRule->linkedEntList);
+ mergeData->mergeList = g_list_next(mergeData->mergeList);
+ }
+ g_list_free(mergeData->mergeList);
+ g_slist_free(mergeData->mergeObjectParams);
+ g_slist_free(mergeData->targetList);
+ if(mergeData->orphan_list != NULL) { g_slist_free(mergeData->orphan_list); }
+ g_hash_table_destroy(mergeData->target_table);
+ g_free(mergeData);
+ return 0;
+}
+
+void
+qof_book_mergeRuleForeach( qof_book_mergeData *mergeData,
+ qof_book_mergeRuleForeachCB cb,
+ qof_book_mergeResult mergeResult )
+{
+ struct qof_book_mergeRuleIterate iter;
+ qof_book_mergeRule *currentRule;
+ GList *matching_rules;
+
+ g_return_if_fail(cb != NULL);
+ g_return_if_fail(mergeData != NULL);
+ currentRule = mergeData->currentRule;
+ g_return_if_fail(mergeResult > 0);
+ g_return_if_fail(mergeResult != MERGE_INVALID);
+ g_return_if_fail(mergeData->abort == FALSE);
+ iter.fcn = cb;
+ iter.data = mergeData;
+ matching_rules = NULL;
+ iter.ruleList = g_list_copy(mergeData->mergeList);
+ while(iter.ruleList!=NULL) {
+ currentRule = iter.ruleList->data;
+ if(currentRule->mergeResult == mergeResult) {
+ matching_rules = g_list_prepend(matching_rules, currentRule);
+ }
+ iter.ruleList = g_list_next(iter.ruleList);
+ }
+ iter.remainder = g_list_length(matching_rules);
+ g_list_foreach (matching_rules, qof_book_mergeRuleCB, &iter);
+ g_list_free(matching_rules);
+}
+
+/* End of file. */
+/* ==================================================================== */
Added: gnucash/trunk/lib/libqof/qof/qof_book_merge.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qof_book_merge.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qof_book_merge.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,461 @@
+/*********************************************************************
+ * qof_book_merge.h -- api for QofBook merge with collision handling *
+ * Copyright (C) 2004 Neil Williams <linux at codehelp.co.uk> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ ********************************************************************/
+
+#ifndef QOFBOOKMERGE_H
+#define QOFBOOKMERGE_H
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#define QOF_MOD_MERGE "qof-merge"
+
+/** @addtogroup BookMerge
+
+<b>Collision handling principles.</b>\n
+\n
+ -# Always check for a ::GUID first and compare. qof_book_merge only accepts valid ::QofBook
+ data and therefore ALL objects in the import book will include valid GUID's.
+ -# If the original import data did not contain a GUID (e.g. an external non-GnuCash source)
+ the GUID values will have been created during the import and will not match any existing
+ GUID's in the target book so objects that do not have a GUID match cannot be assumed to
+ be ::MERGE_NEW - parameter values must be checked.
+ -# If import contains data from closed books, store the data from the closed
+ books in the current book as active. i.e. re-open the books.
+
+- If a GUID match exists, set qof_book_mergeRule::mergeAbsolute to \a TRUE.
+ -# If ALL parameters in the import object match the target object with the same \a GUID,
+ set ::qof_book_mergeResult to \a MERGE_ABSOLUTE.
+ -# If any parameters differ, set ::MERGE_UPDATE.
+- If the import object \a GUID does not match an existing object,
+mergeAbsolute is unchanged from the default \a FALSE
+The parameter values of the object are compared to other objects of the same
+type in the target book.
+ -# If the same data exists in the target book with a different GUID, the object
+ is tagged as DUPLICATE.
+ -# If the data has changed, the object is tagged as REPORT.
+ -# If the data does not match, the object is tagged as NEW
+
+More information is at http://code.neil.williamsleesmill.me.uk/
+
+Each foreach function uses g_return_if_fail checks to protect the target book. If
+any essential data is missing, the loop returns without changing the target book.
+Note that this will not set or return an error value. However, g_return is only
+used for critical errors that arise from programming errors, not for invalid import data
+which should be cleaned up before creating the import QofBook.
+
+Only ::qof_book_mergeUpdateResult and ::qof_book_mergeCommit return
+any error values to the calling process. ::qof_book_mergeInit returns a
+pointer to the ::qof_book_mergeData struct - the calling process needs to
+make sure this is non-NULL to know that the Init has been successful.
+
+ @{
+*/
+/** @file qof_book_merge.h
+ @brief API for merging two \c QofBook structures with collision handling
+ @author Copyright (c) 2004-2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#include <glib.h>
+#include "gnc-engine-util.h"
+#include "qofbook.h"
+#include "qofclass.h"
+#include "qofobject.h"
+#include "qofinstance.h"
+#include "gnc-trace.h"
+
+/** \brief Results of collisions and user resolution.
+
+All rules are initialised as ::MERGE_UNDEF.
+Once the comparison is complete, each object within the import will be
+updated.
+
+::MERGE_ABSOLUTE, ::MERGE_NEW, ::MERGE_DUPLICATE and ::MERGE_UPDATE can be reported
+to the user along with all ::MERGE_REPORT objects for confirmation.
+It may be useful later to allow \a MERGE_ABSOLUTE, \a MERGE_NEW, \a MERGE_DUPLICATE and
+\a MERGE_UPDATE to not be reported, if the user sets a preferences option
+for each result. (Always accept new items: Y/N default NO, ignores all
+MERGE_NEW if set to Y etc.) This option would not require any changes
+to qof_book_merge.
+
+\a MERGE_NEW, \a MERGE_DUPLICATE and \a MERGE_UPDATE are only actioned after
+conflicts are resolved by the user using a dialog and all \a MERGE_REPORT objects are
+re-assigned to one of MERGE_NEW, MERGE_DUPLICATE or MERGE_UPDATE. There is no automatic
+merge, even if no entities are tagged as MERGE_REPORT, the calling process must still
+check for REPORT items using ::qof_book_mergeRuleForeach and call ::qof_book_mergeCommit.
+
+\a MERGE_INVALID data should be rare and allows for user-abort - the imported file/source
+ may be corrupted and the prescence of invalid data should raise concerns that
+ the rest of the data may be corrupted, damaged or otherwise altered. If any entity is
+ tagged as MERGE_INVALID, the merge operation will abort and leave the target book
+ completely unchanged.
+
+\a MERGE_ABSOLUTE is only used for a complete match. The import object contains
+the same data in the same parameters with no omissions or amendments. If any data is missing,
+amended or added, the data is labelled \a MERGE_UPDATE.
+
+ Every piece of data has a corresponding result. Only when the count of items labelled
+ \a MERGE_REPORT is equal to zero are \a MERGE_NEW and \a MERGE_UPDATE
+ items added to the existing book.\n \a MERGE_DUPLICATE items are silently ignored.
+ Aborting the dialog/process (by the user or in a program crash) at any point before the
+ final commit leaves the existing book completely untouched.
+*/
+typedef enum {
+ MERGE_UNDEF, /**< default value before comparison is made. */
+ MERGE_ABSOLUTE, /**< GUID exact match, no new data - \b ignore */
+ MERGE_NEW, /**< import object does \b not exist in the target book - \b add */
+ MERGE_REPORT, /**< import object needs user intervention - \b report */
+ MERGE_DUPLICATE, /**< import object with different GUID exactly matches existing GUID - \b ignore */
+ MERGE_UPDATE, /**< import object matches an existing entity but includes new or
+ modified parameter data - \b update */
+ MERGE_INVALID /**< import object didn't match registered object or parameter
+ types or user decided to abort - \b abort */
+}qof_book_mergeResult;
+
+/** \brief One rule per entity, built into a single GList for the entire merge
+
+All rules are stored in the GList qof_book_mergeData::mergeList.
+
+If the ::GUID matches it's the always same semantic object,
+regardless of whether other data fields are changed.
+\n
+The boolean value mergeAbsolute defaults to \c FALSE
+
+NOTE 1: if mergeAbsolute == \c TRUE, ::qof_book_mergeResult will still be set to
+::MERGE_UPDATE if parameters within this entity have been modified.
+
+NOTE 2: ::qof_book_merge_param_as_string returns \b string representations of the parameter
+data that is causing a collision. These values must \b NOT be used to set the target
+parameter - the function is provided for display purposes only, to make it simple to
+explain the collision to the user using MERGE_REPORT and the dialog.
+
+The GHashTable targetTable in qof_book_mergeRule will probably replace the GSList of the
+same name in mergeData.
+
+*/
+
+typedef struct
+{
+ /* internal counters and reference variables */
+ gboolean mergeAbsolute; /**< Only set if the GUID of the import matches the target */
+ double difference; /**< used to find best match in a book where no GUID matches */
+ gboolean updated; /**< prevent the mergeResult from being overwritten. */
+ /* rule objects set from or by external calls */
+ QofIdType mergeType; /**< type of comparison required for check for collision */
+ const char* mergeLabel; /**< Descriptive label for the object type, useful for the
+ user intervention dialog. */
+ GSList *mergeParam; /**< list of usable parameters for the object type */
+ GSList *linkedEntList; /**< list of complex data types included in this object.
+
+ linkedEntList contains an ::QofEntity reference to any parameter that is not
+ one of the core QOF_TYPE data types. This entity must be already registered with QOF
+ and the results of the comparison for the linked entity will modulate the mergeResult
+ of this object. e.g. if an invoice is the same value but for a different customer,
+ the invoice will be set to MERGE_REPORT and the customer as MERGE_NEW.
+ */
+ qof_book_mergeResult mergeResult; /**< result of comparison with main ::QofBook */
+ QofEntity *importEnt; /**< pointer to the current entity in the import book. */
+ QofEntity *targetEnt; /**< pointer to the corresponding entity in the target book, if any. */
+}qof_book_mergeRule;
+
+
+/** \brief mergeData contains the essential context data for any merge.
+
+Used to dictate what to merge, how to merge it, where to get the new data and
+where to put the amended data.
+
+Combines lists of \a ::QofParam, \a ::QofEntity and \a ::qof_book_mergeRule into one struct that
+can be easily passed between callbacks. Also holds the pointers to the import and target ::QofBook
+structures.
+
+- targetList and mergeObjectParams change each time a new object type is set for compare.
+- mergeList is the complete list of rules for all objects in the import book.
+
+*/
+typedef struct
+{
+ GSList *mergeObjectParams; /**< GSList of ::QofParam details for each parameter in the current object. */
+ GList *mergeList; /**< GList of all ::qof_book_mergeRule rules for the merge operation. */
+ GSList *targetList; /**< GSList of ::QofEntity * for each object of this type in the target book */
+ QofBook *mergeBook; /**< pointer to the import book for this merge operation. */
+ QofBook *targetBook; /**< pointer to the target book for this merge operation. */
+ gboolean abort; /**< set to TRUE if MERGE_INVALID is set. */
+ qof_book_mergeRule *currentRule; /**< placeholder for the rule currently being tested or applied. */
+ GSList *orphan_list; /**< List of QofEntity's that need to be rematched.
+
+ When one QofEntity has a lower difference to the targetEnt than the previous best_match,
+ the new match takes precedence. This list holds those orphaned entities that are not a good
+ enough match so that these can be rematched later. The ranking is handled using
+ the private qof_entity_rating struct and the GHashTable ::qof_book_mergeData::target_table.
+ */
+ GHashTable *target_table; /**< The GHashTable to hold the qof_entity_rating values. */
+
+}qof_book_mergeData;
+
+
+/* ======================================================================== */
+/** @name qof_book_merge API
+ @{
+*/
+/** \brief Initialise the qof_book_merge process
+
+ First function of the qof_book_merge API. Every merge must begin with Init.
+
+ Requires the book to import (::QofBook *) and the book to receive the import, the target book
+ (::QofBook *). Returns a pointer to ::qof_book_mergeData which must be checked for a
+ NULL before continuing. \n
+Process:
+
+ -# Invoke the callback ::qof_book_mergeForeachType on every registered object class definition.
+ -# Callback obtains the registered parameter list for each object type. This provides run time
+ access to all registered objects and all object parameters without any changes to
+ qof_book_merge - no registered object or parameter is omitted from any merge operation.
+ -# Use ::qof_object_foreach to invoke the callback ::qof_book_mergeForeach, one object at a time
+ on every instance stored in mergeBook. This is the first point where real data from the import
+ book is accessed.
+ -# qof_book_mergeForeach obtains the ::GUID for the object from the import book and runs the first
+ check on the original book, checking for any exact GUID match. With the full parameter list,
+ the rules for this object can be created. If there is a GUID match, the data in each parameter
+ of the import object is compared with the same semantic object in the original book. If there is
+ no GUID in the import object or no GUID match with the original book, the original book is
+ searched to find a parameter match - checking for a ::MERGE_DUPLICATE result.
+ -# ::qof_book_mergeCompare sets the ::qof_book_mergeResult of the comparison.
+ -# Inserts the completed rule into qof_book_mergeData::mergeList GSList.
+
+\return NULL in case of error, otherwise a ::qof_book_mergeData* metadata context.
+
+*/
+qof_book_mergeData*
+qof_book_mergeInit( QofBook *importBook, QofBook *targetBook);
+
+
+/** \brief Definition of the dialog control callback routine
+
+All ::MERGE_REPORT rules must be offered for user intervention using this template.\n
+Commit will fail if any rules are still tagged as \a MERGE_REPORT.
+
+Calling processes are free to also offer MERGE_NEW, MERGE_UPDATE, MERGE_DUPLICATE and
+MERGE_ABSOLUTE for user intervention. Attempting to query MERGE_INVALID rules
+will cause an error.
+
+For an example, consider test_rule_loop, declared as:
+
+<tt>void test_rule_loop(qof_book_mergeData *mergeData, qof_book_mergeRule *rule, guint remainder);\n
+void test_rule_loop(qof_book_mergeData *mergeData, qof_book_mergeRule *rule, guint remainder) \n
+{\n
+ g_return_if_fail(rule != NULL);\n
+ g_return_if_fail(mergeData != NULL);
+ printf("Rule Result %s", rule->mergeType);\n
+ qof_book_mergeUpdateResult(mergeData, rule, MERGE_UPDATE);\n
+}</tt>
+
+The dialog is free to call ::qof_book_mergeUpdateResult in the loop or at the end
+as long as the link between the rule and the result is maintained, e.g. by using a
+GHashTable.
+\n
+The parameters are:
+ - data : pointer to the ::qof_book_mergeData metadata context returned by Init.
+ - rule : pointer to the ::qof_book_mergeRule that generated the collision report
+ - remainder : guint value returned from g_slist_length for the number of other
+ rules remaining with the same result. This might be useful for a progress dialog, it might not.
+ When updating MERGE_REPORT, remainder must equal zero before calling
+ ::qof_book_mergeCommit or the import will abort.
+\n
+
+If the dialog sets \b any rule result to ::MERGE_INVALID, the import will abort when
+::qof_book_mergeCommit is called. It is the responsibility of the calling function to
+handle the error code from ::qof_book_mergeCommit, close the dialog and return.
+The merge routines in these files will already have halted the merge operation and
+freed any memory allocated to merge structures before returning the error code.
+There is no need for the dialog process to report back to qof_book_merge in this situation.
+*/
+typedef void (* qof_book_mergeRuleForeachCB)( qof_book_mergeData*, qof_book_mergeRule*, guint);
+
+/** \brief Dialog Control Callback
+
+This function is designed to be used to iterate over all rules tagged with a specific
+::qof_book_mergeResult value.
+
+ at param callback external loop of type qof_book_mergeRuleForeachCB
+ at param mergeResult ::qof_book_mergeResult value to look up.
+ at param mergeData ::qof_book_mergeData merge context.
+
+\b Note : MERGE_NEW causes a new entity to be created in the target book at Commit
+which is then assigned as the targetEnt of that rule. If mergeResult == MERGE_NEW,
+the rules returned by qof_book_mergeRuleForeach will have a NULL set for the targetEnt.
+This is because Commit has not yet been called and no changes can be made to the target
+book. The calling process must handle the NULL targetEnt and NOT call any param_getfcn
+routines for the target entity. The import entity is available for display.
+
+Uses ::qof_book_get_collection with the qof_book_mergeRule::mergeType object type to
+return a collection of ::QofEntity entities from either the qof_book_mergeData::mergeBook or
+qof_book_mergeData::targetBook. Then uses ::qof_collection_lookup_entity to lookup
+the qof_book_mergeRule::importEnt and again the qof_book_mergeRule::targetEnt to
+return the two specific entities.
+
+*/
+void qof_book_mergeRuleForeach( qof_book_mergeData* mergeData,
+ qof_book_mergeRuleForeachCB callback ,
+ qof_book_mergeResult mergeResult);
+
+/** \brief provides easy string access to parameter data for dialog use
+
+Uses the param_getfcn to retrieve the parameter value as a string, suitable for
+display in dialogs and user intervention output. Within a qof_book_merge context,
+only the parameters used in the merge are available, i.e. parameters where both
+param_getfcn and param_setfcn are not NULL.
+
+Note that the object type description (a full text version of the object name) is
+also available to the dialog as qof_book_mergeRule::mergeLabel.
+
+This allows the dialog to display the description of the object and all parameter data.
+
+*/
+char* qof_book_merge_param_as_string(QofParam *qtparam, QofEntity *qtEnt);
+
+/** \brief called by dialog callback to set the result of user intervention
+
+Set \b any rule result to ::MERGE_INVALID to abort the import when
+::qof_book_mergeCommit is called, without changing the target book.
+
+The calling process should make it absolutely clear that a merge operation
+\b cannot be undone and that a backup copy should always be available
+\b before a merge is initialised.
+
+Recommended method: Only offer three options to the user per rule:
+
+-# Allow import data to be merged into target data
+ - change MERGE_REPORT to MERGE_UPDATE
+-# Allow import data without an exact match to be
+ added as new
+ - change MERGE_REPORT to MERGE_NEW \b IF mergeAbsolute = FALSE
+-# Ignore import data and leave target data unchanged
+ - change MERGE_REPORT to MERGE_ABSOLUTE or MERGE_DUPLICATE
+
+Handle the required result changes in code: Check the value of
+qof_book_mergeRule::mergeAbsolute and use these principles:
+
+To ignore entities tagged as:
+- MERGE_REPORT, you must check the value of mergeAbsolute.
+ - if mergeAbsolute is TRUE, change MERGE_REPORT to MERGE_ABSOLUTE
+ - if mergeAbsolute is FALSE, change MERGE_REPORT to MERGE_DUPLICATE
+- MERGE_NEW, set MERGE_DUPLICATE.
+- MERGE_UPDATE, you must check the value of mergeAbsolute.
+ - if mergeAbsolute is TRUE, change MERGE_UPDATE to MERGE_ABSOLUTE
+ - if mergeAbsolute is FALSE, change MERGE_UPDATE to MERGE_DUPLICATE
+
+To merge entities that are not pre-set to MERGE_NEW, set MERGE_UPDATE.\n
+Attempting to merge an entity when the pre-set value was MERGE_NEW will
+force a change back to MERGE_NEW because no suitable target exists for the
+merge.
+
+To add entities, check mergeAbsolute is FALSE and set MERGE_NEW.\n
+An entity \b only be added if mergeAbsolute is FALSE. Attempting to
+add an entity when mergeAbsolute is TRUE will always force a MERGE_UPDATE.
+
+It is not possible to update the same rule more than once.
+
+-# \b MERGE_NEW is reserved for new objects and is only pre-set if
+all parameters, including GUID, have already failed to match any
+relevant object. ::qof_book_mergeCommit will create new
+entities for all rules tagged as MERGE_NEW.
+ - if mergeAbsolute is TRUE and the user wants to import the
+ data, requests to set MERGE_NEW will be forced to MERGE_UPDATE
+ because an entity with that GUID already exists in the target book.
+ - if MERGE_NEW is pre-set, requests to change to MERGE_UPDATE will be
+ ignored because a new entity is needed.
+-# \b MERGE_UPDATE is reserved for existing objects - ::qof_book_mergeCommit
+will require a matching entity to update and will force a change to back to
+MERGE_NEW if none is known to exist, using the principle above.
+-# \b MERGE_INVALID will cause an abort of the merge process.
+-# \b MERGE_UNDEF and \b MERGE_REPORT cannot be set - the entity result will be unchanged.
+-# \b MERGE_DUPLICATE and \b MERGE_ABSOLUTE are handled identically but are semantically
+ different - qof_book_mergeRule::mergeAbsolute is used to dictate which to set:
+ - if mergeAbsolute is TRUE but MERGE_DUPLICATE is requested,
+ force a change to MERGE_ABSOLUTE.
+ - if mergeAbsolute is FALSE but MERGE_ABSOLUTE is requested,
+ force a change to MERGE_DUPLICATE.
+
+::qof_book_mergeCommit only commits entities tagged
+with MERGE_NEW and MERGE_UPDATE results.
+\n
+Entities tagged with MERGE_ABSOLUTE and MERGE_DUPLICATE results are ignored.
+
+The calling process must check the return value and call
+::qof_book_merge_abort(mergeData) if non-zero.
+
+ at param mergeData the merge context, ::qof_book_mergeData*
+ at param tag the result to attempt to set, ::qof_book_mergeResult
+
+\return -1 if supplied parameters are invalid or NULL, 0 on success.
+
+*/
+qof_book_mergeData*
+qof_book_mergeUpdateResult(qof_book_mergeData *mergeData, qof_book_mergeResult tag);
+
+
+/** \brief Commits the import data to the target book
+
+ The last function in the API and the final part of any qof_book_merge operation.
+
+qof_book_mergeCommit will abort the \b entire merge operation if any rule is set to
+::MERGE_INVALID. It is the responsibility of the calling
+function to handle the error code from ::qof_book_mergeCommit, close the dialog
+and return. qof_book_mergeCommit will already have halted the merge
+operation and freed any memory allocated to all merge structures before returning the error
+code. There is no way for the dialog process to report back to qof_book_merge in this situation.
+
+qof_book_mergeCommit checks for any entities still tagged as ::MERGE_REPORT and then proceeds
+to import all entities tagged as ::MERGE_UPDATE or ::MERGE_NEW into the target book.
+\n
+<b>This final process cannot be UNDONE!</b>\n
+\n
+
+ at param mergeData the merge context, ::qof_book_mergeData*
+
+\return
+ - -2 if any rules are tagged as ::MERGE_INVALID
+ - mergeData will have been g_free'd).
+ - note that this will be before any operations are done on the target
+ QofBook.
+ - -1 if mergeData is invalid or no merge has been initialised with
+ ::qof_book_mergeInit - the calling process must check the value of mergeData
+ - +1 if some entities are still tagged as \a MERGE_REPORT - use
+ ::qof_book_mergeUpdateRule and try again (mergeData is retained).
+ - 0 on success - mergeData will have been freed.
+*/
+int
+qof_book_mergeCommit( qof_book_mergeData *mergeData );
+
+/** \brief Abort the merge and free all memory allocated by the merge
+
+Sometimes, setting ::MERGE_INVALID is insufficient: e.g. if the user aborts the
+merge from outside the functions dealing with the merge ruleset. This function
+causes an immediate abort - the calling process must start again at Init if
+a new merge is required.
+*/
+void
+qof_book_merge_abort(qof_book_mergeData *mergeData);
+
+#endif // QOFBOOKMERGE_H
+/** @} */
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/qofbackend-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbackend-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbackend-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,410 @@
+/********************************************************************\
+ * qofbackend-p.h -- private api for data storage backend *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup Object
+ @{ */
+/** @addtogroup Object_Private
+ Private interfaces, not meant to be used by applications.
+ @{ */
+/** @name Backend_Private
+ Pseudo-object defining how the engine can interact with different
+ back-ends (which may be SQL databases, or network interfaces to
+ remote GnuCash servers. File-io is just one type of backend).
+
+ The callbacks will be called at the appropriate times during
+ a book session to allow the backend to store the data as needed.
+
+ @file qofbackend-p.h
+ @brief private api for data storage backend
+ @author Copyright (c) 2000,2001,2004 Linas Vepstas <linas at linas.org>
+ @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+@{ */
+
+#ifndef QOF_BACKEND_P_H
+#define QOF_BACKEND_P_H
+
+#include "config.h"
+#include "qof-be-utils.h"
+#include "qofbackend.h"
+#include "qofbook.h"
+#include "qofinstance-p.h"
+#include "qofquery.h"
+#include "qofsession.h"
+
+/**
+ * The session_begin() routine gives the backend a second initialization
+ * opportunity. It is suggested that the backend check that
+ * the URL is syntactically correct, and that it is actually
+ * reachable. This is probably(?) a good time to initialize
+ * the actual network connection.
+ *
+ * The 'ignore_lock' argument indicates whether the single-user
+ * lock on the backend should be cleared. The typical GUI sequence
+ * leading to this is: (1) GUI attempts to open the backend
+ * by calling this routine with FALSE==ignore_lock. (2) If backend
+ * error'ed BACKEND_LOCK, then GUI asks user what to do. (3) if user
+ * answers 'break & enter' then this routine is called again with
+ * TRUE==ignore_lock.
+ *
+ * The 'create_if_nonexistent' argument indicates whether this
+ * routine should create a new 'database', if it doesn't already
+ * exist. For example, for a file-backend, this would create the
+ * file, if it didn't already exist. For an SQL backend, this
+ * would create the database (the schema) if it didn't already
+ * exist. This flag is used to implement the 'SaveAs' GUI, where
+ * the user requests to save data to a new backend.
+ *
+ * The load() routine should load the minimal set of application data
+ * needed for the application to be operable at initial startup.
+ * It is assumed that the application will perform a 'run_query()'
+ * to obtain any additional data that it needs. For file-based
+ * backends, it is acceptable for the backend to return all data
+ * at load time; for SQL-based backends, it is acceptable for the
+ * backend to return no data.
+ *
+ * Thus, for example, for GnuCash, the postrges backend returns
+ * the account tree, all currencies, and the pricedb, as these
+ * are needed at startup. It does not have to return any
+ * transactions whatsoever, as these are obtained at a later stage
+ * when a user opens a register, resulting in a query being sent to
+ * the backend.
+ *
+ * (Its OK to send over transactions at this point, but one should
+ * be careful of the network load; also, its possible that whatever
+ * is sent is not what the user wanted anyway, which is why its
+ * better to wait for the query).
+ *
+ * The begin() routine is called when the engine is about to
+ * make a change to a data structure. It can provide an advisory
+ * lock on data.
+ *
+ * The commit() routine commits the changes from the engine to the
+ * backend data storage.
+ *
+ * The rollback() routine is used to revert changes in the engine
+ * and unlock the backend. For transactions it is invoked in one
+ * of two different ways. In one case, the user may hit 'undo' in
+ * the GUI, resulting in xaccTransRollback() being called, which in
+ * turn calls this routine. In this manner, xaccTransRollback()
+ * implements a single-level undo convenience routine for the GUI.
+ * The other way in which this routine gets invoked involves
+ * conflicting edits by two users to the same transaction. The
+ * second user to make an edit will typically fail in
+ * trans_commit_edit(), with trans_commit_edit() returning an error
+ * code. This causes xaccTransCommitEdit() to call
+ * xaccTransRollback() which in turn calls this routine. Thus,
+ * this routine gives the backend a chance to clean up failed
+ * commits.
+ *
+ * If the second user tries to modify a transaction that
+ * the first user deleted, then the backend should set the error
+ * to ERR_BACKEND_MOD_DESTROY from this routine, so that the
+ * engine can properly clean up.
+ *
+ * The compile_query() method compiles a Gnucash query object into
+ * a backend-specific data structure and returns the compiled
+ * query. For an SQL backend, the contents of the query object
+ * need to be turned into a corresponding SQL query statement, and
+ * sent to the database for evaluation.
+ *
+ * The free_query() method frees the data structure returned from
+ * compile_query()
+ *
+ * The run_query() callback takes a compiled query (generated by
+ * compile_query) and runs the query in across the backend,
+ * inserting the responses into the engine. The database will
+ * return a set of splits and transactions, and this callback needs
+ * to poke these into the account-group hierarchy held by the query
+ * object.
+ *
+ * For a network-communications backend, essentially the same is
+ * done, except that this routine would convert the query to wire
+ * protocol, get an answer from the remote server, and push that
+ * into the account-group object.
+ *
+ * Note a peculiar design decision we've used here. The query
+ * callback has returned a list of splits; these could be returned
+ * directly to the caller. They are not. By poking them into the
+ * existing account hierarchy, we are essentially building a local
+ * cache of the split data. This will allow the GnuCash client to
+ * continue functioning even when disconnected from the server:
+ * this is because it will have its local cache of data to work from.
+ *
+ * The sync() routine synchronizes the engine contents to the backend.
+ * This is done by using version numbers (hack alert -- the engine
+ * does not currently contain version numbers).
+ * If the engine contents are newer than what's in the backend, the
+ * data is stored to the backend. If the engine contents are older,
+ * then the engine contents are updated.
+ *
+ * Note that this sync operation is only meant to apply to the
+ * current contents of the engine. This routine is not intended
+ * to be used to fetch account/transaction data from the backend.
+ * (It might pull new splits from the backend, if this is what is
+ * needed to update an existing transaction. It might pull new
+ * currencies (??))
+ *
+ * The counter() routine increments the named counter and returns the
+ * post-incremented value. Returns -1 if there is a problem.
+ *
+ * The events_pending() routines should return true if there are
+ * external events which need to be processed to bring the
+ * engine up to date with the backend.
+ *
+ * The process_events() routine should process any events indicated
+ * by the events_pending() routine. It should return TRUE if
+ * the engine was changed while engine events were suspended.
+ *
+ * The last_err member indicates the last error that occurred.
+ * It should probably be implemented as an array (actually,
+ * a stack) of all the errors that have occurred.
+ *
+ * For support of book partitioning, use special "Book" begin_edit()
+ * and commit_edit() QOF_ID types.
+ *
+ * Call the book begin() at the begining of a book partitioning. A
+ * 'partitioning' is the splitting off of a chunk of the current
+ * book into a second book by means of a query. Every transaction
+ * in that query is to be moved ('transfered') to the second book
+ * from the existing book. The argument of this routine is a
+ * pointer to the second book, where the results of the query
+ * should go.
+ *
+ * Cann the book commit() to complete the book partitioning.
+ *
+ * After the begin(), there will be a call to run_query(), followed
+ * probably by a string of account and transaction calls, and
+ * completed by commit(). It should be explicitly understood that
+ * the results of that run_query() precisely constitute the set of
+ * transactions that are to be moved between the initial and the
+ * new book. This specification can be used by a clever backend to
+ * avoid excess data movement between the server and the gnucash
+ * client, as explained below.
+ *
+ * There are several possible ways in which a backend may choose to
+ * implement the book splitting process. A 'file-type' backend may
+ * choose to ignore this call, and the subsequent query, and simply
+ * write out the new book to a file when the commit() call is made.
+ * By that point, the engine will have performed all of the
+ * nitty-gritty of moving transactions from one book to the other.
+ *
+ * A 'database-type' backend has several interesting choices. One
+ * simple choice is to simply perform the run_query() as it
+ * normally would, and likewise treat the account and transaction
+ * edits as usual. In this scenario, the commit() is more or less
+ * a no-op. This implementation has a drawback, however: the
+ * run_query() may cause the transfer of a *huge* amount of data
+ * between the backend and the engine. For a large dataset, this
+ * is quite undesirable. In addition, there are risks associated
+ * with the loss of network connectivity during the transfer; thus
+ * a partition might terminate half-finished, in some indeterminate
+ * state, due to network errors. That might be difficult to
+ * recover from: the engine does not take any special transactional
+ * safety measures during the transfer.
+ *
+ * Thus, for a large database, an alternate implementation
+ * might be to use the run_query() call as an opportunity to
+ * transfer transactions between the two books in the database,
+ * and not actually return any new data to the engine. In
+ * this scenario, the engine will attempt to transfer those
+ * transactions that it does know about. It does not, however,
+ * need to know about all the other transactions that also would
+ * be transfered over. In this way, a backend could perform
+ * a mass transfer of transactions between books without having
+ * to actually move much (or any) data to the engine.
+ *
+ *
+ * To support configuration options from the frontend, the backend
+ * can be passed a GHashTable - according to the allowed options
+ * for that backend, using load_config(). Configuration can be
+ * updated at any point - it is up to the frontend to load the
+ * data in time for whatever the backend needs to do. e.g. an
+ * option to save a new book in a compressed format need not be
+ * loaded until the backend is about to save. If the configuration
+ * is updated by the user, the frontend should call load_config
+ * again to update the backend.
+ */
+
+struct QofBackendProvider_s
+{
+ /** Some arbitrary name given for this particular backend provider */
+ const char * provider_name;
+
+ /** The access method that this provider provides, for example,
+ * http:// or postgres:// or rpc://, but without the :// at the end
+ */
+ const char * access_method;
+
+ /** \brief Partial QofBook handler
+
+ TRUE if the backend handles external references
+ to entities outside this book and can save a QofBook that
+ does not contain any specific QOF objects.
+ */
+ gboolean partial_book_supported;
+
+ /** Return a new, initialized backend backend. */
+ QofBackend * (*backend_new) (void);
+
+/** \brief Distinguish two providers with same access method.
+
+ More than 1 backend can be registered under the same access_method,
+ so each one is passed the path to the data (e.g. a file) and
+ should return TRUE only:
+-# if the backend recognises the type as one that it can load and write or
+-# if the path contains no data but can be used (e.g. a new session).
+
+ \note If the backend can cope with more than one type, the backend
+ should not try to store or cache the sub-type for this data.
+ It is sufficient only to return TRUE if any ONE of the supported
+ types match the incoming data. The backend should not assume that
+ returning TRUE will mean that the data will naturally follow.
+ */
+ gboolean (*check_data_type) (const char*);
+
+ /** Free this structure, unregister this backend handler. */
+ void (*provider_free) (QofBackendProvider *);
+};
+
+struct QofBackend_s
+{
+ void (*session_begin) (QofBackend *be,
+ QofSession *session,
+ const char *book_id,
+ gboolean ignore_lock,
+ gboolean create_if_nonexistent);
+ void (*session_end) (QofBackend *);
+ void (*destroy_backend) (QofBackend *);
+
+ void (*load) (QofBackend *, QofBook *);
+
+ void (*begin) (QofBackend *, QofInstance *);
+ void (*commit) (QofBackend *, QofInstance *);
+ void (*rollback) (QofBackend *, QofInstance *);
+
+ gpointer (*compile_query) (QofBackend *, QofQuery *);
+ void (*free_query) (QofBackend *, gpointer);
+ void (*run_query) (QofBackend *, gpointer);
+
+ void (*sync) (QofBackend *, QofBook *);
+ void (*load_config) (QofBackend *, KvpFrame *);
+ KvpFrame* (*get_config) (QofBackend *);
+ gint64 (*counter) (QofBackend *, const char *counter_name);
+
+ gboolean (*events_pending) (QofBackend *);
+ gboolean (*process_events) (QofBackend *);
+
+ QofBePercentageFunc percentage;
+
+ QofBackendProvider *provider;
+
+ /** Document Me !!! what is this supposed to do ?? */
+ gboolean (*save_may_clobber_data) (QofBackend *);
+
+ QofBackendError last_err;
+ char * error_msg;
+
+ KvpFrame* backend_configuration;
+ gint config_count;
+ /** Each backend resolves a fully-qualified file path.
+ * This holds the filepath and communicates it to the frontends.
+ */
+ char * fullpath;
+
+#ifdef GNUCASH_MAJOR_VERSION
+ /** XXX price_lookup should be removed during the redesign
+ * of the SQL backend... prices can now be queried using
+ * the generic query mechanism.
+ *
+ * Note the correct signature for this call is
+ * void (*price_lookup) (QofBackend *, GNCPriceLookup *);
+ * we use gpointer to avoid an unwanted include file dependency.
+ */
+ void (*price_lookup) (QofBackend *, gpointer);
+
+ /** XXX Export should really _NOT_ be here, but is left here for now.
+ * I'm not sure where this should be going to. It should be
+ * removed ASAP. This is a temporary hack-around until period-closing
+ * is fully implemented.
+ */
+ void (*export) (QofBackend *, QofBook *);
+#endif
+
+};
+
+/** Let the sytem know about a new provider of backends. This function
+ * is typically called by the provider library at library load time.
+ * This function allows the backend library to tell the QOF infrastructure
+ * that it can handle URL's of a certain type. Note that a single
+ * backend library may register more than one provider, if it is
+ * capable of handling more than one URL access method.
+ */
+void qof_backend_register_provider (QofBackendProvider *);
+
+/** The qof_backend_set_error() routine pushes an error code onto the error
+ * stack. (FIXME: the stack is 1 deep in current implementation).
+ */
+void qof_backend_set_error (QofBackend *be, QofBackendError err);
+
+/** The qof_backend_get_error() routine pops an error code off the error stack.
+ */
+QofBackendError qof_backend_get_error (QofBackend *be);
+
+/** The qof_backend_set_message() assigns a string to the backend error message.
+ */
+void qof_backend_set_message(QofBackend *be, const char *format, ...);
+
+/** The qof_backend_get_message() pops the error message string from
+ * the Backend. This string should be freed with g_free().
+ */
+char * qof_backend_get_message(QofBackend *be);
+
+void qof_backend_init(QofBackend *be);
+
+/** Allow backends to see if the book is open
+
+ at return 'y' if book is open, otherwise 'n'.
+*/
+gchar qof_book_get_open_marker(QofBook *book);
+
+/** get the book version
+
+used for tracking multiuser updates in backends.
+
+ at return -1 if no book exists, 0 if the book is
+new, otherwise the book version number.
+*/
+gint32 qof_book_get_version (QofBook *book);
+
+/** get the book tag number
+
+used for kvp management in sql backends.
+*/
+guint32 qof_book_get_idata (QofBook *book);
+
+void qof_book_set_version (QofBook *book, gint32 version);
+
+void qof_book_set_idata(QofBook *book, guint32 idata);
+
+/* @} */
+/* @} */
+/* @} */
+#endif /* QOF_BACKEND_P_H */
Added: gnucash/trunk/lib/libqof/qof/qofbackend.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbackend.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbackend.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,427 @@
+/********************************************************************\
+ * qofbackend.c -- utility routines for dealing with the data backend *
+ * Copyright (C) 2000 Linas Vepstas <linas at linas.org> *
+ * Copyright (C) 2004-5 Neil Williams <linux at codehelp.co.uk> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#define _GNU_SOURCE
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <regex.h>
+#include <glib.h>
+#include <gmodule.h>
+#include <dlfcn.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include "qofbackend-p.h"
+
+static QofLogModule log_module = QOF_MOD_BACKEND;
+
+#define QOF_CONFIG_DESC "desc"
+#define QOF_CONFIG_TIP "tip"
+
+/********************************************************************\
+ * error handling *
+\********************************************************************/
+
+void
+qof_backend_set_error (QofBackend *be, QofBackendError err)
+{
+ if (!be) return;
+
+ /* use stack-push semantics. Only the earliest error counts */
+ if (ERR_BACKEND_NO_ERR != be->last_err) return;
+ be->last_err = err;
+}
+
+QofBackendError
+qof_backend_get_error (QofBackend *be)
+{
+ QofBackendError err;
+ if (!be) return ERR_BACKEND_NO_BACKEND;
+
+ /* use 'stack-pop' semantics */
+ err = be->last_err;
+ be->last_err = ERR_BACKEND_NO_ERR;
+ return err;
+}
+
+void
+qof_backend_set_message (QofBackend *be, const char *format, ...)
+{
+ va_list args;
+ char * buffer;
+
+ if (!be) return;
+
+ /* If there's already something here, free it */
+ if (be->error_msg) g_free(be->error_msg);
+
+ if (!format) {
+ be->error_msg = NULL;
+ return;
+ }
+
+ va_start(args, format);
+ buffer = (char *)g_strdup_vprintf(format, args);
+ va_end(args);
+
+ be->error_msg = buffer;
+}
+
+/* This should always return a valid char * */
+char *
+qof_backend_get_message (QofBackend *be)
+{
+ char * msg;
+
+ if (!be) return g_strdup("ERR_BACKEND_NO_BACKEND");
+ if (!be->error_msg) return NULL;
+
+ /*
+ * Just return the contents of the error_msg and then set it to
+ * NULL. This is necessary, because the Backends don't seem to
+ * have a destroy_backend function to take care if freeing stuff
+ * up. The calling function should free the copy.
+ * Also, this is consistent with the qof_backend_get_error() popping.
+ */
+
+ msg = be->error_msg;
+ be->error_msg = NULL;
+ return msg;
+}
+
+/***********************************************************************/
+/* Get a clean backend */
+void
+qof_backend_init(QofBackend *be)
+{
+ be->session_begin = NULL;
+ be->session_end = NULL;
+ be->destroy_backend = NULL;
+
+ be->load = NULL;
+
+ be->begin = NULL;
+ be->commit = NULL;
+ be->rollback = NULL;
+
+ be->compile_query = NULL;
+ be->free_query = NULL;
+ be->run_query = NULL;
+
+ be->sync = NULL;
+ be->load_config = NULL;
+
+ be->events_pending = NULL;
+ be->process_events = NULL;
+
+ be->last_err = ERR_BACKEND_NO_ERR;
+ if (be->error_msg) g_free (be->error_msg);
+ be->error_msg = NULL;
+ be->percentage = NULL;
+ be->backend_configuration = kvp_frame_new();
+
+#ifdef GNUCASH_MAJOR_VERSION
+ /* XXX remove these */
+ be->fullpath = NULL;
+ be->price_lookup = NULL;
+ be->export = NULL;
+#endif
+}
+
+void
+qof_backend_run_begin(QofBackend *be, QofInstance *inst)
+{
+ if(!be || !inst) { return; }
+ if(!be->begin) { return; }
+ (be->begin) (be, inst);
+}
+
+gboolean
+qof_backend_begin_exists(QofBackend *be)
+{
+ if(be->begin) { return TRUE; }
+ else { return FALSE; }
+}
+
+void
+qof_backend_run_commit(QofBackend *be, QofInstance *inst)
+{
+ if(!be || !inst) { return; }
+ if(!be->commit) { return; }
+ (be->commit) (be, inst);
+}
+
+/* =========== Backend Configuration ================ */
+
+void qof_backend_prepare_frame(QofBackend *be)
+{
+ g_return_if_fail(be);
+ if(!kvp_frame_is_empty(be->backend_configuration)) {
+ kvp_frame_delete(be->backend_configuration);
+ be->backend_configuration = kvp_frame_new();
+ }
+ be->config_count = 0;
+}
+
+void qof_backend_prepare_option(QofBackend *be, QofBackendOption *option)
+{
+ KvpValue *value;
+ gchar *temp;
+ gint count;
+
+ g_return_if_fail(be || option);
+ count = be->config_count;
+ count++;
+ value = NULL;
+ ENTER (" %d", count);
+ switch (option->type)
+ {
+ case KVP_TYPE_GINT64 : {
+ value = kvp_value_new_gint64(*(gint64*)option->value);
+ break;
+ }
+ case KVP_TYPE_DOUBLE : {
+ value = kvp_value_new_double(*(double*)option->value);
+ break;
+ }
+ case KVP_TYPE_NUMERIC : {
+ value = kvp_value_new_numeric(*(gnc_numeric*)option->value);
+ break;
+ }
+ case KVP_TYPE_STRING : {
+ value = kvp_value_new_string((const char*)option->value);
+ break;
+ }
+ case KVP_TYPE_GUID : { break; } /* unsupported */
+ case KVP_TYPE_TIMESPEC : {
+ value = kvp_value_new_timespec(*(Timespec*)option->value);
+ break;
+ }
+ case KVP_TYPE_BINARY : { break; } /* unsupported */
+ case KVP_TYPE_GLIST : { break; } /* unsupported */
+ case KVP_TYPE_FRAME : { break; } /* unsupported */
+ }
+ if(value) {
+ temp = g_strdup_printf("/%s", option->option_name);
+ kvp_frame_set_value(be->backend_configuration, temp, value);
+ PINFO (" setting value at %s", temp);
+ g_free(temp);
+ temp = g_strdup_printf("/%s/%s", QOF_CONFIG_DESC, option->option_name);
+ PINFO (" setting description %s at %s", option->description, temp);
+ kvp_frame_set_string(be->backend_configuration, temp, option->description);
+ PINFO (" check= %s", kvp_frame_get_string(be->backend_configuration, temp));
+ g_free(temp);
+ temp = g_strdup_printf("/%s/%s", QOF_CONFIG_TIP, option->option_name);
+ PINFO (" setting tooltip %s at %s", option->tooltip, temp);
+ kvp_frame_set_string(be->backend_configuration, temp, option->tooltip);
+ PINFO (" check= %s", kvp_frame_get_string(be->backend_configuration, temp));
+ g_free(temp);
+ /* only increment the counter if successful */
+ be->config_count = count;
+ }
+ LEAVE (" ");
+}
+
+KvpFrame* qof_backend_complete_frame(QofBackend *be)
+{
+ g_return_val_if_fail(be, NULL);
+ be->config_count = 0;
+ return be->backend_configuration;
+}
+
+struct config_iterate {
+ QofBackendOptionCB fcn;
+ gpointer data;
+ gint count;
+ KvpFrame *recursive;
+};
+
+static void
+config_foreach_cb (const char *key, KvpValue *value, gpointer data)
+{
+ QofBackendOption option;
+ gint64 int64;
+ double db;
+ gnc_numeric num;
+ Timespec ts;
+ gchar *parent;
+ struct config_iterate *helper;
+
+ g_return_if_fail(key || value || data);
+ helper = (struct config_iterate*)data;
+ if(!helper->recursive) { PERR (" no parent frame"); return; }
+ // skip the presets.
+ if(0 == safe_strcmp(key, QOF_CONFIG_DESC)) { return; }
+ if(0 == safe_strcmp(key, QOF_CONFIG_TIP)) { return; }
+ ENTER (" key=%s", key);
+ option.option_name = key;
+ option.type = kvp_value_get_type(value);
+ if(!option.type) { return; }
+ switch (option.type)
+ {
+ case KVP_TYPE_GINT64 : {
+ int64 = kvp_value_get_gint64(value);
+ option.value = (gpointer)&int64;
+ break;
+ }
+ case KVP_TYPE_DOUBLE : {
+ db = kvp_value_get_double(value);
+ option.value = (gpointer)&db;
+ break;
+ }
+ case KVP_TYPE_NUMERIC : {
+ num = kvp_value_get_numeric(value);
+ option.value = (gpointer)#
+ break;
+ }
+ case KVP_TYPE_STRING : {
+ option.value = (gpointer)kvp_value_get_string(value);
+ break;
+ }
+ case KVP_TYPE_GUID : { break; } /* unsupported */
+ case KVP_TYPE_TIMESPEC : {
+ ts = kvp_value_get_timespec(value);
+ option.value = (gpointer)&ts;
+ break;
+ }
+ case KVP_TYPE_BINARY : { break; } /* unsupported */
+ case KVP_TYPE_GLIST : { break; } /* unsupported */
+ case KVP_TYPE_FRAME : { break; } /* unsupported */
+ }
+ parent = g_strdup_printf("/%s/%s", QOF_CONFIG_DESC, key);
+ option.description = kvp_frame_get_string(helper->recursive, parent);
+ g_free(parent);
+ parent = g_strdup_printf("/%s/%s", QOF_CONFIG_TIP, key);
+ option.tooltip = kvp_frame_get_string(helper->recursive, parent);
+ helper->count++;
+ helper->fcn (&option, helper->data);
+ LEAVE (" desc=%s tip=%s", option.description, option.tooltip);
+}
+
+void qof_backend_option_foreach(KvpFrame *config, QofBackendOptionCB cb, gpointer data)
+{
+ struct config_iterate helper;
+
+ if(!config || !cb) { return; }
+ ENTER (" ");
+ helper.fcn = cb;
+ helper.count = 1;
+ helper.data = data;
+ helper.recursive = config;
+ kvp_frame_for_each_slot(config, config_foreach_cb, &helper);
+ LEAVE (" ");
+}
+
+void
+qof_backend_load_config(QofBackend *be, KvpFrame *config)
+{
+ if(!be || !config) { return; }
+ if(!be->load_config) { return; }
+ (be->load_config) (be, config);
+}
+
+KvpFrame*
+qof_backend_get_config(QofBackend *be)
+{
+ if(!be) { return NULL; }
+ if(!be->get_config) { return NULL; }
+ return (be->get_config) (be);
+}
+
+gboolean
+qof_backend_commit_exists(QofBackend *be)
+{
+ if(!be) { return FALSE; }
+ if(be->commit) { return TRUE; }
+ else { return FALSE; }
+}
+
+gboolean
+qof_begin_edit(QofInstance *inst)
+{
+ QofBackend * be;
+
+ if (!inst) { return FALSE; }
+ (inst->editlevel)++;
+ if (1 < inst->editlevel) { return FALSE; }
+ if (0 >= inst->editlevel) { inst->editlevel = 1; }
+ be = qof_book_get_backend (inst->book);
+ if (be && qof_backend_begin_exists(be)) {
+ qof_backend_run_begin(be, inst);
+ } else { inst->dirty = TRUE; }
+ return TRUE;
+}
+
+gboolean qof_commit_edit(QofInstance *inst)
+{
+ QofBackend * be;
+
+ if (!inst) { return FALSE; }
+ (inst->editlevel)--;
+ if (0 < inst->editlevel) { return FALSE; }
+ if ((-1 == inst->editlevel) && inst->dirty)
+ {
+ be = qof_book_get_backend ((inst)->book);
+ if (be && qof_backend_begin_exists(be)) {
+ qof_backend_run_begin(be, inst);
+ }
+ inst->editlevel = 0;
+ }
+ if (0 > inst->editlevel) { inst->editlevel = 0; }
+ return TRUE;
+}
+
+gboolean
+qof_load_backend_library (const char *directory,
+ const char* filename, const char* init_fcn)
+{
+ struct stat sbuf;
+ gchar *fullpath;
+ typedef void (* backend_init) (void);
+ GModule *backend;
+ backend_init gmod_init;
+ gpointer g;
+
+ g_return_val_if_fail(g_module_supported(), FALSE);
+ fullpath = g_module_build_path(directory, filename);
+ PINFO (" fullpath=%s", fullpath);
+ g_return_val_if_fail((stat(fullpath, &sbuf) == 0), FALSE);
+ backend = g_module_open(fullpath, G_MODULE_BIND_LAZY);
+ if(!backend) {
+ g_message ("%s: %s\n", PACKAGE, g_module_error ());
+ return FALSE;
+ }
+ g = &gmod_init;
+ if (!g_module_symbol (backend, init_fcn, g))
+ {
+ g_message ("%s: %s\n", PACKAGE, g_module_error ());
+ return FALSE;
+ }
+ g_module_make_resident(backend);
+ gmod_init();
+ return TRUE;
+}
+
+/************************* END OF FILE ********************************/
Added: gnucash/trunk/lib/libqof/qof/qofbackend.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbackend.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbackend.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,265 @@
+/********************************************************************\
+ * qofbackend.h: api for data storage backend *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup Backend
+
+ The QOF Backend is a pseudo-object providing an interface between the
+ engine and a persistant data store (e.g. a server, a database, or
+ a file). Backends are not meant to be used directly by an
+ application; instead the Session should be used to make a
+ connection with some particular backend.
+ There are no backend functions that are 'public' to
+ users of the engine. The backend can, however, report errors to
+ the GUI & other front-end users. This file defines these errors.
+
+ Backends are used to save and restore Entities in a Book.
+ @{
+*/
+/** @file qofbackend.h
+ @brief API for data storage Backend
+ @author Copyright (C) 2000-2001 Linas Vepstas <linas at linas.org>
+ @author Copyright 2004-2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#ifndef QOF_BACKEND_H
+#define QOF_BACKEND_H
+
+#include "qofinstance.h"
+
+#define QOF_MOD_BACKEND "qof-backend"
+
+/** \brief The errors that can be reported to the GUI & other front-end users
+ * \warning (GnuCash) If you modify QofBackendError, please update
+ * src/engine/gw-engine-spec.scm
+*/
+typedef enum {
+ ERR_BACKEND_NO_ERR = 0,
+ ERR_BACKEND_NO_HANDLER, /**< no backend handler found for this access method (ENOSYS) */
+ ERR_BACKEND_NO_BACKEND, /**< Backend * pointer was unexpectedly null */
+ ERR_BACKEND_BAD_URL, /**< Can't parse url */
+ ERR_BACKEND_NO_SUCH_DB, /**< the named database doesn't exist */
+ ERR_BACKEND_CANT_CONNECT, /**< bad dbname/login/passwd or network failure */
+ ERR_BACKEND_CONN_LOST, /**< Lost connection to server */
+ ERR_BACKEND_LOCKED, /**< in use by another user (ETXTBSY) */
+ ERR_BACKEND_READONLY, /**< cannot write to file/directory */
+ ERR_BACKEND_TOO_NEW, /**< file/db version newer than what we can read */
+ ERR_BACKEND_DATA_CORRUPT, /**< data in db is corrupt */
+ ERR_BACKEND_SERVER_ERR, /**< error in response from server */
+ ERR_BACKEND_ALLOC, /**< internal memory allocation failure */
+ ERR_BACKEND_PERM, /**< user login successful, but no permissions
+ to access the desired object */
+ ERR_BACKEND_MODIFIED, /**< commit of object update failed because
+ another user has modified the object */
+ ERR_BACKEND_MOD_DESTROY, /**< commit of object update failed because
+ another user has deleted the object */
+ ERR_BACKEND_MISC, /**< undetermined error */
+
+ /* QSF add-ons */
+ ERR_QSF_INVALID_OBJ, /**< The QSF object failed to validate against the QSF object schema */
+ ERR_QSF_INVALID_MAP, /**< The QSF map failed to validate against the QSF map schema */
+ ERR_QSF_BAD_OBJ_GUID, /**< The QSF object contains one or more invalid GUIDs. */
+ ERR_QSF_BAD_QOF_VERSION, /**< QSF map or object doesn't match the current QOF_OBJECT_VERSION. */
+ ERR_QSF_BAD_MAP, /**< The selected map validates but is unusable.
+
+ This is usually because not all the required parameters for the defined objects
+ have calculations described in the map.
+ */
+ ERR_QSF_NO_MAP, /**< The QSF object file was loaded without a map
+
+ The QSF Object file requires a map but it was not provided.
+ */
+ ERR_QSF_WRONG_MAP, /**< The selected map validates but is for different objects.
+
+ The list of objects defined in this map does not include all the objects described in
+ the current QSF object file.
+ */
+ ERR_QSF_MAP_NOT_OBJ, /**< Selected file is a QSF map and cannot be opened as a QSF object */
+ ERR_QSF_OVERFLOW, /**< EOVERFLOW - generated by strtol or strtoll.
+
+ When converting XML strings into numbers, an overflow has been detected. The XML file
+ contains invalid data in a field that is meant to hold a signed long integer or signed long long
+ integer.
+ */
+ ERR_QSF_OPEN_NOT_MERGE, /** QSF files cannot be opened alone. The data must be merged.
+
+ This error is more of a warning that can be ignored by any routine
+ that uses qof_book_merge on the new session.
+ */
+ /* fileio errors */
+ ERR_FILEIO_FILE_BAD_READ = 1000, /**< read failed or file prematurely truncated */
+ ERR_FILEIO_FILE_EMPTY, /**< file exists, is readable, but is empty */
+ ERR_FILEIO_FILE_LOCKERR, /**< mangled locks (unspecified error) */
+ ERR_FILEIO_FILE_NOT_FOUND, /**< not found / no such file */
+ ERR_FILEIO_FILE_TOO_OLD, /**< file version so old we can't read it */
+ ERR_FILEIO_UNKNOWN_FILE_TYPE, /**< didn't recognize the file type */
+ ERR_FILEIO_PARSE_ERROR, /**< couldn't parse the data in the file */
+ ERR_FILEIO_BACKUP_ERROR, /**< couldn't make a backup of the file */
+ ERR_FILEIO_WRITE_ERROR, /**< couldn't write to the file */
+
+ /* network errors */
+ ERR_NETIO_SHORT_READ = 2000, /**< not enough bytes received */
+ ERR_NETIO_WRONG_CONTENT_TYPE, /**< wrong kind of server, wrong data served */
+ ERR_NETIO_NOT_GNCXML, /**< whatever it is, we can't parse it. */
+
+ /* database errors */
+ ERR_SQL_MISSING_DATA = 3000, /**< database doesn't contain expected data */
+ ERR_SQL_DB_TOO_OLD, /**< database is old and needs upgrading */
+ ERR_SQL_DB_BUSY, /**< database is busy, cannot upgrade version */
+
+ /* RPC errors */
+ ERR_RPC_HOST_UNK = 4000, /**< Host unknown */
+ ERR_RPC_CANT_BIND, /**< can't bind to address */
+ ERR_RPC_CANT_ACCEPT, /**< can't accept connection */
+ ERR_RPC_NO_CONNECTION, /**< no connection to server */
+ ERR_RPC_BAD_VERSION, /**< RPC Version Mismatch */
+ ERR_RPC_FAILED, /**< Operation failed */
+ ERR_RPC_NOT_ADDED, /**< object not added */
+} QofBackendError;
+
+/**
+ * A structure that declares backend services that can be gotten.
+ * The Provider specifies a URL access method, and specifies the
+ * function to create a backend that can handle that URL access
+ * function.
+ */
+typedef struct QofBackendProvider_s QofBackendProvider;
+
+/** \brief Pseudo-object providing an interface between the
+ * engine and a persistant data store (e.g. a server, a database,
+ * or a file).
+ *
+ * There are no backend functions that are 'public' to users of the
+ * engine. The backend can, however, report errors to the GUI & other
+ * front-end users.
+ */
+typedef struct QofBackend_s QofBackend;
+
+/** \brief DOCUMENT ME! */
+typedef void (*QofBePercentageFunc) (const char *message, double percent);
+
+/** @name Allow access to the begin routine for this backend.
+
+QOF_BEGIN_EDIT and QOF_COMMIT_EDIT_PART1 and part2 rely on
+calling QofBackend *be->begin and be->commit. This means the
+QofBackend struct becomes part of the public API.
+These function replaces those calls to allow the macros to be
+used when QOF is built as a library. */
+//@{
+
+void qof_backend_run_begin(QofBackend *be, QofInstance *inst);
+
+gboolean qof_backend_begin_exists(QofBackend *be);
+
+void qof_backend_run_commit(QofBackend *be, QofInstance *inst);
+
+gboolean qof_backend_commit_exists(QofBackend *be);
+//@}
+
+/** @name Backend Configuration using KVP
+
+The backend uses qof_backend_get_config to pass back a KvpFrame of QofBackendOption
+that includes the \b translated strings that serve as description and
+tooltip for that option. i.e. backends need to run gettext in the init function.
+
+qof_backend_prepare_frame, qof_backend_prepare_option and qof_backend_complete_frame
+are intended to be used by the backend itself to create the options.
+
+qof_backend_get_config, qof_backend_option_foreach and qof_backend_load_config
+are intended for either the backend or the frontend to retrieve the option data
+from the frame or set new data.
+
+@{
+*/
+
+/** A single Backend Configuration Option. */
+typedef struct QofBackendOption_s {
+ KvpValueType type; /**< Only GINT64, DOUBLE, NUMERIC, STRING and TIMESPEC supported. */
+ const char *option_name; /**< non-translated, key. */
+ const char *description; /**< translatable description. */
+ const char *tooltip; /**< translatable tooltip */
+ gpointer value; /**< The value of the option. */
+}QofBackendOption;
+
+/** Initialise the backend_configuration */
+void qof_backend_prepare_frame(QofBackend *be);
+
+/** Add an option to the backend_configuration. Repeat for more. */
+void qof_backend_prepare_option(QofBackend *be, QofBackendOption *option);
+
+/** Complete the backend_configuration and return the frame. */
+KvpFrame* qof_backend_complete_frame(QofBackend *be);
+
+/** Backend configuration option foreach callback prototype. */
+typedef void (*QofBackendOptionCB)(QofBackendOption*, gpointer data);
+
+/** Iterate over the frame and process each option. */
+void qof_backend_option_foreach(KvpFrame *config, QofBackendOptionCB cb, gpointer data);
+
+/** \brief Load configuration options specific to this backend.
+
+ at param be The backend to configure.
+ at param config A KvpFrame of QofBackendOptions that this backend
+will recognise. Each backend needs to document their own config
+types and acceptable values.
+
+*/
+void qof_backend_load_config (QofBackend *be, KvpFrame *config);
+
+/** \brief Get the available configuration options
+
+To retrieve the options from the returned KvpFrame, the caller
+needs to parse the XML file that documents the option names and
+data types. The XML file itself is part of the backend and is
+installed in a directory determined by the backend. Therefore,
+loading a new backend requires two paths: the path to the .la file
+and the path to the xml. Both paths are available by including a
+generated header file, e.g. gncla-dir.h defines GNC_LIB_DIR for
+the location of the .la file and GNC_XML_DIR for the xml.
+
+ at param be The QofBackend to be configured.
+
+ at return A new KvpFrame containing the available options or
+NULL on failure.
+
+*/
+KvpFrame* qof_backend_get_config(QofBackend *be);
+//@}
+
+/** \brief Load a QOF-compatible backend shared library.
+
+\param directory Can be NULL if filename is a complete path.
+\param filename Name of the .la file that describes the
+ shared library. This provides platform independence,
+ courtesy of libtool.
+\param init_fcn The QofBackendProvider init function.
+
+\return FALSE in case or error, otherwise TRUE.
+*/
+gboolean
+qof_load_backend_library (const char *directory,
+ const char* filename, const char* init_fcn);
+
+/** \brief Retrieve the backend used by this book */
+QofBackend* qof_book_get_backend (QofBook *book);
+
+void qof_book_set_backend (QofBook *book, QofBackend *);
+
+#endif /* QOF_BACKEND_H */
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/qofbook-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbook-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbook-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,114 @@
+/********************************************************************\
+ * qof-book-p.h -- private functions for QOF books. *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup Object
+ @{ */
+/** @addtogroup Object_Private
+ Private interfaces, not meant to be used by applications.
+ @{ */
+/** @name Book_Private
+ @{ */
+/*
+ * HISTORY:
+ * Created 2001 by Rob Browning
+ * Copyright (c) 2001 Rob Browning
+ * Copyright (c) 2001,2003 Linas Vepstas <linas at linas.org>
+ */
+
+#ifndef QOF_BOOK_P_H
+#define QOF_BOOK_P_H
+
+#include "kvp_frame.h"
+#include "qofbackend.h"
+#include "qofbook.h"
+#include "qofid.h"
+#include "qofid-p.h"
+#include "qofinstance-p.h"
+
+/** Book structure */
+struct _QofBook
+{
+ QofInstance inst; /**< Unique guid for this book. */
+
+ /** The entity table associates the GUIDs of all the objects
+ * belonging to this book, with their pointers to the respective
+ * objects. This allows a lookup of objects based on thier guid.
+ */
+ GHashTable * hash_of_collections;
+
+ /** In order to store arbitrary data, for extensibility, add a table
+ * that will be used to hold arbitrary pointers.
+ */
+ GHashTable *data_tables;
+
+ /** Hash table of destroy callbacks for the data table. */
+ GHashTable *data_table_finalizers;
+
+ /** state flag: 'y' means 'open for editing',
+ * 'n' means 'book is closed'
+ * xxxxx shouldn't this be replaced by the instance editlevel ???
+ */
+ char book_open;
+
+ /** a flag denoting whether the book is closing down, used to
+ * help the QOF objects shut down cleanly without maintaining
+ * internal consistency.
+ * XXX shouldn't this be replaced by instance->do_free ???
+ */
+ gboolean shutting_down;
+
+ /** version number, used for tracking multiuser updates */
+ gint32 version;
+
+ /** To be technically correct, backends belong to sessions and
+ * not books. So the pointer below "really shouldn't be here",
+ * except that it provides a nice convenience, avoiding a lookup
+ * from the session. Better solutions welcome ... */
+ QofBackend *backend;
+
+ /* -------------------------------------------------------------- */
+ /** Backend private expansion data */
+ guint32 idata; /**< used by the sql backend for kvp management */
+};
+
+/**
+ * These qof_book_set_*() routines are used by backends to
+ * initialize the pointers in the book structure to
+ * something that contains actual data. These routines
+ * should not be used otherwise. (Its somewhat questionable
+ * if the backends should even be doing this much, but for
+ * backwards compatibility, we leave these here.)
+ */
+void qof_book_set_schedxactions( QofBook *book, GList *newList );
+
+void qof_book_set_backend (QofBook *book, QofBackend *be);
+
+/** Register books with the engine */
+gboolean qof_book_register (void);
+
+/** @deprecated */
+#define qof_book_set_guid(book,guid) \
+ qof_entity_set_guid(QOF_ENTITY(book), guid)
+
+/* @} */
+/* @} */
+/* @} */
+#endif /* QOF_BOOK_P_H */
Added: gnucash/trunk/lib/libqof/qof/qofbook.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbook.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbook.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,385 @@
+/********************************************************************\
+ * qofbook.c -- dataset access (set of accounting books) *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+\********************************************************************/
+
+/*
+ * FILE:
+ * qofbook.c
+ *
+ * FUNCTION:
+ * Encapsulate all the information about a gnucash dataset.
+ * See src/doc/books.txt for design overview.
+ *
+ * HISTORY:
+ * Created by Linas Vepstas December 1998
+ * Copyright (c) 1998-2001,2003 Linas Vepstas <linas at linas.org>
+ * Copyright (c) 2000 Dave Peticolas
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "gnc-event.h"
+#include "gnc-event-p.h"
+#include "gnc-trace.h"
+#include "qofbackend-p.h"
+#include "qofbook.h"
+#include "qofbook-p.h"
+#include "qofclass.h"
+#include "qofid-p.h"
+#include "qofobject-p.h"
+#include "gnc-engine-util.h"
+
+#include "guid.h"
+
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+/* ====================================================================== */
+/* constructor / destructor */
+
+static void coll_destroy(gpointer col)
+{
+ qof_collection_destroy((QofCollection *) col);
+}
+
+static void
+qof_book_init (QofBook *book)
+{
+ if (!book) return;
+
+ book->hash_of_collections = g_hash_table_new_full(g_str_hash, g_str_equal,
+ gnc_string_cache_remove,
+ coll_destroy);
+
+ qof_instance_init (&book->inst, QOF_ID_BOOK, book);
+
+ book->data_tables = g_hash_table_new (g_str_hash, g_str_equal);
+ book->data_table_finalizers = g_hash_table_new (g_str_hash, g_str_equal);
+
+ book->book_open = 'y';
+ book->version = 0;
+ book->idata = 0;
+}
+
+QofBook *
+qof_book_new (void)
+{
+ QofBook *book;
+
+ ENTER (" ");
+ book = g_new0(QofBook, 1);
+ qof_book_init(book);
+ qof_object_book_begin (book);
+
+ gnc_engine_gen_event (&book->inst.entity, GNC_EVENT_CREATE);
+ LEAVE ("book=%p", book);
+ return book;
+}
+
+static void
+book_final (gpointer key, gpointer value, gpointer booq)
+{
+ QofBookFinalCB cb = value;
+ QofBook *book = booq;
+
+ gpointer user_data = g_hash_table_lookup (book->data_tables, key);
+ (*cb) (book, key, user_data);
+}
+
+void
+qof_book_destroy (QofBook *book)
+{
+ if (!book) return;
+ ENTER ("book=%p", book);
+
+ book->shutting_down = TRUE;
+ gnc_engine_force_event (&book->inst.entity, GNC_EVENT_DESTROY);
+
+ /* Call the list of finalizers, let them do thier thing.
+ * Do this before tearing into the rest of the book.
+ */
+ g_hash_table_foreach (book->data_table_finalizers, book_final, book);
+
+ qof_object_book_end (book);
+
+ g_hash_table_destroy (book->data_table_finalizers);
+ g_hash_table_destroy (book->data_tables);
+
+ qof_instance_release (&book->inst);
+
+ g_hash_table_destroy (book->hash_of_collections);
+ book->hash_of_collections = NULL;
+
+ g_free (book);
+ LEAVE ("book=%p", book);
+}
+
+/* ====================================================================== */
+/* XXX this should probably be calling is_equal callbacks on gncObject */
+
+gboolean
+qof_book_equal (QofBook *book_1, QofBook *book_2)
+{
+ if (book_1 == book_2) return TRUE;
+ if (!book_1 || !book_2) return FALSE;
+ return TRUE;
+}
+
+/* ====================================================================== */
+
+gboolean
+qof_book_not_saved(QofBook *book)
+{
+ if (!book) return FALSE;
+
+ return(book->inst.dirty || qof_object_is_dirty (book));
+}
+
+void
+qof_book_mark_saved(QofBook *book)
+{
+ if (!book) return;
+
+ book->inst.dirty = FALSE;
+ qof_object_mark_clean (book);
+}
+
+/* ====================================================================== */
+/* getters */
+
+QofBackend *
+qof_book_get_backend (QofBook *book)
+{
+ if (!book) return NULL;
+ return book->backend;
+}
+
+gboolean
+qof_book_shutting_down (QofBook *book)
+{
+ if (!book) return FALSE;
+ return book->shutting_down;
+}
+
+/* ====================================================================== */
+/* setters */
+
+void
+qof_book_set_backend (QofBook *book, QofBackend *be)
+{
+ if (!book) return;
+ ENTER ("book=%p be=%p", book, be);
+ book->backend = be;
+ LEAVE (" ");
+}
+
+void qof_book_kvp_changed (QofBook *book)
+{
+ if (!book) return;
+ book->inst.dirty = TRUE;
+}
+
+/* ====================================================================== */
+
+/* Store arbitrary pointers in the QofBook for data storage extensibility */
+/* XXX if data is NULL, we should remove the key from the hash table!
+ */
+void
+qof_book_set_data (QofBook *book, const char *key, gpointer data)
+{
+ if (!book || !key) return;
+ g_hash_table_insert (book->data_tables, (gpointer)key, data);
+}
+
+void
+qof_book_set_data_fin (QofBook *book, const char *key, gpointer data, QofBookFinalCB cb)
+{
+ if (!book || !key) return;
+ g_hash_table_insert (book->data_tables, (gpointer)key, data);
+
+ if (!cb) return;
+ g_hash_table_insert (book->data_table_finalizers, (gpointer)key, cb);
+}
+
+gpointer
+qof_book_get_data (QofBook *book, const char *key)
+{
+ if (!book || !key) return NULL;
+ return g_hash_table_lookup (book->data_tables, (gpointer)key);
+}
+
+/* ====================================================================== */
+
+QofCollection *
+qof_book_get_collection (QofBook *book, QofIdType entity_type)
+{
+ QofCollection *col;
+
+ if (!book || !entity_type) return NULL;
+
+ col = g_hash_table_lookup (book->hash_of_collections, entity_type);
+ if (col) return col;
+
+ col = qof_collection_new (entity_type);
+
+ g_hash_table_insert (book->hash_of_collections,
+ gnc_string_cache_insert((gpointer) entity_type), col);
+
+ return col;
+}
+
+struct _iterate {
+ QofCollectionForeachCB fn;
+ gpointer data;
+};
+
+static void
+foreach_cb (gpointer key, gpointer item, gpointer arg)
+{
+ struct _iterate *iter = arg;
+ QofCollection *col = item;
+
+ iter->fn (col, iter->data);
+}
+
+void
+qof_book_foreach_collection (QofBook *book,
+ QofCollectionForeachCB cb, gpointer user_data)
+{
+ struct _iterate iter;
+
+ g_return_if_fail (book);
+ g_return_if_fail (cb);
+
+ iter.fn = cb;
+ iter.data = user_data;
+
+ g_hash_table_foreach (book->hash_of_collections, foreach_cb, &iter);
+}
+
+/* ====================================================================== */
+
+void qof_book_mark_closed (QofBook *book)
+{
+ if(!book) { return; }
+ book->book_open = 'n';
+}
+
+gchar qof_book_get_open_marker(QofBook *book)
+{
+ if(!book) { return 'n'; }
+ return book->book_open;
+}
+
+gint32 qof_book_get_version (QofBook *book)
+{
+ if(!book) { return -1; }
+ return book->version;
+}
+
+guint32 qof_book_get_idata (QofBook *book)
+{
+ if(!book) { return 0; }
+ return book->idata;
+}
+
+void qof_book_set_version (QofBook *book, gint32 version)
+{
+ if(!book && version < 0) { return; }
+ book->version = version;
+}
+
+void qof_book_set_idata(QofBook *book, guint32 idata)
+{
+ if(!book && idata < 0) { return; }
+ book->idata = idata;
+}
+
+gint64
+qof_book_get_counter (QofBook *book, const char *counter_name)
+{
+ QofBackend *be;
+ KvpFrame *kvp;
+ KvpValue *value;
+ gint64 counter;
+
+ if (!book) {
+ PWARN ("No book!!!");
+ return -1;
+ }
+
+ if (!counter_name || *counter_name == '\0') {
+ PWARN ("Invalid counter name.");
+ return -1;
+ }
+
+ /* If we've got a backend with a counter method, call it */
+ be = book->backend;
+ if (be && be->counter)
+ return ((be->counter)(be, counter_name));
+
+ /* If not, then use the KVP in the book */
+ kvp = qof_book_get_slots (book);
+
+ if (!kvp) {
+ PWARN ("Book has no KVP_Frame");
+ return -1;
+ }
+
+ value = kvp_frame_get_slot_path (kvp, "counters", counter_name, NULL);
+ if (value) {
+ /* found it */
+ counter = kvp_value_get_gint64 (value);
+ } else {
+ /* New counter */
+ counter = 0;
+ }
+
+ /* Counter is now valid; increment it */
+ counter++;
+
+ /* Save off the new counter */
+ value = kvp_value_new_gint64 (counter);
+ kvp_frame_set_slot_path (kvp, value, "counters", counter_name, NULL);
+ kvp_value_delete (value);
+
+ /* and return the value */
+ return counter;
+}
+
+/* QofObject function implementation and registration */
+gboolean qof_book_register (void)
+{
+ static QofParam params[] = {
+ { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_entity_get_guid, NULL },
+ { QOF_PARAM_KVP, QOF_TYPE_KVP, (QofAccessFunc)qof_instance_get_slots, NULL },
+ { NULL },
+ };
+
+ qof_class_register (QOF_ID_BOOK, NULL, params);
+
+ return TRUE;
+}
+
+/* ========================== END OF FILE =============================== */
Added: gnucash/trunk/lib/libqof/qof/qofbook.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofbook.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofbook.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,177 @@
+/********************************************************************\
+ * qofbook.h -- Encapsulate all the information about a dataset. *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup Object
+ @{ */
+/** @addtogroup Book
+ A QOF Book is a dataset. It provides a single handle
+ through which all the various collections of entities
+ can be found. In particular, given only the type of
+ the entity, the collection can be found.
+
+ Books also provide the 'natural' place to working with
+ a storage backend, as a book can encapsulate everything
+ held in storage.
+ @{ */
+/** @file qofbook.h
+ * @brief Encapsulate all the information about a dataset.
+ *
+ * @author Copyright (c) 1998, 1999, 2001, 2003 Linas Vepstas <linas at linas.org>
+ * @author Copyright (c) 2000 Dave Peticolas
+ */
+
+#ifndef QOF_BOOK_H
+#define QOF_BOOK_H
+
+#include <glib.h>
+#include "qofid.h"
+#include "kvp_frame.h"
+
+/** @brief Encapsulates all the information about a dataset
+ * manipulated by GnuCash. This is the top-most structure
+ * used for anchoring data.
+ */
+
+/** Lookup an entity by guid, returning pointer to the entity */
+#define QOF_BOOK_LOOKUP_ENTITY(book,guid,e_type,c_type) ({ \
+ QofEntity *val = NULL; \
+ if (guid && book) { \
+ QofCollection *col; \
+ col = qof_book_get_collection (book, e_type); \
+ val = qof_collection_lookup_entity (col, guid); \
+ } \
+ (c_type *) val; \
+})
+
+/** \brief QofBook reference */
+typedef struct _QofBook QofBook;
+
+/** GList of QofBook */
+typedef GList QofBookList;
+
+typedef void (*QofBookFinalCB) (QofBook *, gpointer key, gpointer user_data);
+
+/** Register the book object with the QOF object system. */
+gboolean qof_book_register (void);
+
+/** Allocate, initialise and return a new QofBook. Books contain references
+ * to all of the top-level object containers. */
+QofBook * qof_book_new (void);
+
+/** End any editing sessions associated with book, and free all memory
+ associated with it. */
+void qof_book_destroy (QofBook *book);
+
+/** Close a book to editing.
+
+It is up to the application to check this flag,
+and once marked closed, books cannnot be marked as open.
+*/
+void qof_book_mark_closed (QofBook *book);
+
+/** \return The table of entities of the given type.
+ *
+ * When an object's constructor calls qof_instance_init(), a
+ * reference to the object is stored in the book. The book stores
+ * all the references to initialized instances, sorted by type. This
+ * function returns a collection of the references for the specified
+ * type.
+ *
+ * If the collection doesn't yet exist for the indicated type,
+ * it is created. Thus, this routine is gaurenteed to return
+ * a non-NULL value. (Unless the system malloc failed (out of
+ * memory) in which case what happens??).
+ */
+QofCollection * qof_book_get_collection (QofBook *, QofIdType);
+
+/** Invoke the indicated callback on each collection in the book. */
+typedef void (*QofCollectionForeachCB) (QofCollection *, gpointer user_data);
+void qof_book_foreach_collection (QofBook *, QofCollectionForeachCB, gpointer);
+
+/** \return The kvp data for the book.
+ * Note that the book KVP data is persistant, and is stored/retrieved
+ * from the file/database. Thus, the book KVP is the correct place to
+ * store data that needs to be persistant accross sessions (or shared
+ * between multiple users). To store application runtime data, use
+ * qof_book_set_data() instead.
+ */
+#define qof_book_get_slots(book) qof_instance_get_slots(QOF_INSTANCE(book))
+
+/** The qof_book_set_data() allows arbitrary pointers to structs
+ * to be stored in QofBook. This is the "prefered" method for
+ * extending QofBook to hold new data types. This is also
+ * the ideal location to store other arbitrary runtime data
+ * that the application may need.
+ *
+ * The book data differs from the book KVP in that the contents
+ * of the book KVP are persistant (are saved and restored to file
+ * or database), whereas the data pointers exist only at runtime.
+ */
+void qof_book_set_data (QofBook *book, const char *key, gpointer data);
+
+/** Same as qof_book_set_data(), except that the callback will be called
+ * when the book is destroyed. The argument to the callback will be
+ * the book followed by the data pointer.
+ */
+void qof_book_set_data_fin (QofBook *book, const char *key, gpointer data, QofBookFinalCB);
+
+/** Retrieves arbitrary pointers to structs stored by qof_book_set_data. */
+gpointer qof_book_get_data (QofBook *book, const char *key);
+
+/** Is the book shutting down? */
+gboolean qof_book_shutting_down (QofBook *book);
+
+/** qof_book_not_saved() will return TRUE if any
+ * data in the book hasn't been saved to long-term storage.
+ * (Actually, that's not quite true. The book doesn't know
+ * anything about saving. Its just that whenever data is modified,
+ * the 'dirty' flag is set. This routine returns the value of the
+ * 'dirty' flag. Its up to the backend to periodically reset this
+ * flag, when it actually does save the data.)
+ */
+gboolean qof_book_not_saved (QofBook *book);
+
+/** The qof_book_mark_saved() routine marks the book as having been
+ * saved (to a file, to a database). Used by backends to mark the
+ * notsaved flag as FALSE just after loading. Also used by the
+ * main window code when the used has said to abandon any changes.
+ */
+void qof_book_mark_saved(QofBook *book);
+
+/** Call this function when you change the book kvp, to make sure the book
+ * is marked 'dirty'. */
+void qof_book_kvp_changed (QofBook *book);
+
+/** The qof_book_equal() method returns TRUE if books are equal.
+ * XXX this routine is broken, and does not currently compare data.
+ */
+gboolean qof_book_equal (QofBook *book_1, QofBook *book_2);
+
+/** This will 'get and increment' the named counter for this book.
+ * The return value is -1 on error or the incremented counter.
+ */
+gint64 qof_book_get_counter (QofBook *book, const char *counter_name);
+
+/** deprecated */
+#define qof_book_get_guid(X) qof_entity_get_guid (QOF_ENTITY(X))
+
+#endif /* QOF_BOOK_H */
+/** @} */
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/qofchoice.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofchoice.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofchoice.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,109 @@
+/***************************************************************************
+ * qofchoice.c
+ *
+ * Thu Jul 7 12:24:30 2005
+ * Copyright 2005 Neil Williams
+ * linux at codehelp.co.uk
+ ****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <glib.h>
+#include "qofchoice.h"
+
+static GHashTable *qof_choice_table = NULL;
+
+/* To initialise, call qof_choice_add_class in
+qof_object_register for the choice object. */
+static gboolean qof_choice_is_initialized(void)
+{
+ if(!qof_choice_table)
+ {
+ qof_choice_table = g_hash_table_new(g_str_hash, g_str_equal);
+ }
+ if(!qof_choice_table) { return FALSE; }
+ return TRUE;
+}
+
+gboolean qof_object_is_choice(QofIdType type)
+{
+ gpointer value, check;
+
+ value = NULL;
+ check = NULL;
+ g_return_val_if_fail(qof_choice_is_initialized(), FALSE);
+ g_return_val_if_fail(type != NULL, FALSE);
+ value = g_hash_table_lookup(qof_choice_table, type);
+ if((GHashTable*)value) { return TRUE; }
+ g_message("DEBUG: QOF_TYPE_CHOICE setup failed for %s\n", type);
+ return FALSE;
+}
+
+gboolean
+qof_choice_create(char* type)
+{
+ GHashTable *param_table;
+
+ g_return_val_if_fail(type != NULL, FALSE);
+ g_return_val_if_fail(qof_choice_is_initialized() == TRUE, FALSE);
+ param_table = g_hash_table_new(g_str_hash, g_str_equal);
+ g_hash_table_insert(qof_choice_table, type, param_table);
+ return TRUE;
+}
+
+gboolean qof_choice_add_class(char* select, char* option, char* param_name)
+{
+ GHashTable *param_table;
+ GList *option_list;
+
+ option_list = NULL;
+ param_table = NULL;
+ g_return_val_if_fail(select != NULL, FALSE);
+ g_return_val_if_fail(qof_object_is_choice(select), FALSE);
+ param_table = (GHashTable*)g_hash_table_lookup(qof_choice_table, select);
+ g_return_val_if_fail(param_table, FALSE);
+ option_list = (GList*)g_hash_table_lookup(param_table, param_name);
+ option_list = g_list_append(option_list, option);
+ g_hash_table_insert(param_table, param_name, option_list);
+ return TRUE;
+}
+
+GList* qof_object_get_choices(QofIdType type, QofParam *param)
+{
+ GList *choices;
+ GHashTable *param_table;
+
+ g_return_val_if_fail(type != NULL, NULL);
+ g_return_val_if_fail(qof_choice_is_initialized() == TRUE, FALSE);
+ choices = NULL;
+ param_table = g_hash_table_lookup(qof_choice_table, type);
+ choices = g_hash_table_lookup(param_table, param->param_name);
+ return choices;
+}
+
+gboolean qof_choice_check(char* choice_obj, char *param_name, char* choice )
+{
+ GList *choices, *result;
+ GHashTable *param_table;
+
+ choices = result = NULL;
+ g_return_val_if_fail(qof_object_is_choice(choice_obj), FALSE);
+ param_table = g_hash_table_lookup(qof_choice_table, choice_obj);
+ choices = g_hash_table_lookup(param_table, param_name);
+ result = g_list_find(choices, choice);
+ if(!result) { return FALSE; }
+ return TRUE;
+}
Added: gnucash/trunk/lib/libqof/qof/qofchoice.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofchoice.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofchoice.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,151 @@
+/***************************************************************************
+ * qofchoice.h
+ *
+ * Thu Jul 7 12:25:24 2005
+ * Copyright 2005 Neil Williams
+ * linux at codehelp.co.uk
+ ****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _QOFCHOICE_H
+#define _QOFCHOICE_H
+
+/** @addtogroup Choice
+
+Objects can be linked together one-to-one by simply using the name of
+the related object as the parameter type in the QofClass parameter list.
+
+\verbatim
+{ FOO_PARAM, BAR_ID, (QofAccessFunc)qofFooGetBar, (QofSetterFunc)qofFooSetBar },
+\endverbatim
+
+This is limited as each FOO entity can contain only one reference to a
+single BAR entity per parameter. Also, this parameter cannot be used to
+link to a similar object, OBJ. This requires "one to many" links.
+
+There are two types of one-to-many links in QOF.
+
+-# ::QOF_TYPE_COLLECT - one to many entities all of only one type.
+ - Handles links between one object and a series of objects
+ that are \b all of the same type, e.g. a simple list.
+-# ::QOF_TYPE_CHOICE - one to a single entity of many possible types.
+ - Handles links between one object and a series of
+\b dissimilar objects, one of each type.
+
+Currently, there is no explicit way to support many-to-many links
+but existing methods can be combined to give approximately the same results.
+
+A QOF_TYPE_CHOICE object is like a C++ template. QOF_TYPE_CHOICE doesn't
+really exist by itself:
+\verbatim
+QOF_TYPE_CHOICE<QOF_X, QOF_Y, QOF_Z>
+\endverbatim
+It holds a single entity of type X, Y, or Z for the purposes of QOF
+or ::QSF. For querying the object it queries as if it's an X, Y, or Z.
+
+Each choice type has it's own definition of the allowable objects -
+each of which need to be registered as normal. Objects can declare
+themselves to be one option of a particular choice. There is no
+requirement for any object to be either a choice or an option for a
+choice object.
+
+-# Each ::QOF_TYPE_CHOICE parameter provides access to \b ONE entity of
+a pre-determined set of object types.
+-# The entity type within the choice can be determined at run time.
+-# Each entity can have a different *type* of entity to it's siblings,
+provided that it is one of the pre-determined object types.
+-# Objects declare themselves as containing choices and other objects
+can add themselves to the list of acceptable choices of suitable objects.
+-# QOF_TYPE_CHOICE is transparent - objects should retrieve the e_type of
+the received entity and handle the entity appropriately.
+-# The number of different entity types that can be pre-determined for
+any one QOF_TYPE_CHOICE parameter is not fixed. You can have as many
+types as the ::QofAccessFunc and ::QofSetterFunc can handle.
+-# Any one parameter can only have one QOF type. You cannot have a parameter
+that is both ::QOF_TYPE_COLLECT and QOF_TYPE_CHOICE any more than you can have
+one parameter that is both ::QOF_TYPE_BOOLEAN and ::QOF_TYPE_NUMERIC.
+-# When setting references using QOF_TYPE_CHOICE, QOF passes a single entity
+to the QofSetterFunc and it is left to the function to determine how to
+handle that entity type. When retrieving references using QOF_TYPE_CHOICE,
+the object must return a single entity matching one of the choice types.
+
+ @{
+*/
+
+/** @file qofchoice.h
+ @brief Linking one entity to a single entity of many possible types.
+ @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+*/
+#include "qofclass.h"
+#include "qofobject.h"
+
+/** \brief Identify an object as containing a choice. */
+#define QOF_TYPE_CHOICE "choice"
+
+/** \brief Does this object contain a choice parameter?
+
+Returns TRUE if any parameter in the object definition
+uses a choice of elements, whether or not those
+parameters contain any data.
+
+ at param type Type of object/entity.
+
+ at return TRUE if one or more choice parameters has been
+registered using the object definition, otherwise FALSE.
+*/
+gboolean qof_object_is_choice(QofIdType type);
+
+/** \brief Set an object as using QOF_TYPE_CHOICE. */
+gboolean qof_choice_create(char* type);
+
+/** \brief Add the choices for this parameter to the object.
+
+ at param choice The choice object.
+ at param add The object to be added as an option.
+ at param param_name The parameter that will be used to get or set options.
+
+ at return FALSE if object is not a choice object or on error
+ otherwise TRUE.
+*/
+gboolean qof_choice_add_class(char* choice, char* add, char* param_name);
+
+/** \brief Return the list of all object types usable with this parameter.
+
+ at param type The choice object type.
+ at param param The name of the parameter that will be used to
+ get or set options.
+
+ at return NULL on error or if no options exist for this parameter,
+ otherwise a GList of all QofIdType object type(s) available
+ via this choice object using this parameter.
+*/
+GList* qof_object_get_choices(QofIdType type, QofParam *param);
+
+/** \brief Is the choice valid for this param_name?
+
+ at param choice_obj The object containing the QOF_TYPE_CHOICE parameter.
+ at param param_name The name of a QOF_TYPE_CHOICE parameter in this object.
+ at param choice The QofIdType to look for in the list of choices.
+
+ at return TRUE if choice is found in the list of allowed choices for
+this parameter of this object. Otherwise, FALSE
+*/
+gboolean qof_choice_check(char* choice_obj, char *param_name, char* choice);
+
+
+/** @} */
+#endif /* _QOFCHOICE_H */
Added: gnucash/trunk/lib/libqof/qof/qofclass-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofclass-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofclass-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,44 @@
+/********************************************************************\
+ * qofclass-p.h -- Private API for registering queriable objects *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup Object
+ @{ */
+/** @addtogroup Object_Private
+ Private interfaces, not meant to be used by applications.
+ @{ */
+/** @name Class_Private
+ @{ */
+
+#ifndef QOF_CLASSP_H
+#define QOF_CLASSP_H
+
+#include "qofclass.h"
+
+void qof_class_init(void);
+void qof_class_shutdown (void);
+
+QofSortFunc qof_class_get_default_sort (QofIdTypeConst obj_name);
+
+/* @} */
+/* @} */
+/* @} */
+#endif /* QOF_CLASSP_H */
Added: gnucash/trunk/lib/libqof/qof/qofclass.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofclass.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofclass.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,294 @@
+/********************************************************************\
+ * qofclass.c -- provide QOF paramterized data objects *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "qofclass.h"
+#include "qofclass-p.h"
+#include "qofquery.h"
+
+static QofLogModule log_module = QOF_MOD_CLASS;
+
+static GHashTable *classTable = NULL;
+static GHashTable *sortTable = NULL;
+static gboolean initialized = FALSE;
+
+static gboolean clear_table (gpointer key, gpointer value, gpointer user_data)
+{
+ g_hash_table_destroy (value);
+ return TRUE;
+}
+
+/********************************************************************/
+/* PUBLISHED API FUNCTIONS */
+
+void
+qof_class_register (QofIdTypeConst obj_name,
+ QofSortFunc default_sort_function,
+ const QofParam *params)
+{
+ GHashTable *ht;
+ int i;
+
+ if (!obj_name) return;
+
+ if (default_sort_function)
+ {
+ g_hash_table_insert (sortTable, (char *)obj_name, default_sort_function);
+ }
+
+ ht = g_hash_table_lookup (classTable, obj_name);
+
+ /* If it doesn't already exist, create a new table for this object */
+ if (!ht)
+ {
+ ht = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (classTable, (char *)obj_name, ht);
+ }
+
+ /* At least right now, we allow dummy, paramterless objects,
+ * for testing purposes. Although I suppose that should be
+ * an error.. */
+ /* Now insert all the parameters */
+ if (params)
+ {
+ for (i = 0; params[i].param_name; i++)
+ g_hash_table_insert (ht,
+ (char *)params[i].param_name,
+ (gpointer)&(params[i]));
+ }
+}
+
+void
+qof_class_init(void)
+{
+ if (initialized) return;
+ initialized = TRUE;
+
+ classTable = g_hash_table_new (g_str_hash, g_str_equal);
+ sortTable = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+void
+qof_class_shutdown (void)
+{
+ if (!initialized) return;
+ initialized = FALSE;
+
+ g_hash_table_foreach_remove (classTable, clear_table, NULL);
+ g_hash_table_destroy (classTable);
+ g_hash_table_destroy (sortTable);
+}
+
+gboolean
+qof_class_is_registered (QofIdTypeConst obj_name)
+{
+ if (!obj_name) return FALSE;
+
+ if (g_hash_table_lookup (classTable, obj_name)) return TRUE;
+
+ return FALSE;
+}
+
+const QofParam *
+qof_class_get_parameter (QofIdTypeConst obj_name,
+ const char *parameter)
+{
+ GHashTable *ht;
+
+ g_return_val_if_fail (obj_name, NULL);
+ g_return_val_if_fail (parameter, NULL);
+
+ ht = g_hash_table_lookup (classTable, obj_name);
+ if (!ht)
+ {
+ PWARN ("no object of type %s", obj_name);
+ return NULL;
+ }
+
+ return (g_hash_table_lookup (ht, parameter));
+}
+
+QofAccessFunc
+qof_class_get_parameter_getter (QofIdTypeConst obj_name,
+ const char *parameter)
+{
+ const QofParam *prm;
+
+ g_return_val_if_fail (obj_name, NULL);
+ g_return_val_if_fail (parameter, NULL);
+
+ prm = qof_class_get_parameter (obj_name, parameter);
+ if (prm)
+ return prm->param_getfcn;
+
+ return NULL;
+}
+
+QofSetterFunc
+qof_class_get_parameter_setter (QofIdTypeConst obj_name,
+ const char *parameter)
+{
+ const QofParam *prm;
+
+ g_return_val_if_fail (obj_name, NULL);
+ g_return_val_if_fail (parameter, NULL);
+
+ prm = qof_class_get_parameter (obj_name, parameter);
+ if (prm)
+ return prm->param_setfcn;
+
+ return NULL;
+}
+
+QofType
+qof_class_get_parameter_type (QofIdTypeConst obj_name,
+ const char *param_name)
+{
+ const QofParam *prm;
+
+ if (!obj_name || !param_name) return NULL;
+
+ prm = qof_class_get_parameter (obj_name, param_name);
+ if (!prm) return NULL;
+
+ return (prm->param_type);
+}
+
+QofSortFunc
+qof_class_get_default_sort (QofIdTypeConst obj_name)
+{
+ if (!obj_name) return NULL;
+ return g_hash_table_lookup (sortTable, obj_name);
+}
+
+/* ================================================================ */
+
+struct class_iterate {
+ QofClassForeachCB fcn;
+ gpointer data;
+};
+
+static void
+class_foreach_cb (gpointer key, gpointer item, gpointer arg)
+{
+ struct class_iterate *iter = arg;
+ QofIdTypeConst id = key;
+
+ iter->fcn (id, iter->data);
+}
+
+void
+qof_class_foreach (QofClassForeachCB cb, gpointer user_data)
+{
+ struct class_iterate iter;
+
+ if (!cb) return;
+ if (!classTable) return;
+
+ iter.fcn = cb;
+ iter.data = user_data;
+
+ g_hash_table_foreach (classTable, class_foreach_cb, &iter);
+}
+
+/* ================================================================ */
+
+struct parm_iterate {
+ QofParamForeachCB fcn;
+ gpointer data;
+};
+
+static void
+param_foreach_cb (gpointer key, gpointer item, gpointer arg)
+{
+ struct parm_iterate *iter = arg;
+ QofParam *parm = item;
+
+ iter->fcn (parm, iter->data);
+}
+
+void
+qof_class_param_foreach (QofIdTypeConst obj_name,
+ QofParamForeachCB cb, gpointer user_data)
+{
+ struct parm_iterate iter;
+ GHashTable *param_ht;
+
+ if (!obj_name || !cb) return;
+ if (!classTable) return;
+ param_ht = g_hash_table_lookup (classTable, obj_name);
+ if (!param_ht) return;
+
+ iter.fcn = cb;
+ iter.data = user_data;
+
+ g_hash_table_foreach (param_ht, param_foreach_cb, &iter);
+}
+
+struct param_ref_list
+{
+ GList *list;
+};
+
+static void
+find_reference_param_cb(QofParam *param, gpointer user_data)
+{
+ struct param_ref_list *b;
+
+ b = (struct param_ref_list*)user_data;
+ if((param->param_getfcn == NULL)||(param->param_setfcn == NULL)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_STRING)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_NUMERIC)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_DATE)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_CHAR)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_DEBCRED)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_GUID)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_INT32)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_INT64)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_DOUBLE)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_KVP)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_BOOLEAN)) { return; }
+ if(0 == safe_strcmp(param->param_type, QOF_ID_BOOK)) { return; }
+ b->list = g_list_append(b->list, param);
+}
+
+GList*
+qof_class_get_referenceList(QofIdTypeConst type)
+{
+ GList *ref_list;
+ struct param_ref_list b;
+
+ ref_list = NULL;
+ b.list = NULL;
+ qof_class_param_foreach(type, find_reference_param_cb, &b);
+ ref_list = g_list_copy(b.list);
+ return ref_list;
+}
+
+
+/* ============================= END OF FILE ======================== */
Added: gnucash/trunk/lib/libqof/qof/qofclass.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofclass.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofclass.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,268 @@
+/********************************************************************\
+ * qofclass.h -- API for registering parameters on objects *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+/** @addtogroup Object
+ @{ */
+
+/** @addtogroup Class
+ This file defines a class messaging system reminiscent of
+ traditional OO-style setter and getter interfaces to object
+ properties. A C-language object can declare a collection
+ of setters and getters on itself that can then be used
+ to perform run-time (as opposed to compile-time) bindings
+ to the object.
+
+ To put it differently, a QOF class is a set of parameter
+ getters and setters that are associated with an object type.
+ Given a pointer to some thing, the setters and getters can
+ be used to get and set values out of that thing. Note
+ that the pointer to "some thing" need not be a pointer
+ to a QOF Entity or Instance (although QOF classes are
+ more interesting when used with Entities/Instances).
+ What "some thing" is defined entirely by the programmer
+ declaring a new QOF Class.
+
+ Because a QOF Class associates getters and setters with
+ a type, one can then ask, at run time, what parameters
+ are associated with a given type, even if those parameters
+ were not known at compile time. Thus, a QOF Class is
+ sort-of like a DynAny implementation. QOF classes can
+ be used to provide "object introspection", i.e. asking
+ object to describe itself.
+
+ The QOF Query subsystem depends on QOF classes having been
+ declared; the Query uses the getters to get values associated
+ with particular instances.
+
+ A QofAccessFunc or QofSetterFunc do not need to be public
+ functions, if you need to add functions to an object with an
+ established API, define the additional QOF routines as static.
+ Only the register routine needs to be public.
+@{ */
+
+/** @file qofclass.h
+ @brief API for registering paramters on objects
+ @author Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>
+ @author Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+ @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#ifndef QOF_CLASS_H
+#define QOF_CLASS_H
+
+#include "qofid.h"
+
+#define QOF_MOD_CLASS "qof-class"
+
+/** \name Core types
+
+Core data types for objects that can be used in parameters.
+Note that QofIdTypes may also be used and will create a
+single reference between two known objects.
+
+ @{
+ */
+
+#define QOF_TYPE_STRING "string"
+#define QOF_TYPE_DATE "date"
+#define QOF_TYPE_NUMERIC "numeric"
+#define QOF_TYPE_DEBCRED "debcred"
+#define QOF_TYPE_GUID "guid"
+#define QOF_TYPE_INT32 "gint32"
+#define QOF_TYPE_INT64 "gint64"
+#define QOF_TYPE_DOUBLE "double"
+#define QOF_TYPE_BOOLEAN "boolean"
+#define QOF_TYPE_KVP "kvp"
+#define QOF_TYPE_CHAR "character"
+#define QOF_TYPE_COLLECT "collection" /**< secondary collections
+are used for one-to-many references between entities and are
+implemented using ::QofCollection.
+These are \b NOT the same as the main collections in the QofBook.
+
+-# Each ::QofCollection contains one or many entities - *all* of a single type.
+-# The entity type within the collection can be determined at run time.
+-# Easy conversions to GList or whatever in the param_setfcn handler.
+-# Each parameter can have it's own collection.
+-# Each entity can have a different *type* of collection to it's siblings,
+provided that it is acceptable to the set function.
+-# Each object decides which types are acceptable for which parameter in the
+set functions. This is then part of the API for that object.
+
+QOF_TYPE_COLLECT has two functions, both related to one-to-many
+links:
+- Represent a reference between 2 entities with a list of acceptable types.
+ (one object linked to many types of single entities)
+- Represent a reference between one entity and many entities of another type.
+ (one object linked to many entities of a single type.)
+
+If the set function can handle it, it could also be used for true one-to-many
+links: one object linked to many entities of many types.
+
+n.b. Always subject to each collection holding only one type at runtime.
+(otherwise use books).
+
+*/
+/** @} */
+/** Type of Paramters (String, Date, Numeric, GUID, etc.) */
+typedef const char * QofType;
+
+typedef struct _QofParam QofParam;
+
+/** The QofAccessFunc defines an arbitrary function pointer
+ * for access functions. This is needed because C doesn't have
+ * templates, so we just cast a lot. Real functions must be of
+ * the form:
+ *
+ * param_type getter_func (object_type *self);
+ * or
+ * param_type getter_func (object_type *self, QofParam *param);
+ *
+ * The additional argument 'param' allows generic getter functions
+ * to be implemented, because this argument provides for a way to
+ * identify the expected getter_func return type at runtime. It
+ * also provides a place for the user to hang additional user-defined
+ * data.
+ */
+typedef gpointer (*QofAccessFunc)(gpointer object, const QofParam *param);
+
+/** The QofSetterFunc defines an function pointer for parameter
+ * setters. Real functions must be of the form:
+ *
+ * void setter_func (object_type *self, param_type *param);
+ */
+typedef void (*QofSetterFunc) (gpointer, gpointer);
+
+/** This structure is for each queriable parameter in an object
+ *
+ * -- param_name is the name of the parameter.
+ * -- param_type is the type of the parameter, which can be either another
+ * object (QofIdType) or it can be a core data type (QofType).
+ * -- param_getfcn is the function to actually obtain the parameter
+ * -- param_setfcn is the function to actually set the parameter
+ * -- param_userdata is a place where the object author can place any
+ * desired object-author-defined data (and thus can be used by the
+ * author-defined setter/getter).
+ *
+ * Either the getter or the setter may be NULL.
+ *
+ * XXX todo/fixme: need to define a destroy callback, so that when
+ * the param memory is freed, the callback can be used to release the
+ * user-defined data.
+ */
+struct _QofParam
+{
+ const char * param_name;
+ QofType param_type;
+ QofAccessFunc param_getfcn;
+ QofSetterFunc param_setfcn;
+ gpointer param_userdata;
+};
+
+/** This function is the default sort function for a particular object type */
+typedef int (*QofSortFunc)(gpointer, gpointer);
+
+/** This function registers a new object class with the Qof subsystem.
+ * In particular, it registers the set of setters and getters for
+ * controlling the object. The getters are typically used by the
+ * query subsystem to query type specific data. Note that there
+ * is no particular requirement for there to be a setter for every
+ * getter or even vice-versa, nor is there any requeirement for these
+ * to map 'cleanly' or orthogonaly to the underlying object. The
+ * parameters are really just a set of value setting and getting
+ * routines.
+ *
+ * The "params" argument must be a NULL-terminated array of QofParam.
+ * It may be NULL if there are no parameters to be registered.
+ */
+void qof_class_register (QofIdTypeConst obj_name,
+ QofSortFunc default_sort_fcn,
+ const QofParam *params);
+
+/** An example:
+ *
+ * #define MY_OBJ_MEMO "memo"
+ * #define MY_OBJ_VALUE "value"
+ * #define MY_OBJ_DATE "date"
+ * #define MY_OBJ_ACCOUNT "account"
+ * #define MY_OBJ_TRANS "trans"
+ *
+ * static QofParam myParams[] = {
+ * { MY_OBJ_MEMO, QOF_TYPE_STRING, myMemoGetter, NULL },
+ * { MY_OBJ_VALUE, QOF_TYPE_NUMERIC, myValueGetter, NULL },
+ * { MY_OBJ_DATE, QOF_TYPE_DATE, myDateGetter, NULL },
+ * { MY_OBJ_ACCOUNT, GNC_ID_ACCOUNT, myAccountGetter, NULL },
+ * { MY_OBJ_TRANS, GNC_ID_TRANS, myTransactionGetter, NULL },
+ * NULL };
+ *
+ * qof_class_register ("myObjectName", myObjectCompare, &myParams);
+ */
+
+/** Return true if the the indicated type is registered,
+ * else return FALSE.
+ */
+gboolean qof_class_is_registered (QofIdTypeConst obj_name);
+
+/** Return the core datatype of the specified object's parameter */
+QofType qof_class_get_parameter_type (QofIdTypeConst obj_name,
+ const char *param_name);
+
+/** Return the registered Parameter Definition for the requested parameter */
+const QofParam * qof_class_get_parameter (QofIdTypeConst obj_name,
+ const char *parameter);
+
+/** Return the object's parameter getter function */
+QofAccessFunc qof_class_get_parameter_getter (QofIdTypeConst obj_name,
+ const char *parameter);
+
+/** Return the object's parameter setter function */
+QofSetterFunc qof_class_get_parameter_setter (QofIdTypeConst obj_name,
+ const char *parameter);
+
+/** Type definition for the class callback function. */
+typedef void (*QofClassForeachCB) (QofIdTypeConst, gpointer);
+
+/** Call the callback once for each object class that is registered
+ * with the system. The 'user_data' is passed back to the callback.
+ */
+void qof_class_foreach (QofClassForeachCB, gpointer user_data);
+
+/** Type definition for the paramter callback function. */
+typedef void (*QofParamForeachCB) (QofParam *, gpointer user_data);
+
+/** Call the callback once for each parameter on the indicated
+ * object class. The 'user_data' is passed back to the callback.
+ */
+void qof_class_param_foreach (QofIdTypeConst obj_name,
+ QofParamForeachCB, gpointer user_data);
+
+/** \brief List of the parameters that could be references.
+
+Simple check to return a GList of all parameters
+of this object type that are not known QOF data types.
+Used for partial QofBook support, see ::QofEntityReference
+*/
+GList* qof_class_get_referenceList(QofIdTypeConst type);
+
+
+#endif /* QOF_CLASS_H */
+/** @} */
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/qofgobj.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofgobj.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofgobj.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,326 @@
+/********************************************************************\
+ * qofgobj.c -- QOF to GLib GObject mapping *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#include "qof.h"
+#include "qofgobj.h"
+
+static QofLogModule log_module = QOF_MOD_QUERY;
+
+static gboolean initialized = FALSE;
+static GSList *paramList = NULL;
+static GSList *classList = NULL;
+
+/* =================================================================== */
+
+#if 0
+static gboolean
+clear_table (gpointer key, gpointer value, gpointer user_data)
+{
+ g_slist_free (value);
+ return TRUE;
+}
+#endif
+
+void
+qof_gobject_init(void)
+{
+ if (initialized) return;
+ initialized = TRUE;
+
+ // gobjectClassTable = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* Init the other subsystems that we need */
+ qof_object_initialize();
+ qof_query_init ();
+}
+
+void
+qof_gobject_shutdown (void)
+{
+ GSList *n;
+
+ if (!initialized) return;
+ initialized = FALSE;
+
+// GSList *n;
+ for (n=paramList; n; n=n->next) g_free(n->data);
+ g_slist_free (paramList);
+
+ for (n=classList; n; n=n->next) g_free(n->data);
+ g_slist_free (classList);
+
+#if 0
+ // XXX also need to walk over books, and collection and delete
+ // the collection get_data instance lists !!
+ // without this we have a memory leak !!
+ g_hash_table_foreach_remove (gobjectParamTable, clear_table, NULL);
+ g_hash_table_destroy (gobjectParamTable);
+#endif
+}
+
+/* =================================================================== */
+
+#define GOBJECT_TABLE "GobjectTable"
+
+void
+qof_gobject_register_instance (QofBook *book, QofType type, GObject *gob)
+{
+ QofCollection *coll;
+ GSList *instance_list;
+
+ if (!book || !type) return;
+
+ coll = qof_book_get_collection (book, type);
+
+ instance_list = qof_collection_get_data (coll);
+ instance_list = g_slist_prepend (instance_list, gob);
+ qof_collection_set_data (coll, instance_list);
+}
+
+/* =================================================================== */
+
+static gpointer
+qof_gobject_getter (gpointer data, QofParam *getter)
+{
+ GObject *gob = data;
+ const char *str;
+
+ GParamSpec *gps = getter->param_userdata;
+
+ /* Note that the return type must actually be of type
+ * getter->param_type but we just follow the hard-coded
+ * mapping below ... */
+ if (G_IS_PARAM_SPEC_STRING(gps))
+ {
+ GValue gval = {G_TYPE_INVALID};
+ g_value_init (&gval, G_TYPE_STRING);
+ g_object_get_property (gob, getter->param_name, &gval);
+
+ str = g_value_get_string (&gval);
+ return (gpointer) str;
+ }
+ else
+ if (G_IS_PARAM_SPEC_INT(gps))
+ {
+ int ival;
+
+ GValue gval = {G_TYPE_INVALID};
+ g_value_init (&gval, G_TYPE_INT);
+ g_object_get_property (gob, getter->param_name, &gval);
+
+ ival = g_value_get_int (&gval);
+ return (gpointer) ival;
+ }
+ else
+ if (G_IS_PARAM_SPEC_UINT(gps))
+ {
+ int ival;
+ GValue gval = {G_TYPE_INVALID};
+ g_value_init (&gval, G_TYPE_UINT);
+ g_object_get_property (gob, getter->param_name, &gval);
+
+ ival = g_value_get_uint (&gval);
+ return (gpointer) ival;
+ }
+ else
+ if (G_IS_PARAM_SPEC_BOOLEAN(gps))
+ {
+ int ival;
+
+ GValue gval = {G_TYPE_INVALID};
+ g_value_init (&gval, G_TYPE_BOOLEAN);
+ g_object_get_property (gob, getter->param_name, &gval);
+
+ ival = g_value_get_boolean (&gval);
+ return (gpointer) ival;
+ }
+
+ PWARN ("unhandled parameter type %s for paramter %s",
+ G_PARAM_SPEC_TYPE_NAME(gps), getter->param_name);
+ return NULL;
+}
+
+static double
+qof_gobject_double_getter (gpointer data, QofParam *getter)
+{
+ GObject *gob = data;
+ double fval;
+
+ GParamSpec *gps = getter->param_userdata;
+
+ /* Note that the return type must actually be of type
+ * getter->param_type but we just follow the hard-coded
+ * mapping below ... */
+ if (G_IS_PARAM_SPEC_FLOAT(gps))
+ {
+ GValue gval = {G_TYPE_INVALID};
+ g_value_init (&gval, G_TYPE_FLOAT);
+ g_object_get_property (gob, getter->param_name, &gval);
+
+ fval = g_value_get_float (&gval);
+ return fval;
+ }
+ else
+ if (G_IS_PARAM_SPEC_DOUBLE(gps))
+ {
+ GValue gval = {G_TYPE_INVALID};
+ g_value_init (&gval, G_TYPE_DOUBLE);
+ g_object_get_property (gob, getter->param_name, &gval);
+
+ fval = g_value_get_double (&gval);
+ return fval;
+ }
+
+ PWARN ("unhandled parameter type %s for paramter %s",
+ G_PARAM_SPEC_TYPE_NAME(gps), getter->param_name);
+ return 0.0;
+}
+
+/* =================================================================== */
+/* Loop over every instance of the given type in the collection
+ * of instances that we have on hand.
+ */
+static void
+qof_gobject_foreach (QofCollection *coll, QofEntityForeachCB cb, gpointer ud)
+{
+ GSList *n;
+ n = qof_collection_get_data (coll);
+ for (; n; n=n->next)
+ {
+ cb (n->data, ud);
+ }
+}
+
+/* =================================================================== */
+
+void
+qof_gobject_register (QofType e_type, GObjectClass *obclass)
+{
+ int i;
+ int j;
+ QofParam *qof_param_list, *qpar;
+ QofObject *class_def;
+ GParamSpec **prop_list, *gparam;
+ guint n_props;
+
+ /* Get the GObject properties, convert to QOF properties */
+ prop_list = g_object_class_list_properties (obclass, &n_props);
+
+ qof_param_list = g_new0 (QofParam, n_props);
+ paramList = g_slist_prepend (paramList, qof_param_list);
+
+ PINFO ("object %s has %d props", e_type, n_props);
+ j=0;
+ for (i=0; i<n_props; i++)
+ {
+ gparam = prop_list[i];
+ qpar = &qof_param_list[j];
+
+ PINFO ("param %d %s is type %s",
+ i, gparam->name, G_PARAM_SPEC_TYPE_NAME(gparam));
+
+ qpar->param_name = g_param_spec_get_name (gparam);
+ qpar->param_getfcn = (QofAccessFunc)qof_gobject_getter;
+ qpar->param_setfcn = NULL;
+ qpar->param_userdata = gparam;
+ if ((G_IS_PARAM_SPEC_INT(gparam)) ||
+ (G_IS_PARAM_SPEC_UINT(gparam)) ||
+ (G_IS_PARAM_SPEC_ENUM(gparam)) ||
+ (G_IS_PARAM_SPEC_FLAGS(gparam)))
+ {
+ qpar->param_type = QOF_TYPE_INT32;
+ j++;
+ }
+ else
+ if ((G_IS_PARAM_SPEC_INT64(gparam)) ||
+ (G_IS_PARAM_SPEC_UINT64(gparam)))
+ {
+ qpar->param_type = QOF_TYPE_INT64;
+ j++;
+ }
+ else
+ if (G_IS_PARAM_SPEC_BOOLEAN(gparam))
+ {
+ qpar->param_type = QOF_TYPE_BOOLEAN;
+ j++;
+ }
+ else
+ if (G_IS_PARAM_SPEC_STRING(gparam))
+ {
+ qpar->param_type = QOF_TYPE_STRING;
+ j++;
+ }
+ else
+ if ((G_IS_PARAM_SPEC_POINTER(gparam)) ||
+ (G_IS_PARAM_SPEC_OBJECT(gparam)))
+ {
+ /* No-op, silently ignore. Someday we should handle this ... */
+ }
+ else
+ if ((G_IS_PARAM_SPEC_FLOAT(gparam)) ||
+ (G_IS_PARAM_SPEC_DOUBLE(gparam)))
+ {
+ qpar->param_getfcn = (QofAccessFunc) qof_gobject_double_getter;
+ qpar->param_type = QOF_TYPE_DOUBLE;
+ j++;
+ }
+ else
+ if (G_IS_PARAM_SPEC_CHAR(gparam))
+ {
+ qpar->param_type = QOF_TYPE_CHAR;
+ j++;
+ }
+ else
+ {
+ PWARN ("Unknown/unhandled parameter type %s on %s:%s\n",
+ G_PARAM_SPEC_TYPE_NAME(gparam), e_type, qpar->param_name);
+ }
+ }
+
+ /* NULL-terminated list! */
+ qof_param_list[j].param_type = NULL;
+
+ qof_class_register (e_type, NULL, qof_param_list);
+
+ /* ------------------------------------------------------ */
+ /* Now do the class itself */
+ class_def = g_new0 (QofObject, 1);
+ classList = g_slist_prepend (classList, class_def);
+
+ class_def->interface_version = QOF_OBJECT_VERSION;
+ class_def->e_type = e_type;
+ /* We could let the user specify a "nick" here, but
+ * the actual class name seems reasonable, e.g. for debugging. */
+ class_def->type_label = G_OBJECT_CLASS_NAME (obclass);
+ class_def->create = NULL;
+ class_def->book_begin = NULL;
+ class_def->book_end = NULL;
+ class_def->is_dirty = NULL;
+ class_def->mark_clean = NULL;
+ class_def->foreach = qof_gobject_foreach;
+ class_def->printable = NULL;
+ class_def->version_cmp = NULL;
+
+ qof_object_register (class_def);
+}
+
+/* ======================= END OF FILE ================================ */
Added: gnucash/trunk/lib/libqof/qof/qofgobj.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofgobj.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofgobj.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,87 @@
+/********************************************************************\
+ * qofgobj.h -- QOF to GLib GObject mapping *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#ifndef QOF_GOBJ_H
+#define QOF_GOBJ_H
+
+/** @addtogroup Object
+ @{ */
+/** @addtogroup GObject GLib GObjects
+ The API defined in this file allows a user to register any
+ GLib GObject (and any object derived from one, e.g. GTK/Gnome)
+ with the QOF system, as a QOF Object Class. This allows
+ the QOF Query routines to be used to search over collections
+ of GObjects.
+
+ XXX Only GObject properties are searchable, data and other
+ hanging off the GObject is not. Fix this. This needs fixing.
+
+@{ */
+/** @file qofgobj.h
+ @brief QOF to GLib GObject mapping
+ @author Copyright (C) 2004 Linas Vepstas <linas at linas.org>
+*/
+
+
+#include <glib-object.h>
+#include "qofbook.h"
+#include "qofclass.h"
+
+/** Initalize and shut down this subsystem. */
+void qof_gobject_init(void);
+void qof_gobject_shutdown (void);
+
+/** Register a GObject class with the QOF subsystem.
+ * Doing this will make the properties associated with
+ * this GObject searchable using the QOF subsystem.
+ *
+ * The QofType can be any string you desire, although typically
+ * you might want to set it to G_OBJECT_CLASS_NAME() of the
+ * object class. Note that this type will become the name of
+ * the "table" that is searched by SQL queries:
+ * e.g. in order to be able to say "SELECT * FROM MyStuff;"
+ * you must first say:
+ * qof_gobject_register ("MyStuff", gobj_class);
+ */
+void qof_gobject_register (QofType type, GObjectClass *obclass);
+
+/** Register an instance of a GObject with the QOF subsystem.
+ *
+ * The QofType can be any string you desire, although typically
+ * you might want to set it to G_OBJECT_CLASS_NAME() of the
+ * object class. Note that this type will become the name of
+ * the "table" that is searched by SQL queries:
+ * e.g. in order to be able to say "SELECT * FROM MyStuff;"
+ * you must first say:
+ * qof_gobject_register_instance (book, "MyStuff", obj);
+ *
+ * The 'book' argument specifies an anchor point for the collection
+ * of all of the registered instances. By working with disjoint books,
+ * you can have multiple disjoint searchable sets of objects.
+ */
+
+void qof_gobject_register_instance (QofBook *book, QofType, GObject *);
+
+#endif /* QOF_GOBJ_H */
+/** @} */
+/** @} */
+
Added: gnucash/trunk/lib/libqof/qof/qofid-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofid-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofid-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,59 @@
+/********************************************************************\
+ * qofid-p.h -- QOF entity identifier engine-only API *
+ * Copyright (C) 2000 Dave Peticolas <peticola at cs.ucdavis.edu> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup Object
+ @{ */
+/** @addtogroup Object_Private
+ Private interfaces, not meant to be used by applications.
+ @{ */
+/** @name Entity_Private
+ @{ */
+
+#ifndef QOF_ID_P_H
+#define QOF_ID_P_H
+
+#include <glib.h>
+
+#include "qofid.h"
+
+/* This file defines an engine-only API for using gnucash entity
+ * identifiers. */
+
+/** Set the ID of the entity, over-riding teh previous ID.
+ * Very dangerous, use only for file i/o work.
+ */
+void qof_entity_set_guid (QofEntity *ent, const GUID *guid);
+
+/** Take entity, remove it from whatever collection its currently
+ * in, and place it in a new collection. To be used only for
+ * moving entity from one book to another.
+ */
+void qof_collection_insert_entity (QofCollection *, QofEntity *);
+
+/** reset value of dirty flag */
+void qof_collection_mark_clean (QofCollection *);
+void qof_collection_mark_dirty (QofCollection *);
+
+/* @} */
+/* @} */
+/* @} */
+#endif /* QOF_ID_P_H */
Added: gnucash/trunk/lib/libqof/qof/qofid.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofid.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofid.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,396 @@
+/********************************************************************\
+ * qofid.c -- QOF entity identifier implementation *
+ * Copyright (C) 2000 Dave Peticolas <dave at krondo.com> *
+ * Copyright (C) 2003 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#include "config.h"
+
+#include <string.h>
+#include <glib.h>
+
+#include "qofid.h"
+#include "qofid-p.h"
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+
+#define CACHE_INSERT(str) g_cache_insert(gnc_engine_get_string_cache(), (gpointer)(str));
+#define CACHE_REMOVE(str) g_cache_remove(gnc_engine_get_string_cache(), (gpointer)(str));
+
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+struct QofCollection_s
+{
+ QofIdType e_type;
+ gboolean is_dirty;
+
+ GHashTable * hash_of_entities;
+ gpointer data; /* place where object class can hang arbitrary data */
+};
+
+/* =============================================================== */
+
+static void qof_collection_remove_entity (QofEntity *ent);
+
+void
+qof_entity_init (QofEntity *ent, QofIdType type, QofCollection * tab)
+{
+ g_return_if_fail (NULL != tab);
+
+ /* XXX We passed redundant info to this routine ... but I think that's
+ * OK, it might eliminate programming errors. */
+ if (safe_strcmp(tab->e_type, type))
+ {
+ PERR ("attempt to insert \"%s\" into \"%s\"", type, tab->e_type);
+ return;
+ }
+ ent->e_type = CACHE_INSERT (type);
+
+ do
+ {
+ guid_new(&ent->guid);
+
+ if (NULL == qof_collection_lookup_entity (tab, &ent->guid)) break;
+
+ PWARN("duplicate id created, trying again");
+ } while(1);
+
+ ent->collection = tab;
+
+ qof_collection_insert_entity (tab, ent);
+}
+
+void
+qof_entity_release (QofEntity *ent)
+{
+ if (!ent->collection) return;
+ qof_collection_remove_entity (ent);
+ CACHE_REMOVE (ent->e_type);
+ ent->e_type = NULL;
+}
+
+
+/* This is a restricted function, should be used only during
+ * read from file */
+void
+qof_entity_set_guid (QofEntity *ent, const GUID *guid)
+{
+ QofCollection *col;
+ if (guid_equal (guid, &ent->guid)) return;
+
+ col = ent->collection;
+ qof_collection_remove_entity (ent);
+ ent->guid = *guid;
+ qof_collection_insert_entity (col, ent);
+}
+
+const GUID *
+qof_entity_get_guid (QofEntity *ent)
+{
+ if (!ent) return guid_null();
+ return &ent->guid;
+}
+
+/* =============================================================== */
+
+static guint
+id_hash (gconstpointer key)
+{
+ const GUID *guid = key;
+
+ if (key == NULL)
+ return 0;
+
+ /* Compiler should optimize this all away! */
+ if (sizeof(guint) <= 16)
+ return *((guint *) guid->data);
+ else
+ {
+ guint hash = 0;
+ unsigned int i, j;
+
+ for (i = 0, j = 0; i < sizeof(guint); i++, j++)
+ {
+ if (j == 16)
+ j = 0;
+
+ hash <<= 4;
+ hash |= guid->data[j];
+ }
+
+ return hash;
+ }
+}
+
+static gboolean
+id_compare(gconstpointer key_1, gconstpointer key_2)
+{
+ return guid_equal (key_1, key_2);
+}
+
+QofCollection *
+qof_collection_new (QofIdType type)
+{
+ QofCollection *col;
+ col = g_new0(QofCollection, 1);
+ col->e_type = CACHE_INSERT (type);
+ col->hash_of_entities = g_hash_table_new (id_hash, id_compare);
+ col->data = NULL;
+ return col;
+}
+
+void
+qof_collection_destroy (QofCollection *col)
+{
+ CACHE_REMOVE (col->e_type);
+ g_hash_table_destroy(col->hash_of_entities);
+ col->e_type = NULL;
+ col->hash_of_entities = NULL;
+ col->data = NULL; /** XXX there should be a destroy notifier for this */
+ g_free (col);
+}
+
+/* =============================================================== */
+/* getters */
+
+QofIdType
+qof_collection_get_type (QofCollection *col)
+{
+ return col->e_type;
+}
+
+/* =============================================================== */
+
+static void
+qof_collection_remove_entity (QofEntity *ent)
+{
+ QofCollection *col;
+ if (!ent) return;
+ col = ent->collection;
+ if (!col) return;
+ g_hash_table_remove (col->hash_of_entities, &ent->guid);
+ ent->collection = NULL;
+}
+
+void
+qof_collection_insert_entity (QofCollection *col, QofEntity *ent)
+{
+ if (!col || !ent) return;
+ if (guid_equal(&ent->guid, guid_null())) return;
+ g_return_if_fail (col->e_type == ent->e_type);
+ qof_collection_remove_entity (ent);
+ g_hash_table_insert (col->hash_of_entities, &ent->guid, ent);
+ ent->collection = col;
+}
+
+gboolean
+qof_collection_add_entity (QofCollection *coll, QofEntity *ent)
+{
+ QofEntity *e;
+
+ e = NULL;
+ if (!coll || !ent) { return FALSE; }
+ if (guid_equal(&ent->guid, guid_null())) { return FALSE; }
+ g_return_val_if_fail (coll->e_type == ent->e_type, FALSE);
+ e = qof_collection_lookup_entity(coll, &ent->guid);
+ if ( e != NULL ) { return FALSE; }
+ g_hash_table_insert (coll->hash_of_entities, &ent->guid, ent);
+ return TRUE;
+}
+
+static void
+collection_merge_cb (QofEntity *ent, gpointer data)
+{
+ QofCollection *target;
+
+ target = (QofCollection*)data;
+ qof_collection_add_entity(target, ent);
+}
+
+gboolean
+qof_collection_merge (QofCollection *target, QofCollection *merge)
+{
+ if(!target || !merge) { return FALSE; }
+ g_return_val_if_fail (target->e_type == merge->e_type, FALSE);
+ qof_collection_foreach(merge, collection_merge_cb, target);
+ return TRUE;
+}
+
+static void
+collection_compare_cb (QofEntity *ent, gpointer user_data)
+{
+ QofCollection *target;
+ QofEntity *e;
+ gint value;
+
+ e = NULL;
+ target = (QofCollection*)user_data;
+ if (!target || !ent) { return; }
+ value = *(gint*)qof_collection_get_data(target);
+ if (value != 0) { return; }
+ if (guid_equal(&ent->guid, guid_null()))
+ {
+ value = -1;
+ qof_collection_set_data(target, &value);
+ return;
+ }
+ g_return_if_fail (target->e_type == ent->e_type);
+ e = qof_collection_lookup_entity(target, &ent->guid);
+ if ( e == NULL )
+ {
+ value = 1;
+ qof_collection_set_data(target, &value);
+ return;
+ }
+ value = 0;
+ qof_collection_set_data(target, &value);
+}
+
+gint
+qof_collection_compare (QofCollection *target, QofCollection *merge)
+{
+ gint value;
+
+ value = 0;
+ if (!target && !merge) { return 0; }
+ if (target == merge) { return 0; }
+ if (!target && merge) { return -1; }
+ if (target && !merge) { return 1; }
+ if(target->e_type != merge->e_type) { return -1; }
+ qof_collection_set_data(target, &value);
+ qof_collection_foreach(merge, collection_compare_cb, target);
+ value = *(gint*)qof_collection_get_data(target);
+ if(value == 0) {
+ qof_collection_set_data(merge, &value);
+ qof_collection_foreach(target, collection_compare_cb, merge);
+ value = *(gint*)qof_collection_get_data(merge);
+ }
+ return value;
+}
+
+QofEntity *
+qof_collection_lookup_entity (QofCollection *col, const GUID * guid)
+{
+ QofEntity *ent;
+ g_return_val_if_fail (col, NULL);
+ if (guid == NULL) return NULL;
+ ent = g_hash_table_lookup (col->hash_of_entities, guid->data);
+ return ent;
+}
+
+QofCollection *
+qof_collection_from_glist (QofIdType type, GList *glist)
+{
+ QofCollection *coll;
+ QofEntity *ent;
+ GList *list;
+
+ coll = qof_collection_new(type);
+ for(list = glist; list != NULL; list = list->next)
+ {
+ ent = (QofEntity*)list->data;
+ if(FALSE == qof_collection_add_entity(coll, ent))
+ {
+ return NULL;
+ }
+ }
+ return coll;
+}
+
+guint
+qof_collection_count (QofCollection *col)
+{
+ guint c;
+
+ c = g_hash_table_size(col->hash_of_entities);
+ return c;
+}
+
+/* =============================================================== */
+
+gboolean
+qof_collection_is_dirty (QofCollection *col)
+{
+ if (!col) return FALSE;
+ return col->is_dirty;
+}
+
+void
+qof_collection_mark_clean (QofCollection *col)
+{
+ if (!col) return;
+ col->is_dirty = FALSE;
+}
+
+void
+qof_collection_mark_dirty (QofCollection *col)
+{
+ if (!col) return;
+ col->is_dirty = TRUE;
+}
+
+/* =============================================================== */
+
+gpointer
+qof_collection_get_data (QofCollection *col)
+{
+ if (!col) return NULL;
+ return col->data;
+}
+
+void
+qof_collection_set_data (QofCollection *col, gpointer user_data)
+{
+ if (!col) return;
+ col->data = user_data;
+}
+
+/* =============================================================== */
+
+struct _iterate {
+ QofEntityForeachCB fcn;
+ gpointer data;
+};
+
+static void foreach_cb (gpointer key, gpointer item, gpointer arg)
+{
+ struct _iterate *iter = arg;
+ QofEntity *ent = item;
+
+ iter->fcn (ent, iter->data);
+}
+
+void
+qof_collection_foreach (QofCollection *col,
+ QofEntityForeachCB cb_func, gpointer user_data)
+{
+ struct _iterate iter;
+
+ g_return_if_fail (col);
+ g_return_if_fail (cb_func);
+
+ iter.fcn = cb_func;
+ iter.data = user_data;
+
+ g_hash_table_foreach (col->hash_of_entities, foreach_cb, &iter);
+}
+
+/* =============================================================== */
Added: gnucash/trunk/lib/libqof/qof/qofid.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofid.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofid.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,264 @@
+/********************************************************************\
+ * qofid.h -- QOF entity type identification system *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#ifndef QOF_ID_H
+#define QOF_ID_H
+
+/** @addtogroup Entity
+ @{ */
+/** @addtogroup Entities
+
+ This file defines an API that adds types to the GUID's.
+ GUID's with types can be used to identify and reference
+ typed entities.
+
+ The idea here is that a GUID can be used to uniquely identify
+ some thing. By adding a type, one can then talk about the
+ type of thing identified. By adding a collection, one can
+ then work with a handle to a collection of things of a given
+ type, each uniquely identified by a given ID. QOF Entities
+ can be used independently of any other part of the system.
+ In particular, Entities can be useful even if one is not using
+ the Query ond Object parts of the QOF system.
+
+ Identifiers are globally-unique and permanent, i.e., once
+ an entity has been assigned an identifier, it retains that same
+ identifier for its lifetime.
+ Identifiers can be encoded as hex strings.
+
+ GUID Identifiers are 'typed' with strings. The native ids used
+ by QOF are defined below. An id with type QOF_ID_NONE does not
+ refer to any entity, although that may change (???). An id with
+ type QOF_ID_NULL does not refer to any entity, and will never refer
+ to any entity. An identifier with any other type may refer to an
+ actual entity, but that is not guaranteed (??? Huh?). If an id
+ does refer to an entity, the type of the entity will match the
+ type of the identifier.
+
+ If you have a type name, and you want to have a way of finding
+ a collection that is associated with that type, then you must use
+ Books.
+
+ Entities can refer to other entities as well as to the basic
+ QOF types, using the qofclass parameters.
+
+ @{ */
+/** @file qofid.h
+ @brief QOF entity type identification system
+ @author Copyright (C) 2000 Dave Peticolas <peticola at cs.ucdavis.edu>
+ @author Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+*/
+
+#include <string.h>
+#include "guid.h"
+
+/** QofIdType declaration */
+typedef const char * QofIdType;
+/** QofIdTypeConst declaration */
+typedef const char * QofIdTypeConst;
+/** QofLogModule declaration */
+typedef const gchar* QofLogModule;
+
+#define QOF_ID_NONE NULL
+#define QOF_ID_NULL "null"
+
+#define QOF_ID_BOOK "Book"
+#define QOF_ID_SESSION "Session"
+
+/** simple,cheesy cast but holds water for now */
+#define QOF_ENTITY(object) ((QofEntity *)(object))
+
+/** Inline string comparision; compiler will optimize away most of this */
+#define QSTRCMP(da,db) ({ \
+ int val = 0; \
+ if ((da) && (db)) { \
+ if ((da) != (db)) { \
+ val = strcmp ((da), (db)); \
+ } \
+ } else \
+ if ((!(da)) && (db)) { \
+ val = -1; \
+ } else \
+ if ((da) && (!(db))) { \
+ val = 1; \
+ } \
+ val; /* block assumes value of last statment */ \
+})
+
+/** return TRUE if object is of the given type */
+#define QOF_CHECK_TYPE(obj,type) (0 == QSTRCMP((type),(((QofEntity *)(obj))->e_type)))
+
+/** cast object to the indicated type, print error message if its bad */
+#define QOF_CHECK_CAST(obj,e_type,c_type) ( \
+ QOF_CHECK_TYPE((obj),(e_type)) ? \
+ (c_type *) (obj) : \
+ (c_type *) ({ \
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, \
+ "Error: Bad QofEntity at %s:%d", __FILE__, __LINE__); \
+ (obj); \
+ }))
+
+/** QofEntity declaration */
+typedef struct QofEntity_s QofEntity;
+/** QofCollection declaration
+
+ at param e_type QofIdType
+ at param is_dirty gboolean
+ at param hash_of_entities GHashTable
+ at param data gpointer, place where object class can hang arbitrary data
+
+*/
+typedef struct QofCollection_s QofCollection;
+
+/** QofEntity structure
+
+ at param e_type Entity type
+ at param guid GUID for the entity
+ at param collection Entity collection
+*/
+
+struct QofEntity_s
+{
+ QofIdType e_type;
+ GUID guid;
+ QofCollection * collection;
+};
+
+/** @name QOF Entity Initialization & Shutdown
+ @{ */
+/** Initialise the memory associated with an entity */
+void qof_entity_init (QofEntity *, QofIdType, QofCollection *);
+
+/** Release the data associated with this entity. Dont actually free
+ * the memory associated with the instance. */
+void qof_entity_release (QofEntity *);
+/** @} */
+
+/** Return the GUID of this entity */
+const GUID * qof_entity_get_guid (QofEntity *);
+
+/** @name Collections of Entities
+ @{ */
+
+/** create a new collection of entities of type */
+QofCollection * qof_collection_new (QofIdType type);
+
+/** return the number of entities in the collection. */
+guint qof_collection_count (QofCollection *col);
+
+/** destroy the collection */
+void qof_collection_destroy (QofCollection *col);
+
+/** return the type that the collection stores */
+QofIdType qof_collection_get_type (QofCollection *);
+
+/** Find the entity going only from its guid */
+QofEntity * qof_collection_lookup_entity (QofCollection *, const GUID *);
+
+/** Callback type for qof_entity_foreach */
+typedef void (*QofEntityForeachCB) (QofEntity *, gpointer user_data);
+
+/** Call the callback for each entity in the collection. */
+void qof_collection_foreach (QofCollection *,
+ QofEntityForeachCB, gpointer user_data);
+
+/** Store and retreive arbitrary object-defined data
+ *
+ * XXX We need to add a callback for when the collection is being
+ * destroyed, so that the user has a chance to clean up anything
+ * that was put in the 'data' member here.
+ */
+gpointer qof_collection_get_data (QofCollection *col);
+void qof_collection_set_data (QofCollection *col, gpointer user_data);
+
+/** Return value of 'dirty' flag on collection */
+gboolean qof_collection_is_dirty (QofCollection *col);
+
+/** @name QOF_TYPE_COLLECT: Linking one entity to many of one type
+
+\note These are \b NOT the same as the main collections in the book.
+
+QOF_TYPE_COLLECT is a secondary collection, used to select entities
+of one object type as references of another entity.
+\sa QOF_TYPE_CHOICE.
+
+@{
+*/
+/** \brief Add an entity to a QOF_TYPE_COLLECT.
+
+\note These are \b NOT the same as the main collections in the book.
+
+Entities can be
+freely added and merged across these secondary collections, they
+will not be removed from the original collection as they would
+by using ::qof_entity_insert_entity or ::qof_entity_remove_entity.
+
+*/
+gboolean
+qof_collection_add_entity (QofCollection *coll, QofEntity *ent);
+
+/** \brief Merge two QOF_TYPE_COLLECT of the same type.
+
+\note \b NOT the same as the main collections in the book.
+
+QOF_TYPE_COLLECT uses a secondary collection, independent of
+those in the book. Entities will not be removed from the
+original collection as when using ::qof_entity_insert_entity
+or ::qof_entity_remove_entity.
+
+*/
+gboolean
+qof_collection_merge (QofCollection *target, QofCollection *merge);
+
+/** \brief Compare two secondary collections.
+
+Performs a deep comparision of the collections. Each QofEntity in
+each collection is looked up in the other collection, via the GUID.
+
+\return 0 if the collections are identical or both are NULL
+otherwise -1 if target is NULL or either collection contains an entity with an invalid
+GUID or if the types of the two collections do not match,
+or +1 if merge is NULL or if any entity exists in one collection but
+not in the other.
+*/
+gint
+qof_collection_compare (QofCollection *target, QofCollection *merge);
+
+/** \brief Create a secondary collection from a GList
+
+ at param type The QofIdType of the QofCollection \b and of
+ \b all entities in the GList.
+ at param glist GList of entities of the same QofIdType.
+
+ at return NULL if any of the entities fail to match the
+ QofCollection type, else a pointer to the collection
+ on success.
+*/
+QofCollection*
+qof_collection_from_glist (QofIdType type, GList *glist);
+
+/** @} */
+/** @} */
+
+#endif /* QOF_ID_H */
+/** @} */
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/qofinstance-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofinstance-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofinstance-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,97 @@
+/********************************************************************\
+ * qofinstance-p.h -- private fields common to all object instances *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/*
+ * Object instance holds many common fields that most
+ * gnucash objects use.
+ *
+ * Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+ */
+
+/** @addtogroup Object
+ @{ */
+/** @addtogroup Object_Private
+ Private interfaces, not meant to be used by applications.
+ @{ */
+/** @name Instance_Private
+ @{ */
+
+#ifndef QOF_INSTANCE_P_H
+#define QOF_INSTANCE_P_H
+
+#include "qofinstance.h"
+
+/**
+ * UNDER CONSTRUCTION!
+ * This is mostly scaffolding for now,
+ * eventually, it may hold more fields, such as refrence counting...
+ *
+ */
+struct QofInstance_s
+{
+ /** Globally unique id identifying this instance */
+ QofEntity entity;
+
+ /** The entity_table in which this instance is stored */
+ QofBook * book;
+
+ /** kvp_data is a key-value pair database for storing arbirtary
+ * information associated with this instance.
+ * See src/engine/kvp_doc.txt for a list and description of the
+ * important keys. */
+ KvpFrame *kvp_data;
+
+ /** Timestamp used to track the last modification to this
+ * instance. Typically used to compare two versions of the
+ * same object, to see which is newer. When used with the
+ * SQL backend, this field is reserved for SQL use, to compare
+ * the version in local memory to the remote, server version.
+ */
+ Timespec last_update;
+
+ /** Keep track of nesting level of begin/end edit calls */
+ int editlevel;
+
+ /** In process of being destroyed */
+ gboolean do_free;
+
+ /** dirty/clean flag. If dirty, then this instance has been modified,
+ * but has not yet been written out to storage (file/database)
+ */
+ gboolean dirty;
+};
+
+/** reset the dirty flag */
+void qof_instance_mark_clean (QofInstance *);
+
+void qof_instance_set_slots (QofInstance *, KvpFrame *);
+
+/** Set the last_update time. Reserved for use by the SQL backend;
+ * used for comparing version in local memory to that in remote
+ * server.
+ */
+void qof_instance_set_last_update (QofInstance *inst, Timespec ts);
+
+/* @} */
+/* @} */
+/* @} */
+
+#endif /* QOF_INSTANCE_P_H */
Added: gnucash/trunk/lib/libqof/qof/qofinstance.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofinstance.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofinstance.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,245 @@
+/********************************************************************\
+ * qofinstance.c -- handler for fields common to all objects *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+/*
+ * Object instance holds many common fields that most
+ * gnucash objects use.
+ *
+ * Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+ */
+
+#include "gnc-trace.h"
+
+#include "kvp-util-p.h"
+#include "qofbook.h"
+#include "qofbook-p.h"
+#include "qofid.h"
+#include "qofid-p.h"
+#include "qofinstance.h"
+#include "qofinstance-p.h"
+
+static QofLogModule log_module = QOF_MOD_ENGINE;
+
+/* ========================================================== */
+
+QofInstance*
+qof_instance_create (QofIdType type, QofBook *book)
+{
+ QofInstance *inst;
+
+ inst = g_new0(QofInstance, 1);
+ qof_instance_init(inst, type, book);
+ return inst;
+}
+
+void
+qof_instance_init (QofInstance *inst, QofIdType type, QofBook *book)
+{
+ QofCollection *col;
+
+ inst->book = book;
+ inst->kvp_data = kvp_frame_new();
+ inst->last_update.tv_sec = 0;
+ inst->last_update.tv_nsec = -1;
+ inst->editlevel = 0;
+ inst->do_free = FALSE;
+ inst->dirty = FALSE;
+
+ col = qof_book_get_collection (book, type);
+ qof_entity_init (&inst->entity, type, col);
+}
+
+void
+qof_instance_release (QofInstance *inst)
+{
+ kvp_frame_delete (inst->kvp_data);
+ inst->editlevel = 0;
+ inst->do_free = FALSE;
+ inst->dirty = FALSE;
+ qof_entity_release (&inst->entity);
+}
+
+const GUID *
+qof_instance_get_guid (QofInstance *inst)
+{
+ if (!inst) return NULL;
+ return &inst->entity.guid;
+}
+
+QofBook *
+qof_instance_get_book (QofInstance *inst)
+{
+ if (!inst) return NULL;
+ return inst->book;
+}
+
+KvpFrame*
+qof_instance_get_slots (QofInstance *inst)
+{
+ if (!inst) return NULL;
+ return inst->kvp_data;
+}
+
+Timespec
+qof_instance_get_last_update (QofInstance *inst)
+{
+ if (!inst)
+ {
+ Timespec ts = {0,-1};
+ return ts;
+ }
+ return inst->last_update;
+}
+
+int
+qof_instance_version_cmp (QofInstance *left, QofInstance *right)
+{
+ if (!left && !right) return 0;
+ if (!left) return -1;
+ if (!right) return +1;
+ if (left->last_update.tv_sec < right->last_update.tv_sec) return -1;
+ if (left->last_update.tv_sec > right->last_update.tv_sec) return +1;
+ if (left->last_update.tv_nsec < right->last_update.tv_nsec) return -1;
+ if (left->last_update.tv_nsec > right->last_update.tv_nsec) return +1;
+ return 0;
+}
+
+gboolean
+qof_instance_is_dirty (QofInstance *inst)
+{
+ QofCollection *coll;
+
+ if (!inst) { return FALSE; }
+ coll = inst->entity.collection;
+ if(qof_collection_is_dirty(coll)) { return inst->dirty; }
+ inst->dirty = FALSE;
+ return FALSE;
+}
+
+void
+qof_instance_set_dirty(QofInstance* inst)
+{
+ QofCollection *coll;
+
+ inst->dirty = TRUE;
+ coll = inst->entity.collection;
+ qof_collection_mark_dirty(coll);
+}
+
+gboolean
+qof_instance_check_edit(QofInstance *inst)
+{
+ if(inst->editlevel > 0) { return TRUE; }
+ return FALSE;
+}
+
+gboolean
+qof_instance_do_free(QofInstance *inst)
+{
+ return inst->do_free;
+}
+
+void
+qof_instance_mark_free(QofInstance *inst)
+{
+ inst->do_free = TRUE;
+}
+
+/* ========================================================== */
+/* setters */
+
+void
+qof_instance_mark_clean (QofInstance *inst)
+{
+ if(!inst) return;
+ inst->dirty = FALSE;
+}
+
+void
+qof_instance_set_slots (QofInstance *inst, KvpFrame *frm)
+{
+ if (!inst) return;
+ if (inst->kvp_data && (inst->kvp_data != frm))
+ {
+ kvp_frame_delete(inst->kvp_data);
+ }
+
+ inst->dirty = TRUE;
+ inst->kvp_data = frm;
+}
+
+void
+qof_instance_set_last_update (QofInstance *inst, Timespec ts)
+{
+ if (!inst) return;
+ inst->last_update = ts;
+}
+
+/* ========================================================== */
+
+void
+qof_instance_gemini (QofInstance *to, QofInstance *from)
+{
+ time_t now;
+
+ /* Books must differ for a gemini to be meaningful */
+ if (!from || !to || (from->book == to->book)) return;
+
+ now = time(0);
+
+ /* Make a note of where the copy came from */
+ gnc_kvp_bag_add (to->kvp_data, "gemini", now,
+ "inst_guid", &from->entity.guid,
+ "book_guid", &from->book->inst.entity.guid,
+ NULL);
+ gnc_kvp_bag_add (from->kvp_data, "gemini", now,
+ "inst_guid", &to->entity.guid,
+ "book_guid", &to->book->inst.entity.guid,
+ NULL);
+
+ to->dirty = TRUE;
+}
+
+QofInstance *
+qof_instance_lookup_twin (QofInstance *src, QofBook *target_book)
+{
+ QofCollection *col;
+ KvpFrame *fr;
+ GUID * twin_guid;
+ QofInstance * twin;
+
+ if (!src || !target_book) return NULL;
+ ENTER (" ");
+
+ fr = gnc_kvp_bag_find_by_guid (src->kvp_data, "gemini",
+ "book_guid", &target_book->inst.entity.guid);
+
+ twin_guid = kvp_frame_get_guid (fr, "inst_guid");
+
+ col = qof_book_get_collection (target_book, src->entity.e_type);
+ twin = (QofInstance *) qof_collection_lookup_entity (col, twin_guid);
+
+ LEAVE (" found twin=%p", twin);
+ return twin;
+}
+
+/* ========================== END OF FILE ======================= */
Added: gnucash/trunk/lib/libqof/qof/qofinstance.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofinstance.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofinstance.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,130 @@
+/********************************************************************\
+ * qofinstance.h -- fields common to all object instances *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup Entity
+ @{ */
+/** @addtogroup Instance
+ Qof Instances are a derived type of QofEntity. The Instance
+ adds some common features and functions that most objects
+ will want to use.
+
+ @{ */
+/** @file qofinstance.h
+ * @brief Object instance holds common fields that most gnucash objects use.
+ *
+ * @author Copyright (C) 2003,2004 Linas Vepstas <linas at linas.org>
+ */
+
+#ifndef QOF_INSTANCE_H
+#define QOF_INSTANCE_H
+
+#include "guid.h"
+#include "gnc-date.h"
+#include "kvp_frame.h"
+#include "qofbook.h"
+#include "qofid.h"
+
+/* --- type macros --- */
+/* cheesy, but will do for now, eventually should be more gtk-like, handle
+ * thunks, etc. */
+#define QOF_INSTANCE(object) ((QofInstance *)(object))
+
+typedef struct QofInstance_s QofInstance;
+
+/** Initialise the memory associated with an instance */
+void qof_instance_init (QofInstance *, QofIdType, QofBook *);
+
+/** release the data associated with this instance. Dont actually free
+ * the memory associated with the instance. */
+void qof_instance_release (QofInstance *inst);
+
+/** Return the book pointer */
+QofBook * qof_instance_get_book (QofInstance *);
+
+/** Return the GUID of this instance */
+const GUID * qof_instance_get_guid (QofInstance *);
+
+/** Return the pointer to the kvp_data */
+KvpFrame* qof_instance_get_slots (QofInstance *);
+
+/** Return the last time this instance was modified. If QofInstances
+ * are used with the QofObject storage backends, then the instance
+ * update times are reserved for use by the backend, for managing
+ * multi-user updates. Non-backend code should not set the update
+ * times.
+ */
+Timespec qof_instance_get_last_update (QofInstance *inst);
+
+/** Compare two instances, based on thier last update times.
+ * Returns a negative, zero or positive value, respectively,
+ * if 'left' is earlier, same as or later than 'right'.
+ * Accepts NULL pointers, NULL's are by definition earlier
+ * than any value.
+ */
+int qof_instance_version_cmp (QofInstance *left, QofInstance *right);
+
+/** Return value of is_dirty flag */
+gboolean qof_instance_is_dirty (QofInstance *);
+
+/** \brief Set the dirty flag
+
+Sets this instance AND the collection as dirty.
+*/
+void qof_instance_set_dirty(QofInstance* inst);
+
+gboolean qof_instance_check_edit(QofInstance *inst);
+
+gboolean qof_instance_do_free(QofInstance *inst);
+
+void qof_instance_mark_free(QofInstance *inst);
+
+QofInstance* qof_instance_create (QofIdType type, QofBook *book);
+
+/** Pair things up. This routine inserts a kvp value into each instance
+ * containing the guid of the other. In this way, if one has one of the
+ * pair, one can always find the other by looking up it's guid. Typically,
+ * you will want to use qof_instance_lookup_twin() to find the twin.
+ * (The current implementation assumes the two instances belong to different
+ * books, and will not add gemini kvp's unless the books differ. Note that
+ * the gemini kvp includes the book guid as well, so that the right book can
+ * be found.
+ */
+void qof_instance_gemini (QofInstance *to, QofInstance *from);
+
+/** The qof_instance_lookup_twin() routine will find the "twin" of this
+ * instance 'src' in the given other 'book' (if the twin exists).
+ *
+ * When instances are gemini'ed or cloned, both of the pair are marked
+ * with the guid of thier copy, thus allowing the sibling-copy of
+ * an instance to be found. Since the sibling may end up in a
+ * different book, we need a way of finding it, given only that we
+ * know the book, and that we know its twin.
+ *
+ * That's what this routine does. Given some book 'book', and an
+ * instance 'src', it will find the sibling instance of 'src' that is
+ * in 'book', and return it. If not found, it returns NULL. This
+ * routine uses the 'gemini' kvp values to do its work.
+ */
+QofInstance * qof_instance_lookup_twin (QofInstance *src, QofBook *book);
+
+/* @} */
+/* @} */
+#endif /* QOF_INSTANCE_H */
Added: gnucash/trunk/lib/libqof/qof/qofla-dir.h.in
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofla-dir.h.in 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofla-dir.h.in 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,26 @@
+/***************************************************************************
+ * qofla-dir.h.in
+ *
+ * Mon Dec 20 20:27:48 2004
+ * Copyright 2004 Neil Williams
+ * linux at codehelp.co.uk
+ ****************************************************************************/
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define QOF_LIB_DIR "@-libdir-@"
+
Added: gnucash/trunk/lib/libqof/qof/qofmath128.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofmath128.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofmath128.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,408 @@
+/********************************************************************
+ * qofmath128.c -- an 128-bit integer library *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ *******************************************************************/
+
+#define _GNU_SOURCE
+
+#include "config.h"
+#include "qofmath128.h"
+
+#include <glib.h>
+
+/* =============================================================== */
+/*
+ * Quick-n-dirty 128-bit integer math lib. Things seem to mostly
+ * work, and have been tested, but not comprehensively tested.
+ */
+
+#define HIBIT (0x8000000000000000ULL)
+
+/** Multiply a pair of signed 64-bit numbers,
+ * returning a signed 128-bit number.
+ */
+inline qofint128
+mult128 (gint64 a, gint64 b)
+{
+ qofint128 prod;
+ guint64 a0, a1;
+ guint64 b0, b1;
+ guint64 d, d0, d1;
+ guint64 e, e0, e1;
+ guint64 f, f0, f1;
+ guint64 g, g0, g1;
+ guint64 sum, carry, roll, pmax;
+
+ prod.isneg = 0;
+ if (0>a)
+ {
+ prod.isneg = !prod.isneg;
+ a = -a;
+ }
+
+ if (0>b)
+ {
+ prod.isneg = !prod.isneg;
+ b = -b;
+ }
+
+ a1 = a >> 32;
+ a0 = a - (a1<<32);
+
+ b1 = b >> 32;
+ b0 = b - (b1<<32);
+
+ d = a0*b0;
+ d1 = d >> 32;
+ d0 = d - (d1<<32);
+
+ e = a0*b1;
+ e1 = e >> 32;
+ e0 = e - (e1<<32);
+
+ f = a1*b0;
+ f1 = f >> 32;
+ f0 = f - (f1<<32);
+
+ g = a1*b1;
+ g1 = g >> 32;
+ g0 = g - (g1<<32);
+
+ sum = d1+e0+f0;
+ carry = 0;
+ /* Can't say 1<<32 cause cpp will goof it up; 1ULL<<32 might work */
+ roll = 1<<30;
+ roll <<= 2;
+
+ pmax = roll-1;
+ while (pmax < sum)
+ {
+ sum -= roll;
+ carry ++;
+ }
+
+ prod.lo = d0 + (sum<<32);
+ prod.hi = carry + e1 + f1 + g0 + (g1<<32);
+ // prod.isbig = (prod.hi || (sum >> 31));
+ prod.isbig = prod.hi || (prod.lo >> 63);
+
+ return prod;
+}
+
+/** Shift right by one bit (i.e. divide by two) */
+inline qofint128
+shift128 (qofint128 x)
+{
+ guint64 sbit = x.hi & 0x1;
+ x.hi >>= 1;
+ x.lo >>= 1;
+ x.isbig = 0;
+ if (sbit)
+ {
+ x.lo |= HIBIT;
+ x.isbig = 1;
+ return x;
+ }
+ if (x.hi)
+ {
+ x.isbig = 1;
+ }
+ return x;
+}
+
+/** Shift leftt by one bit (i.e. multiply by two) */
+inline qofint128
+shiftleft128 (qofint128 x)
+{
+ guint64 sbit;
+ sbit = x.lo & HIBIT;
+ x.hi <<= 1;
+ x.lo <<= 1;
+ x.isbig = 0;
+ if (sbit)
+ {
+ x.hi |= 1;
+ x.isbig = 1;
+ return x;
+ }
+ if (x.hi)
+ {
+ x.isbig = 1;
+ }
+ return x;
+}
+
+/** increment a 128-bit number by one */
+inline qofint128
+inc128 (qofint128 a)
+{
+ if (0 == a.isneg)
+ {
+ a.lo ++;
+ if (0 == a.lo)
+ {
+ a.hi ++;
+ }
+ }
+ else
+ {
+ if (0 == a.lo)
+ {
+ a.hi --;
+ }
+ a.lo --;
+ }
+
+ a.isbig = (a.hi != 0) || (a.lo >> 63);
+ return a;
+}
+
+/** Divide a signed 128-bit number by a signed 64-bit,
+ * returning a signed 128-bit number.
+ */
+inline qofint128
+div128 (qofint128 n, gint64 d)
+{
+ qofint128 quotient;
+ int i;
+ guint64 remainder = 0;
+
+ quotient = n;
+ if (0 > d)
+ {
+ d = -d;
+ quotient.isneg = !quotient.isneg;
+ }
+
+ /* Use grade-school long division algorithm */
+ for (i=0; i<128; i++)
+ {
+ guint64 sbit = HIBIT & quotient.hi;
+ remainder <<= 1;
+ if (sbit) remainder |= 1;
+ quotient = shiftleft128 (quotient);
+ if (remainder >= d)
+ {
+ remainder -= d;
+ quotient.lo |= 1;
+ }
+ }
+
+ /* compute the carry situation */
+ quotient.isbig = (quotient.hi || (quotient.lo >> 63));
+
+ return quotient;
+}
+
+/** Return the remainder of a signed 128-bit number modulo
+ * a signed 64-bit. That is, return n%d in 128-bit math.
+ * I beleive that ths algo is overflow-free, but should be
+ * audited some more ...
+ */
+inline gint64
+rem128 (qofint128 n, gint64 d)
+{
+ qofint128 quotient = div128 (n,d);
+
+ qofint128 mu = mult128 (quotient.lo, d);
+
+ gint64 nn = 0x7fffffffffffffffULL & n.lo;
+ gint64 rr = 0x7fffffffffffffffULL & mu.lo;
+ return nn - rr;
+}
+
+/** Return true of two numbers are equal */
+inline gboolean
+equal128 (qofint128 a, qofint128 b)
+{
+ if (a.lo != b.lo) return 0;
+ if (a.hi != b.hi) return 0;
+ if (a.isneg != b.isneg) return 0;
+ return 1;
+}
+
+/** Return returns 1 if a>b, -1 if b>a, 0 if a == b */
+inline int
+cmp128 (qofint128 a, qofint128 b)
+{
+ if ((0 == a.isneg) && b.isneg) return 1;
+ if (a.isneg && (0 == b.isneg)) return -1;
+ if (0 == a.isneg)
+ {
+ if (a.hi > b.hi) return 1;
+ if (a.hi < b.hi) return -1;
+ if (a.lo > b.lo) return 1;
+ if (a.lo < b.lo) return -1;
+ return 0;
+ }
+
+ if (a.hi > b.hi) return -1;
+ if (a.hi < b.hi) return 1;
+ if (a.lo > b.lo) return -1;
+ if (a.lo < b.lo) return 1;
+ return 0;
+}
+
+/** Return the greatest common factor of two 64-bit numbers */
+inline guint64
+gcf64(guint64 num, guint64 denom)
+{
+ guint64 t;
+
+ t = num % denom;
+ num = denom;
+ denom = t;
+
+ /* The strategy is to use Euclid's algorithm */
+ while (0 != denom)
+ {
+ t = num % denom;
+ num = denom;
+ denom = t;
+ }
+ /* num now holds the GCD (Greatest Common Divisor) */
+ return num;
+}
+
+/** Return the least common multiple of two 64-bit numbers. */
+inline qofint128
+lcm128 (guint64 a, guint64 b)
+{
+ guint64 gcf = gcf64 (a,b);
+ b /= gcf;
+ return mult128 (a,b);
+}
+
+/** Add a pair of 128-bit numbers, returning a 128-bit number */
+inline qofint128
+add128 (qofint128 a, qofint128 b)
+{
+ qofint128 sum;
+ if (a.isneg == b.isneg)
+ {
+ sum.isneg = a.isneg;
+ sum.hi = a.hi + b.hi;
+ sum.lo = a.lo + b.lo;
+ if ((sum.lo < a.lo) || (sum.lo < b.lo))
+ {
+ sum.hi ++;
+ }
+ sum.isbig = sum.hi || (sum.lo >> 63);
+ return sum;
+ }
+ if ((b.hi > a.hi) ||
+ ((b.hi == a.hi) && (b.lo > a.lo)))
+ {
+ qofint128 tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ sum.isneg = a.isneg;
+ sum.hi = a.hi - b.hi;
+ sum.lo = a.lo - b.lo;
+
+ if (sum.lo > a.lo)
+ {
+ sum.hi --;
+ }
+
+ sum.isbig = sum.hi || (sum.lo >> 63);
+ return sum;
+}
+
+
+#ifdef TEST_128_BIT_MULT
+static void pr (gint64 a, gint64 b)
+{
+ qofint128 prod = mult128 (a,b);
+ printf ("%" G_GINT64_FORMAT " * %" G_GINT64_FORMAT " = %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " (0x%llx %llx) %hd\n",
+ a, b, prod.hi, prod.lo, prod.hi, prod.lo, prod.isbig);
+}
+
+static void prd (gint64 a, gint64 b, gint64 c)
+{
+ qofint128 prod = mult128 (a,b);
+ qofint128 quot = div128 (prod, c);
+ gint64 rem = rem128 (prod, c);
+ printf ("%" G_GINT64_FORMAT " * %" G_GINT64_FORMAT " / %" G_GINT64_FORMAT " = %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " + %" G_GINT64_FORMAT " (0x%llx %llx) %hd\n",
+ a, b, c, quot.hi, quot.lo, rem, quot.hi, quot.lo, quot.isbig);
+}
+
+int main ()
+{
+ pr (2,2);
+
+ gint64 x = 1<<30;
+ x <<= 2;
+
+ pr (x,x);
+ pr (x+1,x);
+ pr (x+1,x+1);
+
+ pr (x,-x);
+ pr (-x,-x);
+ pr (x-1,x);
+ pr (x-1,x-1);
+ pr (x-2,x-2);
+
+ x <<= 1;
+ pr (x,x);
+ pr (x,-x);
+
+ pr (1000000, 10000000000000);
+
+ prd (x,x,2);
+ prd (x,x,3);
+ prd (x,x,4);
+ prd (x,x,5);
+ prd (x,x,6);
+
+ x <<= 29;
+ prd (3,x,3);
+ prd (6,x,3);
+ prd (99,x,3);
+ prd (100,x,5);
+ prd (540,x,5);
+ prd (777,x,7);
+ prd (1111,x,11);
+
+ /* Really test division */
+ qofint128 n;
+ n.hi = 0xdd91;
+ n.lo = 0x6c5abefbb9e13480ULL;
+
+ gint64 d = 0x2ae79964d3ae1d04ULL;
+
+ int i;
+ for (i=0; i<20; i++) {
+
+ qofint128 quot = div128 (n, d);
+ printf ("%d result = %llx %llx\n", i, quot.hi, quot.lo);
+ d >>=1;
+ n = shift128 (n);
+ }
+ return 0;
+}
+
+#endif /* TEST_128_BIT_MULT */
+
+/* ======================== END OF FILE =================== */
Added: gnucash/trunk/lib/libqof/qof/qofmath128.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofmath128.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofmath128.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,85 @@
+/********************************************************************
+ * qofmath128.h -- an 128-bit integer library *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+ *******************************************************************/
+
+#ifndef QOF_MATH_128_H
+#define QOF_MATH_128_H
+
+#include <glib.h>
+
+/** @addtogroup Math128
+ * Quick-n-dirty 128-bit integer math lib. Things seem to mostly
+ * work, and have been tested, but not comprehensively tested.
+ * @{
+ */
+
+typedef struct {
+ guint64 hi;
+ guint64 lo;
+ short isneg; /**< sign-bit -- T if number is negative */
+ short isbig; /**< sizeflag -- T if number won't fit in signed 64-bit */
+} qofint128;
+
+/** Return true of two numbers are equal */
+inline gboolean equal128 (qofint128 a, qofint128 b);
+
+/** Return returns 1 if a>b, -1 if b>a, 0 if a == b */
+inline int cmp128 (qofint128 a, qofint128 b);
+
+/** Shift right by one bit (i.e. divide by two) */
+inline qofint128 shift128 (qofint128 x);
+
+/** Shift left by one bit (i.e. multiply by two) */
+inline qofint128 shiftleft128 (qofint128 x);
+
+/** Increment by one */
+inline qofint128 inc128 (qofint128 a);
+
+/** Add a pair of 128-bit numbers, returning a 128-bit number */
+inline qofint128 add128 (qofint128 a, qofint128 b);
+
+/** Multiply a pair of signed 64-bit numbers,
+ * returning a signed 128-bit number.
+ */
+inline qofint128 mult128 (gint64 a, gint64 b);
+
+/** Divide a signed 128-bit number by a signed 64-bit,
+ * returning a signed 128-bit number.
+ */
+inline qofint128 div128 (qofint128 n, gint64 d);
+
+/** Return the remainder of a signed 128-bit number modulo
+ * a signed 64-bit. That is, return n%d in 128-bit math.
+ * I beleive that ths algo is overflow-free, but should be
+ * audited some more ...
+ */
+inline gint64 rem128 (qofint128 n, gint64 d);
+
+/** Return the greatest common factor of two 64-bit numbers */
+inline guint64 gcf64(guint64 num, guint64 denom);
+
+/** Return the least common multiple of two 64-bit numbers. */
+inline qofint128 lcm128 (guint64 a, guint64 b);
+
+#endif
+
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/qofobject-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofobject-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofobject-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,49 @@
+/********************************************************************\
+ * qofobject-p.h -- the private Object Registration/Lookup Interface *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup Object
+ @{ */
+/** @addtogroup Object_Private
+ Private interfaces, not meant to be used by applications.
+ @{ */
+/** @name Objects_Private
+ @{ */
+/** @file qofobject-p.h
+ * @brief the Core Object Registration/Lookup Private Interface
+ * @author Copyright (c) 2001,2002, Derek Atkins <warlord at MIT.EDU>
+ */
+
+#ifndef QOF_OBJECT_P_H_
+#define QOF_OBJECT_P_H_
+
+#include "qofbook.h"
+#include "qofobject.h"
+
+/** To be called from within the book */
+void qof_object_book_begin (QofBook *book);
+void qof_object_book_end (QofBook *book);
+
+gboolean qof_object_is_dirty (QofBook *book);
+void qof_object_mark_clean (QofBook *book);
+
+#endif /* QOF_OBJECT_P_H_ */
+/** @} */
+/** @} */
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/qofobject.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofobject.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofobject.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,353 @@
+/********************************************************************\
+ * qofobject.c -- the Core Object Registration/Lookup Interface *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/*
+ * qofobject.c -- the Core Object Object Registry
+ * Copyright (C) 2001 Derek Atkins
+ * Author: Derek Atkins <warlord at MIT.EDU>
+ */
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "qofobject.h"
+#include "qofobject-p.h"
+#include "qofbook.h"
+
+static QofLogModule log_module = QOF_MOD_OBJECT;
+
+static gboolean object_is_initialized = FALSE;
+static GList *object_modules = NULL;
+static GList *book_list = NULL;
+static GHashTable *backend_data = NULL;
+
+gpointer
+qof_object_new_instance (QofIdTypeConst type_name, QofBook *book)
+{
+ const QofObject *obj;
+
+ if (!type_name) return NULL;
+
+ obj = qof_object_lookup (type_name);
+ if (!obj) return NULL;
+
+ if (obj->create)
+ return (obj->create (book));
+
+ return NULL;
+}
+
+void qof_object_book_begin (QofBook *book)
+{
+ GList *l;
+
+ if (!book) return;
+ ENTER (" ");
+ for (l = object_modules; l; l = l->next) {
+ QofObject *obj = l->data;
+ if (obj->book_begin)
+ obj->book_begin (book);
+ }
+
+ /* Remember this book for later */
+ book_list = g_list_prepend (book_list, book);
+ LEAVE (" ");
+}
+
+void qof_object_book_end (QofBook *book)
+{
+ GList *l;
+
+ if (!book) return;
+ ENTER (" ");
+ for (l = object_modules; l; l = l->next) {
+ QofObject *obj = l->data;
+ if (obj->book_end)
+ obj->book_end (book);
+ }
+
+ /* Remove it from the list */
+ book_list = g_list_remove (book_list, book);
+ LEAVE (" ");
+}
+
+gboolean
+qof_object_is_dirty (QofBook *book)
+{
+ GList *l;
+
+ if (!book) return FALSE;
+ for (l = object_modules; l; l = l->next)
+ {
+ QofObject *obj = l->data;
+ if (obj->is_dirty)
+ {
+ QofCollection *col;
+ col = qof_book_get_collection (book, obj->e_type);
+ if (obj->is_dirty (col)) return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+void
+qof_object_mark_clean (QofBook *book)
+{
+ GList *l;
+
+ if (!book) return;
+ for (l = object_modules; l; l = l->next)
+ {
+ QofObject *obj = l->data;
+ if (obj->mark_clean)
+ {
+ QofCollection *col;
+ col = qof_book_get_collection (book, obj->e_type);
+ (obj->mark_clean) (col);
+ }
+ }
+}
+
+void qof_object_foreach_type (QofForeachTypeCB cb, gpointer user_data)
+{
+ GList *l;
+
+ if (!cb) return;
+
+ for (l = object_modules; l; l = l->next) {
+ QofObject *obj = l->data;
+ (cb) (obj, user_data);
+ }
+}
+
+void
+qof_object_foreach (QofIdTypeConst type_name, QofBook *book,
+ QofEntityForeachCB cb, gpointer user_data)
+{
+ QofCollection *col;
+ const QofObject *obj;
+
+ if (!book || !type_name) return;
+ ENTER ("type=%s", type_name);
+
+ obj = qof_object_lookup (type_name);
+ if (!obj)
+ {
+ PERR ("No object of type %s", type_name);
+ return;
+ }
+ col = qof_book_get_collection (book, obj->e_type);
+ PINFO ("lookup obj=%p for type=%s", obj, type_name);
+ if (!obj) return;
+
+ PINFO ("type=%s foreach=%p", type_name, obj->foreach);
+ if (obj->foreach)
+ {
+ obj->foreach (col, cb, user_data);
+ }
+ LEAVE ("type=%s", type_name);
+
+ return;
+}
+
+const char *
+qof_object_printable (QofIdTypeConst type_name, gpointer obj)
+{
+ const QofObject *b_obj;
+
+ if (!type_name || !obj) return NULL;
+
+ b_obj = qof_object_lookup (type_name);
+ if (!b_obj) return NULL;
+
+ if (b_obj->printable)
+ return (b_obj->printable (obj));
+
+ return NULL;
+}
+
+const char * qof_object_get_type_label (QofIdTypeConst type_name)
+{
+ const QofObject *obj;
+
+ if (!type_name) return NULL;
+
+ obj = qof_object_lookup (type_name);
+ if (!obj) return NULL;
+
+ return (obj->type_label);
+}
+
+static gboolean clear_table (gpointer key, gpointer value, gpointer user_data)
+{
+ g_hash_table_destroy (value);
+ return TRUE;
+}
+
+/* INITIALIZATION and PRIVATE FUNCTIONS */
+
+void qof_object_initialize (void)
+{
+ if (object_is_initialized) return;
+ backend_data = g_hash_table_new (g_str_hash, g_str_equal);
+ object_is_initialized = TRUE;
+}
+
+void qof_object_shutdown (void)
+{
+ g_return_if_fail (object_is_initialized == TRUE);
+
+ g_hash_table_foreach_remove (backend_data, clear_table, NULL);
+ g_hash_table_destroy (backend_data);
+ backend_data = NULL;
+
+ g_list_free (object_modules);
+ object_modules = NULL;
+ g_list_free (book_list);
+ book_list = NULL;
+ object_is_initialized = FALSE;
+}
+
+/* Register new types of object objects.
+ * Return TRUE if successful,
+ * return FALSE if it fails, invalid arguments, or if the object
+ * already exists
+ */
+gboolean qof_object_register (const QofObject *object)
+{
+ g_return_val_if_fail (object_is_initialized, FALSE);
+
+ if (!object) return FALSE;
+ g_return_val_if_fail (object->interface_version == QOF_OBJECT_VERSION, FALSE);
+
+ if (g_list_index (object_modules, (gpointer)object) == -1)
+ object_modules = g_list_prepend (object_modules, (gpointer)object);
+ else
+ return FALSE;
+
+ /* Now initialize all the known books */
+ if (object->book_begin && book_list) {
+ GList *node;
+ for (node = book_list; node; node = node->next)
+ object->book_begin (node->data);
+ }
+
+ return TRUE;
+}
+
+const QofObject * qof_object_lookup (QofIdTypeConst name)
+{
+ GList *iter;
+ const QofObject *obj;
+
+ g_return_val_if_fail (object_is_initialized, NULL);
+
+ if (!name) return NULL;
+
+ for (iter = object_modules; iter; iter = iter->next) {
+ obj = iter->data;
+ if (!safe_strcmp (obj->e_type, name))
+ return obj;
+ }
+ return NULL;
+}
+
+gboolean qof_object_register_backend (QofIdTypeConst type_name,
+ const char *backend_name,
+ gpointer be_data)
+{
+ GHashTable *ht;
+ g_return_val_if_fail (object_is_initialized, FALSE);
+
+ if (!type_name || *type_name == '\0' ||
+ !backend_name || *backend_name == '\0' ||
+ !be_data)
+ return FALSE;
+
+ ht = g_hash_table_lookup (backend_data, backend_name);
+
+ /* If it doesn't already exist, create a new table for this backend */
+ if (!ht) {
+ ht = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (backend_data, (char *)backend_name, ht);
+ }
+
+ /* Now insert the data */
+ g_hash_table_insert (ht, (char *)type_name, be_data);
+
+ return TRUE;
+}
+
+gpointer qof_object_lookup_backend (QofIdTypeConst type_name,
+ const char *backend_name)
+{
+ GHashTable *ht;
+
+ if (!type_name || *type_name == '\0' ||
+ !backend_name || *backend_name == '\0')
+ return NULL;
+
+ ht = g_hash_table_lookup (backend_data, (char *)backend_name);
+ if (!ht)
+ return NULL;
+
+ return g_hash_table_lookup (ht, (char *)type_name);
+}
+
+struct foreach_data {
+ QofForeachBackendTypeCB cb;
+ gpointer user_data;
+};
+
+static void foreach_backend (gpointer key, gpointer be_item, gpointer arg)
+{
+ char *data_type = key;
+ struct foreach_data *cb_data = arg;
+
+ g_return_if_fail (key && be_item && arg);
+
+ /* Call the callback for this data type */
+ (cb_data->cb) (data_type, be_item, cb_data->user_data);
+}
+
+void qof_object_foreach_backend (const char *backend_name,
+ QofForeachBackendTypeCB cb,
+ gpointer user_data)
+{
+ GHashTable *ht;
+ struct foreach_data cb_data;
+
+ if (!backend_name || *backend_name == '\0' || !cb)
+ return;
+
+ ht = g_hash_table_lookup (backend_data, (char *)backend_name);
+ if (!ht)
+ return;
+
+ cb_data.cb = cb;
+ cb_data.user_data = user_data;
+
+ g_hash_table_foreach (ht, foreach_backend, &cb_data);
+}
+
+/* ========================= END OF FILE =================== */
Added: gnucash/trunk/lib/libqof/qof/qofobject.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofobject.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofobject.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,179 @@
+/********************************************************************\
+ * qofobject.h -- the Core Object Registration/Lookup Interface *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/** @addtogroup Object
+ @{ */
+/** @addtogroup Objects
+ QOF Objects provide the means for associating
+ a storage backend to a set of QOF Entities. While an entity
+ can be though of as an identified instance of some thing, the
+ QOF Object provides for a way to associate instances with
+ a storage backend. Storage might be file or SQL storage.
+
+ QOF Objects are also used by the query system ....
+
+ To work with your own QOF Objects, you can use the QOF
+ Generator to create sample objects and a mini-application
+ with the SQL-type query interface.
+ http://qof-gen.sourceforge.net/
+
+ XXX todo, we should split out the storage aspects of this
+ thing from the 'foreach' that query depends on. These are
+ kinda unrelated concepts.
+
+ @{ */
+/** @file qofobject.h
+ * @brief the Core Object Registration/Lookup Interface
+ * @author Copyright (c) 2001,2002 Derek Atkins <warlord at MIT.EDU>
+ */
+
+#ifndef QOF_OBJECT_H_
+#define QOF_OBJECT_H_
+
+#include "qofbook.h"
+#include "qofid.h"
+#include "qofchoice.h"
+
+/** Defines the version of the core object object registration
+ * interface. Only object modules compiled against this version
+ * of the interface will load properly
+ */
+#define QOF_OBJECT_VERSION 3
+
+#define QOF_MOD_OBJECT "qof-object"
+
+typedef struct _QofObject QofObject;
+typedef void (*QofForeachCB) (gpointer obj, gpointer user_data);
+typedef void (*QofForeachTypeCB) (QofObject *type, gpointer user_data);
+typedef void (*QofForeachBackendTypeCB) (QofIdTypeConst type,
+ gpointer backend_data,
+ gpointer user_data);
+
+/** This is the QofObject Class descriptor
+ */
+struct _QofObject
+{
+ gint interface_version; /* of this object interface */
+ QofIdType e_type; /* the Object's QOF_ID */
+ const char * type_label; /* "Printable" type-label string */
+
+ /** Create a new instance of this object type. This routine might be
+ * NULL if the object type doesn't provide a way of creating new
+ * instances.
+ */
+ gpointer (*create)(QofBook *);
+
+ /** book_begin is called from within the Book routines to create
+ * module-specific hooks in a book whenever a book is created.
+ */
+ void (*book_begin)(QofBook *);
+
+ /** book_end is called when the book is being closed, to clean
+ * up (and free memory).
+ */
+ void (*book_end)(QofBook *);
+
+ /** Determine if there are any dirty items in this book */
+ gboolean (*is_dirty)(QofCollection *);
+
+ /** Mark this object's book clean (for after a load) */
+ void (*mark_clean)(QofCollection *);
+
+ /** Traverse over all of the items in the collection, calling
+ * the callback on each item. The third argument can be any
+ * arbitrary caller-supplied data, and is passed to the callback.
+ * Although (*foreach) may be NULL, allmost all objects should
+ * provide this routine, as without it, little of interest can
+ * be done.
+ */
+ void (*foreach)(QofCollection *, QofEntityForeachCB, gpointer);
+
+ /** Given a particular item of this type, return a printable string.
+ */
+ const char * (*printable)(gpointer instance);
+
+ /** Given a pair of items of this type, this routine returns value
+ * indicating which item is 'newer'. This routine is used by storage
+ * backends to determine if the local or the remote copy of a
+ * particular item is the latest, 'uptodate' version. Tis routine
+ * should return an integer less than, equal to, or greater than zero
+ * if 'instance_left' is found to be, respecitvely, earlier than, equal
+ * to or later than than 'instance_right'.
+ */
+ int (*version_cmp)(gpointer instance_left, gpointer instance_right);
+};
+
+/* -------------------------------------------------------------- */
+
+/** @name Initialize the object registration subsystem */
+/** @{ */
+void qof_object_initialize (void);
+void qof_object_shutdown (void);
+/** @} */
+
+/** Register new types of object objects */
+gboolean qof_object_register (const QofObject *object);
+
+/** Lookup an object definition */
+const QofObject * qof_object_lookup (QofIdTypeConst type_name);
+
+/** Create an instance of the indicated type, returning a pointer to that
+ * instance. This routine just calls the (*new) callback on the object
+ * definition.
+ */
+gpointer qof_object_new_instance (QofIdTypeConst type_name, QofBook *book);
+
+/** Get the printable label for a type. This label is *not*
+ * translated; you must use _() on it if you want a translated version.
+ */
+const char * qof_object_get_type_label (QofIdTypeConst type_name);
+
+/** @return a Human-readable string name for an instance */
+const char * qof_object_printable (QofIdTypeConst type_name, gpointer instance);
+
+/** Invoke the callback 'cb' on every object class definition.
+ * The user_data pointer is passed back to the callback.
+ */
+void qof_object_foreach_type (QofForeachTypeCB cb, gpointer user_data);
+
+/** Invoke the callback 'cb' on every instance ov a particular
+ * object type. It is presumed that the 'book' stores or somehow
+ * identifies a colllection of instances; thus the callback will
+ * be invoked only for those instances stored in the book.
+ */
+void qof_object_foreach (QofIdTypeConst type_name, QofBook *book,
+ QofEntityForeachCB cb, gpointer user_data);
+
+/** Register and lookup backend-specific data for this particular object */
+gboolean qof_object_register_backend (QofIdTypeConst type_name,
+ const char *backend_name,
+ gpointer be_data);
+
+gpointer qof_object_lookup_backend (QofIdTypeConst type_name,
+ const char *backend_name);
+
+void qof_object_foreach_backend (const char *backend_name,
+ QofForeachBackendTypeCB cb,
+ gpointer user_data);
+
+#endif /* QOF_OBJECT_H_ */
+/** @} */
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/qofquery-deserial.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery-deserial.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery-deserial.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,760 @@
+/********************************************************************\
+ * qofquery-deserial.c -- Convert Qof-Query XML to QofQuery *
+ * Copyright (C) 2001,2002,2004 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+// #include "config.h"
+
+#include <stdlib.h>
+#include <glib.h>
+#include <libxml/parser.h>
+
+#include "qofquery-deserial.h"
+#include "qofquery-serialize.h"
+#include "qofquery-p.h"
+#include "qofquerycore-p.h"
+#include "gnc-engine-util.h"
+
+#define CACHE_INSERT(str) \
+ g_cache_insert(gnc_engine_get_string_cache(), (gpointer)(str))
+#define CACHE_REMOVE(str) \
+ g_cache_remove(gnc_engine_get_string_cache(), (gpointer)(str))
+
+/* =========================================================== */
+
+#define GET_TEXT(node) ({ \
+ char * sstr = NULL; \
+ xmlNodePtr text; \
+ text = node->xmlChildrenNode; \
+ if (text && 0 == strcmp ("text", text->name)) { \
+ sstr = text->content; \
+ } \
+ sstr; \
+})
+
+#define GET_STR(SELF,FN,TOK) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ FN (SELF, str); \
+ } \
+ else
+
+#define GET_DBL(SELF,FN,TOK) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ double rate = atof (str); \
+ FN (SELF, rate); \
+ } \
+ else
+
+#define GET_INT32(SELF,FN,TOK) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ gint32 ival = atoi (str); \
+ FN (SELF, ival); \
+ } \
+ else
+
+#define GET_INT64(SELF,FN,TOK) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ gint64 ival = atoll (str); \
+ FN (SELF, ival); \
+ } \
+ else
+
+#define GET_DATE(SELF,FN,TOK) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ Timespec tval = gnc_iso8601_to_timespec_gmt (str); \
+ FN (SELF, tval); \
+ } \
+ else
+
+#define GET_BOOL(SELF,FN,TOK) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ gboolean bval = qof_util_bool_to_int (str); \
+ FN (SELF, bval); \
+ } \
+ else
+
+#define GET_NUMERIC(SELF,FN,TOK) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ gnc_numeric nval; \
+ string_to_gnc_numeric (str, &nval); \
+ FN (SELF, nval); \
+ } \
+ else
+
+#define GET_GUID(SELF,FN,TOK) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ GUID guid; \
+ string_to_guid (str, &guid); \
+ FN (SELF, &guid); \
+ } \
+ else
+
+#define GET_HOW(VAL,TOK,A,B,C,D,E,F) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ int ival = QOF_COMPARE_##A; \
+ if (!strcmp (#A, str)) ival = QOF_COMPARE_##A; \
+ else if (!strcmp (#B, str)) ival = QOF_COMPARE_##B; \
+ else if (!strcmp (#C, str)) ival = QOF_COMPARE_##C; \
+ else if (!strcmp (#D, str)) ival = QOF_COMPARE_##D; \
+ else if (!strcmp (#E, str)) ival = QOF_COMPARE_##E; \
+ else if (!strcmp (#F, str)) ival = QOF_COMPARE_##F; \
+ VAL = ival; \
+ } \
+ else
+
+#define GET_MATCH2(VAL,TOK,PFX,A,B) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ int ival = QOF_##PFX##_##A; \
+ if (!strcmp (#A, str)) ival = QOF_##PFX##_##A; \
+ else if (!strcmp (#B, str)) ival = QOF_##PFX##_##B; \
+ VAL = ival; \
+ } \
+ else
+
+#define GET_MATCH3(VAL,TOK,PFX,A,B,C) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ int ival = QOF_##PFX##_##A; \
+ if (!strcmp (#A, str)) ival = QOF_##PFX##_##A; \
+ else if (!strcmp (#B, str)) ival = QOF_##PFX##_##B; \
+ else if (!strcmp (#C, str)) ival = QOF_##PFX##_##C; \
+ VAL = ival; \
+ } \
+ else
+
+#define GET_MATCH5(VAL,TOK,PFX,A,B,C,D,E) \
+ if (0 == strcmp (TOK, node->name)) \
+ { \
+ const char *str = GET_TEXT (node); \
+ int ival = QOF_##PFX##_##A; \
+ if (!strcmp (#A, str)) ival = QOF_##PFX##_##A; \
+ else if (!strcmp (#B, str)) ival = QOF_##PFX##_##B; \
+ else if (!strcmp (#C, str)) ival = QOF_##PFX##_##C; \
+ else if (!strcmp (#D, str)) ival = QOF_##PFX##_##D; \
+ else if (!strcmp (#E, str)) ival = QOF_##PFX##_##E; \
+ VAL = ival; \
+ } \
+ else
+
+/* =============================================================== */
+/* Autogen the code for the simple, repetitive predicates */
+
+#define SIMPLE_PRED_HANDLER(SUBRNAME,CTYPE,GETTER,XMLTYPE,PRED) \
+static QofQueryPredData * \
+SUBRNAME (xmlNodePtr root) \
+{ \
+ QofQueryPredData *pred; \
+ xmlNodePtr xp; \
+ xmlNodePtr node; \
+ QofQueryCompare how; \
+ CTYPE val; \
+ xp = root->xmlChildrenNode; \
+ \
+ how = QOF_COMPARE_EQUAL; \
+ val = 0; \
+ \
+ for (node=xp; node; node = node->next) \
+ { \
+ if (node->type != XML_ELEMENT_NODE) continue; \
+ \
+ GET_HOW (how, "qofquery:compare", LT, LTE, EQUAL, GT, GTE, NEQ); \
+ GETTER (0, val=, XMLTYPE); \
+ {} \
+ } \
+ \
+ pred = PRED (how, val); \
+ return pred; \
+}
+
+SIMPLE_PRED_HANDLER (qof_query_pred_double_from_xml,
+ double,
+ GET_DBL,
+ "qofquery:double",
+ qof_query_double_predicate);
+
+SIMPLE_PRED_HANDLER (qof_query_pred_int64_from_xml,
+ gint64,
+ GET_INT64,
+ "qofquery:int64",
+ qof_query_int64_predicate);
+
+SIMPLE_PRED_HANDLER (qof_query_pred_int32_from_xml,
+ gint32,
+ GET_INT32,
+ "qofquery:int32",
+ qof_query_int32_predicate);
+
+SIMPLE_PRED_HANDLER (qof_query_pred_boolean_from_xml,
+ gboolean,
+ GET_BOOL,
+ "qofquery:boolean",
+ qof_query_boolean_predicate);
+
+/* =============================================================== */
+
+static void wrap_new_gint64(KvpValue **v, gint64 value) {
+ *v = kvp_value_new_gint64 (value); }
+static void wrap_new_double(KvpValue **v, double value) {
+ *v = kvp_value_new_double (value); }
+static void wrap_new_numeric(KvpValue **v, gnc_numeric value) {
+ *v = kvp_value_new_gnc_numeric (value); }
+static void wrap_new_string(KvpValue **v, const char * value) {
+ *v = kvp_value_new_string (value); }
+static void wrap_new_guid(KvpValue **v, const GUID * value) {
+ *v = kvp_value_new_guid (value); }
+static void wrap_new_timespec(KvpValue **v, Timespec value) {
+ *v = kvp_value_new_timespec (value); }
+
+
+static QofQueryPredData *
+qof_query_pred_kvp_from_xml (xmlNodePtr root)
+{
+ QofQueryCompare how;
+ GSList *path;
+ KvpValue *value;
+ QofQueryPredData *pred;
+ xmlNodePtr xp;
+ xmlNodePtr node;
+
+ how = QOF_COMPARE_EQUAL;
+ xp = root->xmlChildrenNode;
+ path = NULL;
+ value = NULL;
+
+ for (node=xp; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+
+ GET_HOW (how, "qofquery:compare", LT, LTE, EQUAL, GT, GTE, NEQ);
+ if (0 == strcmp ("qofquery:kvp-path", node->name))
+ {
+ const char *str = GET_TEXT (node);
+ path = g_slist_append (path, (gpointer) str);
+ }
+ else
+ GET_INT64(&value, wrap_new_gint64, "qofquery:int64");
+ GET_DBL(&value, wrap_new_double, "qofquery:double");
+ GET_NUMERIC(&value, wrap_new_numeric, "qofquery:numeric");
+ GET_STR(&value, wrap_new_string, "qofquery:string");
+ GET_GUID(&value, wrap_new_guid, "qofquery:guid");
+ GET_DATE(&value, wrap_new_timespec, "qofquery:date");
+ }
+
+ pred = qof_query_kvp_predicate (how, path, value);
+ g_slist_free (path);
+ return pred;
+}
+
+/* =============================================================== */
+
+static QofQueryPredData *
+qof_query_pred_guid_from_xml (xmlNodePtr root)
+{
+ GList *guid_list, *n;
+ const char *str;
+ GUID *guid;
+ gboolean decode;
+ QofQueryPredData *pred;
+ QofGuidMatch sm;
+ xmlNodePtr xp;
+ xmlNodePtr node;
+ guid_list = NULL;
+
+ sm = QOF_GUID_MATCH_ANY;
+ xp = root->xmlChildrenNode;
+ for (node=xp; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+
+ /* char pred doesn't have GET_HOW */
+ GET_MATCH5 (sm, "qofquery:guid-match",
+ GUID_MATCH, ANY, NONE, NULL, ALL, LIST_ANY);
+
+ if (0 == strcmp ("qofquery:guid", node->name))
+ {
+ str = GET_TEXT (node);
+ guid = guid_malloc ();
+ decode = string_to_guid (str, guid);
+ if (decode)
+ {
+ guid_list = g_list_append (guid_list, guid);
+ }
+ else
+ {
+ guid_free (guid);
+ // XXX error! let someone know!
+ }
+ }
+ }
+
+ pred = qof_query_guid_predicate (sm, guid_list);
+
+ /* The predicate made a copy of everything, so free our stuff */
+ for (n=guid_list; n; n=n->next)
+ {
+ guid_free (n->data);
+ }
+ g_list_free (guid_list);
+ return pred;
+}
+
+/* =============================================================== */
+
+static QofQueryPredData *
+qof_query_pred_char_from_xml (xmlNodePtr root)
+{
+ QofQueryPredData *pred;
+ QofCharMatch sm;
+ const char * char_list;
+ xmlNodePtr xp;
+ xmlNodePtr node;
+
+ char_list = NULL;
+ xp = root->xmlChildrenNode;
+ sm = QOF_CHAR_MATCH_ANY;
+
+ for (node=xp; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+
+ /* char pred doesn't have GET_HOW */
+ GET_MATCH2 (sm, "qofquery:char-match",
+ CHAR_MATCH, ANY, NONE);
+ GET_STR (0, char_list=, "qofquery:char-list");
+ {}
+ }
+
+ pred = qof_query_char_predicate (sm, char_list);
+ return pred;
+}
+
+/* =============================================================== */
+
+static QofQueryPredData *
+qof_query_pred_numeric_from_xml (xmlNodePtr root)
+{
+ QofQueryPredData *pred;
+ xmlNodePtr node;
+ QofQueryCompare how;
+ QofNumericMatch sm;
+ gnc_numeric num;
+ xmlNodePtr xp;
+
+ xp = root->xmlChildrenNode;
+ how = QOF_COMPARE_EQUAL;
+ sm = QOF_NUMERIC_MATCH_ANY;
+
+ for (node=xp; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+
+ GET_HOW (how, "qofquery:compare", LT, LTE, EQUAL, GT, GTE, NEQ);
+ GET_MATCH3 (sm, "qofquery:numeric-match",
+ NUMERIC_MATCH, DEBIT, CREDIT, ANY);
+ GET_NUMERIC (0, num=, "qofquery:numeric");
+ {}
+ }
+
+ pred = qof_query_numeric_predicate (how, sm, num);
+ return pred;
+}
+
+/* =============================================================== */
+
+static QofQueryPredData *
+qof_query_pred_date_from_xml (xmlNodePtr root)
+{
+ xmlNodePtr xp;
+ xmlNodePtr node;
+ QofQueryCompare how;
+ QofDateMatch sm;
+ Timespec date;
+ QofQueryPredData *pred;
+
+ xp = root->xmlChildrenNode;
+
+ how = QOF_COMPARE_EQUAL;
+ sm = QOF_DATE_MATCH_DAY;
+ date = (Timespec){0,0};
+
+ for (node=xp; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+
+ GET_HOW (how, "qofquery:compare", LT, LTE, EQUAL, GT, GTE, NEQ);
+ GET_MATCH2 (sm, "qofquery:date-match",
+ DATE_MATCH, NORMAL, DAY);
+ GET_DATE (0, date=, "qofquery:date");
+ {}
+ }
+
+ pred = qof_query_date_predicate (how, sm, date);
+ return pred;
+}
+
+/* =============================================================== */
+
+static QofQueryPredData *
+qof_query_pred_string_from_xml (xmlNodePtr root)
+{
+ QofQueryPredData *pred;
+ xmlNodePtr xp;
+ xmlNodePtr node;
+ QofQueryCompare how;
+ QofStringMatch sm;
+ gboolean is_regex;
+ const char *pstr;
+
+ xp = root->xmlChildrenNode;
+ how = QOF_COMPARE_EQUAL;
+ sm = QOF_STRING_MATCH_CASEINSENSITIVE;
+ is_regex = FALSE;
+ pstr = NULL;
+
+ for (node=xp; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+
+ GET_HOW (how, "qofquery:compare", LT, LTE, EQUAL, GT, GTE, NEQ);
+ GET_BOOL (0, is_regex=, "qofquery:is-regex");
+ GET_STR (0, pstr=, "qofquery:string");
+ GET_MATCH2 (sm, "qofquery:string-match",
+ STRING_MATCH, NORMAL, CASEINSENSITIVE);
+ {}
+ }
+ pred = qof_query_string_predicate (how, pstr, sm , is_regex);
+ return pred;
+}
+
+/* =============================================================== */
+
+static GSList *
+qof_query_param_path_from_xml (xmlNodePtr root)
+{
+ xmlNodePtr pterms;
+ GSList *plist;
+ xmlNodePtr node;
+
+ pterms = root->xmlChildrenNode;
+ plist = NULL;
+ for (node=pterms; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+
+ if (0 == strcmp (node->name, "qofquery:param"))
+ {
+ const char *str = GET_TEXT (node);
+ plist = g_slist_append (plist, CACHE_INSERT(str));
+ }
+ }
+ return plist;
+}
+
+/* =============================================================== */
+
+static void
+qof_query_term_from_xml (QofQuery *q, xmlNodePtr root)
+{
+ xmlNodePtr node;
+ xmlNodePtr term;
+ QofQueryPredData *pred;
+ GSList *path;
+ QofQuery *qt;
+ QofQuery *qinv;
+
+ pred = NULL;
+ path = NULL;
+ term = root->xmlChildrenNode;
+ for (node=term; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+ if (0 == strcmp (node->name, "qofquery:invert"))
+ {
+ qt = qof_query_create();
+ qof_query_term_from_xml (qt, node);
+ qinv = qof_query_invert (qt);
+ qof_query_merge_in_place (q, qinv, QOF_QUERY_AND);
+ qof_query_destroy (qinv);
+ qof_query_destroy (qt);
+ return;
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:param-path"))
+ {
+ path = qof_query_param_path_from_xml (node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:pred-string"))
+ {
+ pred = qof_query_pred_string_from_xml (node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:pred-date"))
+ {
+ pred = qof_query_pred_date_from_xml (node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:pred-numeric"))
+ {
+ pred = qof_query_pred_numeric_from_xml (node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:pred-int32"))
+ {
+ pred = qof_query_pred_int32_from_xml (node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:pred-int64"))
+ {
+ pred = qof_query_pred_int64_from_xml (node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:pred-double"))
+ {
+ pred = qof_query_pred_double_from_xml (node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:pred-boolean"))
+ {
+ pred = qof_query_pred_boolean_from_xml (node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:pred-char"))
+ {
+ pred = qof_query_pred_char_from_xml (node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:pred-guid"))
+ {
+ pred = qof_query_pred_guid_from_xml (node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:pred-kvp"))
+ {
+ pred = qof_query_pred_kvp_from_xml (node);
+ }
+ else
+ {
+ // warning unhandled predicate type
+ }
+ }
+
+ /* At this level, the terms should always be anded */
+ qof_query_add_term (q, path, pred, QOF_QUERY_AND);
+}
+
+/* =============================================================== */
+
+static void
+qof_query_and_terms_from_xml (QofQuery *q, xmlNodePtr root)
+{
+ xmlNodePtr andterms;
+ xmlNodePtr node;
+
+ andterms = root->xmlChildrenNode;
+ for (node=andterms; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+
+ if (0 == strcmp (node->name, "qofquery:term"))
+ {
+ qof_query_term_from_xml (q, node);
+ }
+ }
+}
+
+/* =============================================================== */
+
+static void
+qof_query_or_terms_from_xml (QofQuery *q, xmlNodePtr root)
+{
+ xmlNodePtr andterms;
+ xmlNodePtr node;
+ QofQuery *qand;
+
+ andterms = root->xmlChildrenNode;
+ for (node=andterms; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+
+ if (0 == strcmp (node->name, "qofquery:and-terms"))
+ {
+ qand = qof_query_create ();
+ qof_query_and_terms_from_xml (qand, node);
+ qof_query_merge_in_place (q, qand, QOF_QUERY_OR);
+ qof_query_destroy (qand);
+ }
+ }
+}
+
+/* =============================================================== */
+
+QofQuery *
+qof_query_from_xml (xmlNodePtr root)
+{
+ QofQuery *q;
+ xmlChar *version;
+ xmlNodePtr qpart;
+ xmlNodePtr node;
+
+ if (!root) return NULL;
+
+ version = xmlGetProp(root, "version");
+ if (!root->name || strcmp ("qof:qofquery", root->name))
+ {
+ // XXX something is wrong. warn ...
+ return NULL;
+ }
+
+ q = qof_query_create ();
+
+ qpart = root->xmlChildrenNode;
+ for (node=qpart; node; node = node->next)
+ {
+ if (node->type != XML_ELEMENT_NODE) continue;
+
+ GET_STR (q, qof_query_search_for, "qofquery:search-for");
+ GET_INT32 (q, qof_query_set_max_results, "qofquery:max-results");
+ if (0 == strcmp (node->name, "qofquery:or-terms"))
+ {
+ qof_query_or_terms_from_xml (q, node);
+ }
+ else
+ if (0 == strcmp (node->name, "qofquery:sort-list"))
+ {
+// XXX unfinished I'm bored
+ }
+ else
+ {
+ // XXX unknown node type tell someone about it
+ }
+ }
+
+ return q;
+}
+
+/* =============================================================== */
+
+#ifdef UNIT_TEST
+
+#include <stdio.h>
+#include <qof/qofsql.h>
+
+int main (int argc, char * argv[])
+{
+ QofQuery *q, *qnew;
+ QofSqlQuery *sq;
+ xmlNodePtr topnode;
+
+ guid_init();
+ qof_query_init();
+ qof_object_initialize ();
+
+ static QofParam params[] = {
+ { "adate", QOF_TYPE_DATE, NULL, NULL},
+ { "aint", QOF_TYPE_INT32, NULL, NULL},
+ { "aint64", QOF_TYPE_INT64, NULL, NULL},
+ { "aflt", QOF_TYPE_DOUBLE, NULL, NULL},
+ { "abool", QOF_TYPE_BOOLEAN, NULL, NULL},
+ { "astr", QOF_TYPE_STRING, NULL, NULL},
+ { "adate", QOF_TYPE_DATE, NULL, NULL},
+ { "anum", QOF_TYPE_NUMERIC, NULL, NULL},
+ { "achar", QOF_TYPE_CHAR, NULL, NULL},
+ { "aguid", QOF_TYPE_GUID, NULL, NULL},
+ { "akvp", QOF_TYPE_KVP, NULL, NULL},
+ { NULL },
+ };
+
+ qof_class_register ("GncABC", NULL, params);
+ sq = qof_sql_query_new();
+
+ qof_sql_query_parse (sq,
+ "SELECT * from GncABC WHERE aint = 123 "
+ "and not aint64 = 6123123456789 "
+ "or abool = TRUE "
+ "and not aflt >= \'3.14159265358979\' "
+ "and not astr=\'asdf\' "
+ "and adate<\'01-01-01\' "
+ "or anum<\'12301/100\' "
+ "or achar != asdf "
+ "and aguid != abcdef01234567890fedcba987654321 "
+ "and akvp != \'/some/path:abcdef01234567890fedcba987654321\' "
+ "and not akvp != \'/some/path/glop:1234\' "
+ "and akvp = \'/arf/arf/arf:10.234\' "
+ "and akvp != \'/some/other/path:qwerty1234uiop\' "
+ "and not akvp = \'/some/final/path:123401/100\' "
+ );
+ // qof_sql_query_parse (sq, "SELECT * from GncABC;");
+ q = qof_sql_query_get_query (sq);
+
+ qof_query_print (q);
+
+ xmlNodePtr topnode = qof_query_to_xml (q);
+
+ qnew = qof_query_from_xml (topnode);
+ printf (" ------------------------------------------------------- \n");
+ qof_query_print (qnew);
+
+ /* If the before and after trees are the same, the test pases. */
+ gboolean eq = qof_query_equal (q, qnew);
+ printf ("Are the two equal? answer=%d\n", eq);
+
+#define DOPRINT 1
+#ifdef DOPRINT
+ xmlDocPtr doc = doc = xmlNewDoc("1.0");
+ xmlDocSetRootElement(doc,topnode);
+
+ xmlChar *xbuf;
+ int bufsz;
+ xmlDocDumpFormatMemory (doc, &xbuf, &bufsz, 1);
+
+ printf ("%s\n", xbuf);
+ xmlFree (xbuf);
+ xmlFreeDoc(doc);
+#endif
+
+ return 0;
+}
+
+#endif /* UNIT_TEST */
+
+/* ======================== END OF FILE =================== */
Added: gnucash/trunk/lib/libqof/qof/qofquery-deserial.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery-deserial.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery-deserial.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,47 @@
+/********************************************************************\
+ * qofquery-deserial.h -- Convert Qof-Query XML to QofQuery *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/*
+ qofquery-deserial.h
+ Convert Qof-Query XML to QofQuery
+author Copyright (C) 2004 Linas Vepstas <linas at linas.org>
+*/
+
+#ifndef QOF_QUERY_DESERIAL_H
+#define QOF_QUERY_DESERIAL_H
+
+#include "qofquery.h"
+#include <libxml/tree.h>
+
+/*
+ Qof Queries can be converted to and from XML so that they
+ can be sent from here to there. This file implements the
+ routine needed to convert the XML back into a C struct.
+
+ Unfinished. XXX Why is this easier than reading a text/sql
+ file?
+
+ */
+/* Given an XML tree, reconstruct and return the equivalent query. */
+QofQuery *qof_query_from_xml (xmlNodePtr);
+
+#endif /* QOF_QUERY_DESERIAL_H */
Added: gnucash/trunk/lib/libqof/qof/qofquery-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,65 @@
+/********************************************************************\
+ * qofquery-p.h -- internal/private API for finding objects *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#ifndef QOF_QUERY_P_H
+#define QOF_QUERY_P_H
+
+#include "qofquery.h"
+
+typedef struct _QofQueryTerm QofQueryTerm;
+typedef struct _QofQuerySort QofQuerySort;
+
+/* Functions to get Query information */
+int qof_query_get_max_results (QofQuery *q);
+
+
+/* Functions to get and look at QueryTerms */
+
+/* This returns a List of List of Query Terms. Each list of Query
+ * Terms are ANDed together, and each list of ANDed terms are ORed
+ * together. So, what is returned is the 'or' list of 'and' lists
+ * of query term objects.
+ *
+ * Note that you should NOT modify this list in any way. It belongs
+ * to the query.
+ */
+GList * qof_query_get_terms (QofQuery *q);
+
+GSList * qof_query_term_get_param_path (QofQueryTerm *queryterm);
+QofQueryPredData *qof_query_term_get_pred_data (QofQueryTerm *queryterm);
+gboolean qof_query_term_is_inverted (QofQueryTerm *queryterm);
+
+
+/* Functions to get and look at QuerySorts */
+
+/* This function returns the primary, secondary, and tertiary sorts.
+ * These are part of the query and should NOT be changed!
+ */
+void qof_query_get_sorts (QofQuery *q, QofQuerySort **primary,
+ QofQuerySort **secondary, QofQuerySort **tertiary);
+
+GSList * qof_query_sort_get_param_path (QofQuerySort *querysort);
+gint qof_query_sort_get_sort_options (QofQuerySort *querysort);
+gboolean qof_query_sort_get_increasing (QofQuerySort *querysort);
+
+#endif /* QOF_QUERY_P_H */
Added: gnucash/trunk/lib/libqof/qof/qofquery-serialize.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery-serialize.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery-serialize.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,597 @@
+/********************************************************************\
+ * qofquery-serialize.c -- Convert QofQuery to XML *
+ * Copyright (C) 2001,2002,2004 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#include "config.h"
+
+#include "qofquery-serialize.h"
+#include "qofquery-p.h"
+#include "qofquerycore-p.h"
+#include "kvp_frame.h"
+
+/* ======================================================= */
+
+#define PUT_STR(TOK,VAL) { \
+ xmlNodePtr node; \
+ const char * str = (VAL); \
+ if (str && 0 != str[0]) \
+ { \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, str); \
+ xmlAddChild (topnode, node); \
+ } \
+}
+
+#define PUT_INT32(TOK,VAL) { \
+ xmlNodePtr node; \
+ char buff[80]; \
+ g_snprintf (buff, sizeof(buff), "%d", (VAL)); \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, buff); \
+ xmlAddChild (topnode, node); \
+}
+
+#define PUT_INT64(TOK,VAL) { \
+ xmlNodePtr node; \
+ char buff[80]; \
+ g_snprintf (buff, sizeof(buff), "%" G_GINT64_FORMAT, (VAL)); \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, buff); \
+ xmlAddChild (topnode, node); \
+}
+
+#define PUT_DBL(TOK,VAL) { \
+ xmlNodePtr node; \
+ char buff[80]; \
+ g_snprintf (buff, sizeof(buff), "%.18g", (VAL)); \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, buff); \
+ xmlAddChild (topnode, node); \
+}
+
+#define PUT_GUID(TOK,VAL) { \
+ xmlNodePtr node; \
+ char buff[80]; \
+ guid_to_string_buff ((VAL), buff); \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, buff); \
+ xmlAddChild (topnode, node); \
+}
+
+#define PUT_DATE(TOK,VAL) { \
+ xmlNodePtr node; \
+ char buff[80]; \
+ gnc_timespec_to_iso8601_buff ((VAL), buff); \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, buff); \
+ xmlAddChild (topnode, node); \
+}
+
+#define PUT_NUMERIC(TOK,VAL) { \
+ xmlNodePtr node; \
+ char *str; \
+ str = gnc_numeric_to_string (VAL); \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, str); \
+ g_free (str); \
+ xmlAddChild (topnode, node); \
+}
+
+#define PUT_BOOL(TOK,VAL) { \
+ xmlNodePtr node; \
+ gboolean boll = (VAL); \
+ node = xmlNewNode (NULL, TOK); \
+ if (boll) { \
+ xmlNodeAddContent(node, "T"); \
+ } else { \
+ xmlNodeAddContent(node, "F"); \
+ } \
+ xmlAddChild (topnode, node); \
+}
+
+#define PUT_HOW(TOK,VAL,A,B,C,D,E,F) { \
+ xmlNodePtr node; \
+ const char * str = "EQUAL"; \
+ switch (VAL) \
+ { \
+ case QOF_COMPARE_##A: str = #A; break; \
+ case QOF_COMPARE_##B: str = #B; break; \
+ case QOF_COMPARE_##C: str = #C; break; \
+ case QOF_COMPARE_##D: str = #D; break; \
+ case QOF_COMPARE_##E: str = #E; break; \
+ case QOF_COMPARE_##F: str = #F; break; \
+ } \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, str); \
+ xmlAddChild (topnode, node); \
+}
+
+#define PUT_MATCH2(TOK,VAL,PFX,A,B) { \
+ xmlNodePtr node; \
+ const char * str = #A; \
+ switch (VAL) \
+ { \
+ case QOF_##PFX##_##A: str = #A; break; \
+ case QOF_##PFX##_##B: str = #B; break; \
+ } \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, str); \
+ xmlAddChild (topnode, node); \
+}
+
+#define PUT_MATCH3(TOK,VAL,PFX,A,B,C) { \
+ xmlNodePtr node; \
+ const char * str = #A; \
+ switch (VAL) \
+ { \
+ case QOF_##PFX##_##A: str = #A; break; \
+ case QOF_##PFX##_##B: str = #B; break; \
+ case QOF_##PFX##_##C: str = #C; break; \
+ } \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, str); \
+ xmlAddChild (topnode, node); \
+}
+
+#define PUT_MATCH5(TOK,VAL,PFX,A,B,C,D,E) { \
+ xmlNodePtr node; \
+ const char * str = #A; \
+ switch (VAL) \
+ { \
+ case QOF_##PFX##_##A: str = #A; break; \
+ case QOF_##PFX##_##B: str = #B; break; \
+ case QOF_##PFX##_##C: str = #C; break; \
+ case QOF_##PFX##_##D: str = #D; break; \
+ case QOF_##PFX##_##E: str = #E; break; \
+ } \
+ node = xmlNewNode (NULL, TOK); \
+ xmlNodeAddContent(node, str); \
+ xmlAddChild (topnode, node); \
+}
+
+/* ======================================================= */
+
+static void
+qof_kvp_value_to_xml (KvpValue *kval, xmlNodePtr topnode)
+{
+ KvpValueType kvt = kvp_value_get_type (kval);
+
+ switch (kvt)
+ {
+ case KVP_TYPE_GINT64:
+ PUT_INT64 ("qofquery:int64", kvp_value_get_gint64(kval));
+ break;
+ case KVP_TYPE_DOUBLE:
+ PUT_DBL ("qofquery:double", kvp_value_get_double(kval));
+ break;
+ case KVP_TYPE_NUMERIC:
+ PUT_NUMERIC ("qofquery:numeric", kvp_value_get_numeric(kval));
+ break;
+ case KVP_TYPE_GUID:
+ PUT_GUID ("qofquery:guid", kvp_value_get_guid(kval));
+ break;
+ case KVP_TYPE_STRING:
+ PUT_STR ("qofquery:string", kvp_value_get_string(kval));
+ break;
+ case KVP_TYPE_TIMESPEC:
+ PUT_DATE ("qofquery:date", kvp_value_get_timespec(kval));
+ break;
+ case KVP_TYPE_BINARY:
+ case KVP_TYPE_GLIST:
+ case KVP_TYPE_FRAME:
+ // XXX don't know how to support these.
+ break;
+ }
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_pred_data_to_xml (QofQueryPredData *pd)
+{
+ GList *n;
+ GSList *ns;
+ xmlNodePtr topnode;
+ query_guid_t pdata_g;
+ query_string_t pdata_s;
+ query_numeric_t pdata_n;
+ query_kvp_t pdata_k;
+ query_date_t pdata_d;
+ query_int64_t pdata_i64;
+ query_int32_t pdata_i32;
+ query_double_t pdata_db;
+ query_boolean_t pdata_bool;
+ query_char_t pdata_c;
+
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_GUID))
+ {
+ topnode = xmlNewNode (NULL, "qofquery:pred-guid");
+ /* GUID Predicate doesn't do a PUT_HOW */
+
+ pdata_g = (query_guid_t) pd;
+ PUT_MATCH5("qofquery:guid-match", pdata_g->options,
+ GUID_MATCH, ANY, ALL, NONE, NULL, LIST_ANY);
+
+ for (n = pdata_g->guids; n; n = n->next)
+ {
+ PUT_GUID ("qofquery:guid", n->data);
+ }
+ return topnode;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_STRING))
+ {
+ topnode = xmlNewNode (NULL, "qofquery:pred-string");
+ PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+ pdata_s = (query_string_t) pd;
+ PUT_MATCH2("qofquery:string-match", pdata_s->options,
+ STRING_MATCH, NORMAL, CASEINSENSITIVE);
+ PUT_BOOL ("qofquery:is-regex", pdata_s->is_regex);
+ PUT_STR ("qofquery:string", pdata_s->matchstring);
+ return topnode;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_NUMERIC))
+ {
+ topnode = xmlNewNode (NULL, "qofquery:pred-numeric");
+ PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+ pdata_n = (query_numeric_t) pd;
+ PUT_MATCH3("qofquery:numeric-match", pdata_n->options,
+ NUMERIC_MATCH, DEBIT, CREDIT, ANY);
+
+ PUT_NUMERIC ("qofquery:numeric", pdata_n->amount);
+ return topnode;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_KVP))
+ {
+ topnode = xmlNewNode (NULL, "qofquery:pred-kvp");
+ PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+ pdata_k = (query_kvp_t) pd;
+ for (ns=pdata_k->path; ns; ns=ns->next)
+ {
+ PUT_STR ("qofquery:kvp-path", ns->data);
+ }
+ qof_kvp_value_to_xml (pdata_k->value, topnode);
+ return topnode;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_DATE))
+ {
+ topnode = xmlNewNode (NULL, "qofquery:pred-date");
+ PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+ pdata_d = (query_date_t) pd;
+
+ PUT_MATCH2("qofquery:date-match", pdata_d->options,
+ DATE_MATCH, NORMAL, DAY);
+
+ PUT_DATE ("qofquery:date", pdata_d->date);
+ return topnode;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_INT64))
+ {
+ topnode = xmlNewNode (NULL, "qofquery:pred-int64");
+ PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+ pdata_i64 = (query_int64_t) pd;
+ PUT_INT64 ("qofquery:int64", pdata_i64->val);
+ return topnode;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_INT32))
+ {
+ topnode = xmlNewNode (NULL, "qofquery:pred-int32");
+ PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+ pdata_i32 = (query_int32_t) pd;
+
+ PUT_INT32 ("qofquery:int32", pdata_i32->val);
+ return topnode;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_DOUBLE))
+ {
+ topnode = xmlNewNode (NULL, "qofquery:pred-double");
+ PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+ pdata_db = (query_double_t) pd;
+
+ PUT_DBL ("qofquery:double", pdata_db->val);
+ return topnode;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_BOOLEAN))
+ {
+ topnode = xmlNewNode (NULL, "qofquery:pred-boolean");
+ PUT_HOW ("qofquery:compare", pd->how, LT, LTE, EQUAL, GT, GTE, NEQ);
+
+ pdata_bool = (query_boolean_t) pd;
+
+ PUT_BOOL ("qofquery:boolean", pdata_bool->val);
+ return topnode;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_CHAR))
+ {
+ topnode = xmlNewNode (NULL, "qofquery:pred-char");
+ /* There is no PUT_HOW for char-match */
+ pdata_c = (query_char_t) pd;
+
+ PUT_MATCH2("qofquery:char-match", pdata_c->options,
+ CHAR_MATCH, ANY, NONE);
+
+ PUT_STR ("qofquery:char-list", pdata_c->char_list);
+ return topnode;
+ }
+ return NULL;
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_param_path_to_xml (GSList *param_path)
+{
+ xmlNodePtr topnode;
+ GSList *n;
+ QofIdTypeConst path;
+
+ n = param_path;
+ topnode = xmlNewNode (NULL, "qofquery:param-path");
+ for ( ; n; n=n->next)
+ {
+ path = n->data;
+ if (!path) continue;
+ PUT_STR ("qofquery:param", path);
+ }
+ return topnode;
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_one_term_to_xml (QofQueryTerm *qt)
+{
+ xmlNodePtr node;
+ xmlNodePtr term;
+ xmlNodePtr topnode;
+ gboolean invert;
+ GSList *path;
+ QofQueryPredData *pd;
+
+ invert = qof_query_term_is_inverted (qt);
+ term = xmlNewNode (NULL, "qofquery:term");
+ topnode = term;
+ path = qof_query_term_get_param_path (qt);
+ pd = qof_query_term_get_pred_data (qt);
+ if (invert)
+ {
+ /* inverter becomes new top mode */
+ topnode = xmlNewNode (NULL, "qofquery:invert");
+ xmlAddChild (term, topnode);
+ }
+
+ node = qof_query_param_path_to_xml (path);
+ if (node) xmlAddChild (topnode, node);
+
+ node = qof_query_pred_data_to_xml (pd);
+ if (node) xmlAddChild (topnode, node);
+
+ return term;
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_and_terms_to_xml (GList *and_terms)
+{
+ xmlNodePtr terms;
+ GList *n;
+ QofQueryTerm *qt;
+ xmlNodePtr t;
+
+ terms = xmlNewNode (NULL, "qofquery:and-terms");
+ n = and_terms;
+ for ( ; n; n=n->next)
+ {
+ qt = n->data;
+ if (!qt) continue;
+
+ t = qof_query_one_term_to_xml (n->data);
+ if (t) xmlAddChild (terms, t);
+ }
+ return terms;
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_terms_to_xml (QofQuery *q)
+{
+ xmlNodePtr terms;
+ GList *n;
+ xmlNodePtr andt;
+
+ terms = NULL;
+ n = qof_query_get_terms (q);
+ if (!n) return NULL;
+ terms = xmlNewNode (NULL, "qofquery:or-terms");
+
+ for ( ; n; n=n->next)
+ {
+ andt = qof_query_and_terms_to_xml (n->data);
+ if (andt) xmlAddChild (terms, andt);
+ }
+ return terms;
+}
+
+/* ======================================================= */
+
+static xmlNodePtr
+qof_query_sorts_to_xml (QofQuery *q)
+{
+ QofQuerySort *s[3];
+ xmlNodePtr sortlist;
+ GSList *plist;
+ xmlNodePtr sort;
+ xmlNodePtr topnode;
+ gboolean increasing;
+ gint opt;
+ xmlNodePtr pl;
+ int i;
+
+ qof_query_get_sorts (q, &s[0], &s[1], &s[2]);
+
+ if (NULL == s[0]) return NULL;
+
+ sortlist = xmlNewNode (NULL, "qofquery:sort-list");
+ for (i=0; i<3; i++)
+ {
+ if (NULL == s[i]) continue;
+
+ plist = qof_query_sort_get_param_path (s[i]);
+ if (!plist) continue;
+
+ sort = xmlNewNode (NULL, "qofquery:sort");
+ xmlAddChild (sortlist, sort);
+
+ topnode = sort;
+
+ increasing = qof_query_sort_get_increasing (s[i]);
+ PUT_STR ("qofquery:order", increasing ? "DESCENDING" : "ASCENDING");
+
+ opt = qof_query_sort_get_sort_options (s[i]);
+ PUT_INT32 ("qofquery:options", opt);
+
+ pl = qof_query_param_path_to_xml (plist);
+ if (pl) xmlAddChild (sort, pl);
+ }
+
+ return sortlist;
+}
+
+/* ======================================================= */
+
+static void
+do_qof_query_to_xml (QofQuery *q, xmlNodePtr topnode)
+{
+ QofIdType search_for;
+ xmlNodePtr terms;
+ xmlNodePtr sorts;
+ gint max_results;
+
+ search_for = qof_query_get_search_for (q);
+ PUT_STR ("qofquery:search-for", search_for);
+
+ terms = qof_query_terms_to_xml(q);
+ if (terms) xmlAddChild (topnode, terms);
+
+ sorts = qof_query_sorts_to_xml (q);
+ if (sorts) xmlAddChild (topnode, sorts);
+
+ max_results = qof_query_get_max_results (q);
+ PUT_INT32 ("qofquery:max-results", max_results);
+}
+
+/* ======================================================= */
+
+xmlNodePtr
+qof_query_to_xml (QofQuery *q)
+{
+ xmlNodePtr topnode;
+ xmlNodePtr node;
+ xmlNsPtr ns;
+
+ topnode = xmlNewNode(NULL, "qof:qofquery");
+ xmlSetProp(topnode, "version", "1.0.1");
+
+ // XXX path to DTD is wrong
+ // ns = xmlNewNs (topnode, "file:" "/usr/share/lib" "/qofquery.dtd", "qof");
+
+ do_qof_query_to_xml (q, topnode);
+
+ return topnode;
+}
+
+/* =============================================================== */
+
+#ifdef UNIT_TEST
+
+#include <stdio.h>
+#include <qof/qofsql.h>
+
+int main (int argc, char * argv[])
+{
+ QofQuery *q;
+ QofSqlQuery *sq;
+ xmlDocPtr doc;
+ xmlNodePtr topnode;
+ xmlChar *xbuf;
+ int bufsz;
+ xmlOutputBufferPtr xbuf;
+
+ qof_query_init();
+ qof_object_initialize ();
+
+ static QofParam params[] = {
+ { "adate", QOF_TYPE_DATE, NULL, NULL},
+ { "aint", QOF_TYPE_INT32, NULL, NULL},
+ { "aint64", QOF_TYPE_INT64, NULL, NULL},
+ { "astr", QOF_TYPE_STRING, NULL, NULL},
+ { NULL },
+ };
+
+ qof_class_register ("GncABC", NULL, params);
+ sq = qof_sql_query_new();
+
+ qof_sql_query_parse (sq,
+ "SELECT * from GncABC WHERE aint = 123 "
+ "or not astr=\'asdf\' "
+ "and aint64 = 9876123456789;");
+ // qof_sql_query_parse (sq, "SELECT * from GncABC;");
+ q = qof_sql_query_get_query (sq);
+
+ qof_query_print (q);
+
+ doc = doc = xmlNewDoc("1.0");
+ topnode = qof_query_to_xml (q);
+ xmlDocSetRootElement(doc,topnode);
+
+ xmlDocDumpFormatMemory (doc, &xbuf, &bufsz, 1);
+
+ printf ("%s\n", xbuf);
+ xmlFree (xbuf);
+ xmlFreeDoc(doc);
+
+#if 0
+printf ("duude\n");
+ // xmlOutputBufferPtr xbuf = xmlAllocOutputBuffer (enc);
+ xbuf = xmlOutputBufferCreateFile (stdout, NULL);
+printf ("duude\n");
+
+ xbuf = xmlOutputBufferCreateFd (1, NULL);
+printf ("duude\n");
+ xmlNodeDumpOutput (xbuf, NULL, topnode, 99, 99, "iso-8859-1");
+ // xmlElemDump (stdout, NULL, topnode);
+#endif
+
+ return 0;
+}
+
+#endif /* UNIT_TEST */
+
+/* ======================== END OF FILE =================== */
Added: gnucash/trunk/lib/libqof/qof/qofquery-serialize.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery-serialize.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery-serialize.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,42 @@
+/********************************************************************\
+ * qofquery-serialize.h -- Convert QofQuery to XML *
+ * Copyright (C) 2004 Linas Vepstas <linas at linas.org> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+/* qofquery-serialize.h
+ Convert QofQuery to XML
+ Copyright (C) 2001,2002,2004 Linas Vepstas <linas at linas.org>
+ */
+
+#ifndef QOF_QUERY_SERIALIZE_H
+#define QOF_QUERY_SERIALIZE_H
+
+#include "qofquery.h"
+#include <libxml/tree.h>
+
+/* XML Serialize Queries to/from XML */
+
+/* Take the query passed as input, and serialize it into XML.
+ * The DTD used will be a very qofquery specific DTD
+ * This is NOT the XQuery XML.
+ */
+xmlNodePtr qof_query_to_xml (QofQuery *q);
+
+#endif /* QOF_QUERY_SERIALIZE_H */
Added: gnucash/trunk/lib/libqof/qof/qofquery.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1879 @@
+/********************************************************************\
+ * qof_query.c -- Implement predicate API for searching for objects *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <time.h>
+#include <glib.h>
+#include <regex.h>
+#include <string.h>
+
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+
+#include "qofbackend-p.h"
+#include "qofbook.h"
+#include "qofbook-p.h"
+#include "qofclass.h"
+#include "qofclass-p.h"
+#include "qofobject.h"
+#include "qofquery.h"
+#include "qofquery-p.h"
+#include "qofquerycore.h"
+#include "qofquerycore-p.h"
+
+static QofLogModule log_module = QOF_MOD_QUERY;
+
+struct _QofQueryTerm
+{
+ GSList * param_list;
+ QofQueryPredData *pdata;
+ gboolean invert;
+
+ /* These values are filled in during "compilation" of the query
+ * term, based upon the obj_name, param_name, and searched-for
+ * object type. If conv_fcn is NULL, then we don't know how to
+ * convert types.
+ */
+ GSList * param_fcns;
+ QofQueryPredicateFunc pred_fcn;
+};
+
+struct _QofQuerySort
+{
+ GSList * param_list;
+ gint options;
+ gboolean increasing;
+
+ /* These values are filled in during "compilation" of the query
+ * term, based upon the obj_name, param_name, and searched-for
+ * object type. If conv_fcn is NULL, then we don't know how to
+ * convert types.
+ */
+ gboolean use_default;
+ GSList * param_fcns; /* Chain of paramters to walk */
+ QofSortFunc obj_cmp; /* In case you are comparing objects */
+ QofCompareFunc comp_fcn; /* When you are comparing core types */
+};
+
+/* The QUERY structure */
+struct _QofQuery
+{
+ /* The object type that we're searching for */
+ QofIdType search_for;
+
+ /* terms is a list of the OR-terms in a sum-of-products
+ * logical expression. */
+ GList * terms;
+
+ /* sorting and chopping is independent of the search filter */
+
+ QofQuerySort primary_sort;
+ QofQuerySort secondary_sort;
+ QofQuerySort tertiary_sort;
+ QofSortFunc defaultSort; /* <- Computed from search_for */
+
+ /* The maximum number of results to return */
+ int max_results;
+
+ /* list of books that will be participating in the query */
+ GList * books;
+
+ /* a map of book to backend-compiled queries */
+ GHashTable* be_compiled;
+
+ /* cache the results so we don't have to run the whole search
+ * again until it's really necessary */
+ int changed;
+
+ GList * results;
+};
+
+typedef struct _QofQueryCB
+{
+ QofQuery * query;
+ GList * list;
+ int count;
+} QofQueryCB;
+
+/* initial_term will be owned by the new Query */
+static void query_init (QofQuery *q, QofQueryTerm *initial_term)
+{
+ GList * or = NULL;
+ GList *and = NULL;
+ GHashTable *ht;
+
+ if (initial_term) {
+ or = g_list_alloc ();
+ and = g_list_alloc ();
+ and->data = initial_term;
+ or->data = and;
+ }
+
+ if(q->terms)
+ qof_query_clear (q);
+
+ g_list_free (q->results);
+ g_list_free (q->books);
+
+ g_slist_free (q->primary_sort.param_list);
+ g_slist_free (q->secondary_sort.param_list);
+ g_slist_free (q->tertiary_sort.param_list);
+
+ g_slist_free (q->primary_sort.param_fcns);
+ g_slist_free (q->secondary_sort.param_fcns);
+ g_slist_free (q->tertiary_sort.param_fcns);
+
+ ht = q->be_compiled;
+ memset (q, 0, sizeof (*q));
+ q->be_compiled = ht;
+
+ q->terms = or;
+ q->changed = 1;
+ q->max_results = -1;
+
+ q->primary_sort.param_list = g_slist_prepend (NULL, QUERY_DEFAULT_SORT);
+ q->primary_sort.increasing = TRUE;
+ q->secondary_sort.increasing = TRUE;
+ q->tertiary_sort.increasing = TRUE;
+}
+
+static void swap_terms (QofQuery *q1, QofQuery *q2)
+{
+ GList *g;
+
+ if (!q1 || !q2) return;
+
+ g = q1->terms;
+ q1->terms = q2->terms;
+ q2->terms = g;
+
+ g = q1->books;
+ q1->books = q2->books;
+ q2->books = g;
+
+ q1->changed = 1;
+ q2->changed = 1;
+}
+
+static void free_query_term (QofQueryTerm *qt)
+{
+ if (!qt) return;
+
+ qof_query_core_predicate_free (qt->pdata);
+ g_slist_free (qt->param_list);
+ g_slist_free (qt->param_fcns);
+ g_free (qt);
+}
+
+static QofQueryTerm * copy_query_term (QofQueryTerm *qt)
+{
+ QofQueryTerm *new_qt;
+ if (!qt) return NULL;
+
+ new_qt = g_new0 (QofQueryTerm, 1);
+ memcpy (new_qt, qt, sizeof(QofQueryTerm));
+ new_qt->param_list = g_slist_copy (qt->param_list);
+ new_qt->param_fcns = g_slist_copy (qt->param_fcns);
+ new_qt->pdata = qof_query_core_predicate_copy (qt->pdata);
+ return new_qt;
+}
+
+static GList * copy_and_terms (GList *and_terms)
+{
+ GList *and = NULL;
+ GList *cur_and;
+
+ for(cur_and = and_terms; cur_and; cur_and = cur_and->next)
+ {
+ and = g_list_prepend(and, copy_query_term (cur_and->data));
+ }
+
+ return g_list_reverse(and);
+}
+
+static GList *
+copy_or_terms(GList * or_terms)
+{
+ GList * or = NULL;
+ GList * cur_or;
+
+ for(cur_or = or_terms; cur_or; cur_or = cur_or->next)
+ {
+ or = g_list_prepend(or, copy_and_terms(cur_or->data));
+ }
+
+ return g_list_reverse(or);
+}
+
+static void copy_sort (QofQuerySort *dst, const QofQuerySort *src)
+{
+ memcpy (dst, src, sizeof (*dst));
+ dst->param_list = g_slist_copy (src->param_list);
+ dst->param_fcns = g_slist_copy (src->param_fcns);
+}
+
+static void free_sort (QofQuerySort *s)
+{
+ g_slist_free (s->param_list);
+ s->param_list = NULL;
+
+ g_slist_free (s->param_fcns);
+ s->param_fcns = NULL;
+}
+
+static void free_members (QofQuery *q)
+{
+ GList * cur_or;
+
+ if (q == NULL) return;
+
+ for(cur_or = q->terms; cur_or; cur_or = cur_or->next)
+ {
+ GList * cur_and;
+
+ for(cur_and = cur_or->data; cur_and; cur_and = cur_and->next)
+ {
+ free_query_term(cur_and->data);
+ cur_and->data = NULL;
+ }
+
+ g_list_free(cur_or->data);
+ cur_or->data = NULL;
+ }
+
+ free_sort (&(q->primary_sort));
+ free_sort (&(q->secondary_sort));
+ free_sort (&(q->tertiary_sort));
+
+ g_list_free(q->terms);
+ q->terms = NULL;
+
+ g_list_free(q->books);
+ q->books = NULL;
+
+ g_list_free(q->results);
+ q->results = NULL;
+}
+
+static int cmp_func (QofQuerySort *sort, QofSortFunc default_sort,
+ gconstpointer a, gconstpointer b)
+{
+ QofParam *param = NULL;
+ GSList *node;
+ gpointer conva, convb;
+
+ g_return_val_if_fail (sort, 0);
+
+ /* See if this is a default sort */
+ if (sort->use_default)
+ {
+ if (default_sort) return default_sort ((gpointer)a, (gpointer)b);
+ return 0;
+ }
+
+ /* If no parameters, consider them equal */
+ if (!sort->param_fcns) return 0;
+
+ /* no compare function, consider the two objects equal */
+ if (!sort->comp_fcn && !sort->obj_cmp) return 0;
+
+ /* Do the list of conversions */
+ conva = (gpointer)a;
+ convb = (gpointer)b;
+ for (node = sort->param_fcns; node; node = node->next)
+ {
+ param = node->data;
+
+ /* The last term is really the "parameter getter",
+ * unless we're comparing objects ;) */
+ if (!node->next && !sort->obj_cmp)
+ break;
+
+ /* Do the converstions */
+ conva = (param->param_getfcn) (conva, param);
+ convb = (param->param_getfcn) (convb, param);
+ }
+
+ /* And now return the (appropriate) compare */
+ if (sort->comp_fcn)
+ {
+ int rc = sort->comp_fcn (conva, convb, sort->options, param);
+ return rc;
+ }
+
+ return sort->obj_cmp (conva, convb);
+}
+
+static QofQuery * sortQuery = NULL;
+
+static int sort_func (gconstpointer a, gconstpointer b)
+{
+ int retval;
+
+ g_return_val_if_fail (sortQuery, 0);
+
+ retval = cmp_func (&(sortQuery->primary_sort), sortQuery->defaultSort, a, b);
+ if (retval == 0)
+ {
+ retval = cmp_func (&(sortQuery->secondary_sort), sortQuery->defaultSort,
+ a, b);
+ if (retval == 0)
+ {
+ retval = cmp_func (&(sortQuery->tertiary_sort), sortQuery->defaultSort,
+ a, b);
+ return sortQuery->tertiary_sort.increasing ? retval : -retval;
+ }
+ else
+ {
+ return sortQuery->secondary_sort.increasing ? retval : -retval;
+ }
+ }
+ else
+ {
+ return sortQuery->primary_sort.increasing ? retval : -retval;
+ }
+}
+
+/* ==================================================================== */
+/* This is the main workhorse for performing the query. For each
+ * object, it walks over all of the query terms to see if the
+ * object passes the seive.
+ */
+
+static int
+check_object (QofQuery *q, gpointer object)
+{
+ GList * and_ptr;
+ GList * or_ptr;
+ QofQueryTerm * qt;
+ int and_terms_ok=1;
+
+ ENTER (" object=%p terms=%p name=%s",
+ object, q->terms, qof_object_printable (q->search_for, object));
+
+ for(or_ptr = q->terms; or_ptr; or_ptr = or_ptr->next)
+ {
+ and_terms_ok = 1;
+ for(and_ptr = or_ptr->data; and_ptr; and_ptr = and_ptr->next)
+ {
+ qt = (QofQueryTerm *)(and_ptr->data);
+ if (qt->param_fcns && qt->pred_fcn)
+ {
+ GSList *node;
+ QofParam *param = NULL;
+ gpointer conv_obj = object;
+
+ /* iterate through the conversions */
+ for (node = qt->param_fcns; node; node = node->next)
+ {
+ param = node->data;
+
+ /* The last term is the actual parameter getter */
+ if (!node->next) break;
+
+ conv_obj = param->param_getfcn (conv_obj, param);
+ }
+
+ if (((qt->pred_fcn)(conv_obj, param, qt->pdata)) == qt->invert)
+ {
+ and_terms_ok = 0;
+ break;
+ }
+ }
+ else
+ {
+ /* XXX: Don't know how to do this conversion -- do we care? */
+ }
+ }
+ if (and_terms_ok)
+ {
+ LEAVE (" (terms are OK)");
+ return 1;
+ }
+ }
+
+ /* If there are no terms, assume a "match any" applies.
+ * A query with no terms is still meaningful, since the user
+ * may want to get all objects, but in a particular sorted
+ * order.
+ */
+ LEAVE (" ");
+ if (NULL == q->terms) return 1;
+ return 0;
+}
+
+/* walk the list of parameters, starting with the given object, and
+ * compile the list of parameter get-functions. Save the last valid
+ * parameter definition in "final" and return the list of functions.
+ *
+ * returns NULL if the first parameter is bad (and final is unchanged).
+ */
+static GSList *
+compile_params (GSList *param_list, QofIdType start_obj,
+ QofParam const **final)
+{
+ const QofParam *objDef = NULL;
+ GSList *fcns = NULL;
+
+ ENTER ("param_list=%p id=%s", param_list, start_obj);
+ g_return_val_if_fail (param_list, NULL);
+ g_return_val_if_fail (start_obj, NULL);
+ g_return_val_if_fail (final, NULL);
+
+ for (; param_list; param_list = param_list->next)
+ {
+ QofIdType param_name = param_list->data;
+ objDef = qof_class_get_parameter (start_obj, param_name);
+
+ /* If it doesn't exist, then we've reached the end */
+ if (!objDef) break;
+
+ /* Save off this parameter */
+ fcns = g_slist_prepend (fcns, (gpointer) objDef);
+
+ /* Save this off, just in case */
+ *final = objDef;
+
+ /* And reset for the next parameter */
+ start_obj = (QofIdType) objDef->param_type;
+ }
+
+ LEAVE ("fcns=%p", fcns);
+ return (g_slist_reverse (fcns));
+}
+
+static void
+compile_sort (QofQuerySort *sort, QofIdType obj)
+{
+ const QofParam *resObj = NULL;
+
+ ENTER ("sort=%p id=%s params=%p", sort, obj, sort->param_list);
+ sort->use_default = FALSE;
+
+ g_slist_free (sort->param_fcns);
+ sort->param_fcns = NULL;
+ sort->comp_fcn = NULL;
+ sort->obj_cmp = NULL;
+
+ /* An empty param_list implies "no sort" */
+ if (!sort->param_list) { LEAVE (" "); return; }
+
+ /* Walk the parameter list of obtain the parameter functions */
+ sort->param_fcns = compile_params (sort->param_list, obj, &resObj);
+
+ /* If we have valid parameters, grab the compare function,
+ * If not, check if this is the default sort.
+ */
+ if (sort->param_fcns)
+ {
+ sort->comp_fcn = qof_query_core_get_compare (resObj->param_type);
+
+ /* Hrm, perhaps this is an object compare, not a core compare? */
+ if (sort->comp_fcn == NULL)
+ {
+ sort->obj_cmp = qof_class_get_default_sort (resObj->param_type);
+ }
+ }
+ else if (!safe_strcmp (sort->param_list->data, QUERY_DEFAULT_SORT))
+ {
+ sort->use_default = TRUE;
+ }
+ LEAVE ("sort=%p id=%s", sort, obj);
+}
+
+static void compile_terms (QofQuery *q)
+{
+ GList *or_ptr, *and_ptr, *node;
+
+ ENTER (" query=%p", q);
+ /* Find the specific functions for this Query. Note that the
+ * Query's search_for should now be set to the new type.
+ */
+ for (or_ptr = q->terms; or_ptr; or_ptr = or_ptr->next) {
+ for (and_ptr = or_ptr->data; and_ptr; and_ptr = and_ptr->next) {
+ QofQueryTerm *qt = and_ptr->data;
+ const QofParam *resObj = NULL;
+
+ g_slist_free (qt->param_fcns);
+ qt->param_fcns = NULL;
+
+ /* Walk the parameter list of obtain the parameter functions */
+ qt->param_fcns = compile_params (qt->param_list, q->search_for,
+ &resObj);
+
+ /* If we have valid parameters, grab the predicate function,
+ * If not, see if this is the default sort.
+ */
+
+ if (qt->param_fcns)
+ qt->pred_fcn = qof_query_core_get_predicate (resObj->param_type);
+ else
+ qt->pred_fcn = NULL;
+ }
+ }
+
+ /* Update the sort functions */
+ compile_sort (&(q->primary_sort), q->search_for);
+ compile_sort (&(q->secondary_sort), q->search_for);
+ compile_sort (&(q->tertiary_sort), q->search_for);
+
+ q->defaultSort = qof_class_get_default_sort (q->search_for);
+
+ /* Now compile the backend instances */
+ for (node = q->books; node; node = node->next) {
+ QofBook *book = node->data;
+ QofBackend *be = book->backend;
+
+ if (be && be->compile_query) {
+ gpointer result = (be->compile_query)(be, q);
+ if (result)
+ g_hash_table_insert (q->be_compiled, book, result);
+ }
+ }
+ LEAVE (" query=%p", q);
+}
+
+static void check_item_cb (gpointer object, gpointer user_data)
+{
+ QofQueryCB *ql = user_data;
+
+ if (!object || !ql) return;
+
+ if (check_object (ql->query, object)) {
+ ql->list = g_list_prepend (ql->list, object);
+ ql->count++;
+ }
+ return;
+}
+
+static int param_list_cmp (GSList *l1, GSList *l2)
+{
+ while (1) {
+ int ret;
+
+ /* Check the easy stuff */
+ if (!l1 && !l2) return 0;
+ if (!l1 && l2) return -1;
+ if (l1 && !l2) return 1;
+
+ ret = safe_strcmp (l1->data, l2->data);
+ if (ret)
+ return ret;
+
+ l1 = l1->next;
+ l2 = l2->next;
+ }
+}
+
+static GList * merge_books (GList *l1, GList *l2)
+{
+ GList *res = NULL;
+ GList *node;
+
+ res = g_list_copy (l1);
+
+ for (node = l2; node; node = node->next) {
+ if (g_list_index (res, node->data) == -1)
+ res = g_list_prepend (res, node->data);
+ }
+
+ return res;
+}
+
+static gboolean
+query_free_compiled (gpointer key, gpointer value, gpointer not_used)
+{
+ QofBook* book = key;
+ QofBackend* be = book->backend;
+
+ if (be && be->free_query)
+ (be->free_query)(be, value);
+
+ return TRUE;
+}
+
+/* clear out any cached query_compilations */
+static void query_clear_compiles (QofQuery *q)
+{
+ g_hash_table_foreach_remove (q->be_compiled, query_free_compiled, NULL);
+}
+
+/********************************************************************/
+/* PUBLISHED API FUNCTIONS */
+
+GSList *
+qof_query_build_param_list (char const *param, ...)
+{
+ GSList *param_list = NULL;
+ char const *this_param;
+ va_list ap;
+
+ if (!param)
+ return NULL;
+
+ va_start (ap, param);
+
+ for (this_param = param; this_param; this_param = va_arg (ap, const char *))
+ param_list = g_slist_prepend (param_list, (gpointer)this_param);
+
+ va_end (ap);
+
+ return g_slist_reverse (param_list);
+}
+
+void qof_query_add_term (QofQuery *q, GSList *param_list,
+ QofQueryPredData *pred_data, QofQueryOp op)
+{
+ QofQueryTerm *qt;
+ QofQuery *qr, *qs;
+
+ if (!q || !param_list || !pred_data) return;
+
+ qt = g_new0 (QofQueryTerm, 1);
+ qt->param_list = param_list;
+ qt->pdata = pred_data;
+ qs = qof_query_create ();
+ query_init (qs, qt);
+
+ if (qof_query_has_terms (q))
+ qr = qof_query_merge (q, qs, op);
+ else
+ qr = qof_query_merge (q, qs, QOF_QUERY_OR);
+
+ swap_terms (q, qr);
+ qof_query_destroy (qs);
+ qof_query_destroy (qr);
+}
+
+void qof_query_purge_terms (QofQuery *q, GSList *param_list)
+{
+ QofQueryTerm *qt;
+ GList *or, *and;
+
+ if (!q || !param_list) return;
+
+ for (or = q->terms; or; or = or->next) {
+ for (and = or->data; and; and = and->next) {
+ qt = and->data;
+ if (!param_list_cmp (qt->param_list, param_list)) {
+ if (g_list_length (or->data) == 1) {
+ q->terms = g_list_remove_link (q->terms, or);
+ g_list_free_1 (or);
+ or = q->terms;
+ break;
+ } else {
+ or->data = g_list_remove_link (or->data, and);
+ g_list_free_1 (and);
+ and = or->data;
+ if (!and) break;
+ }
+ q->changed = 1;
+ free_query_term (qt);
+ }
+ }
+ if (!or) break;
+ }
+}
+
+GList * qof_query_run (QofQuery *q)
+{
+ GList *matching_objects = NULL;
+ GList *node;
+ int object_count = 0;
+
+ if (!q) return NULL;
+ g_return_val_if_fail (q->search_for, NULL);
+ g_return_val_if_fail (q->books, NULL);
+ ENTER (" q=%p", q);
+
+ /* XXX: Prioritize the query terms? */
+
+ /* prepare the Query for processing */
+ if (q->changed)
+ {
+ query_clear_compiles (q);
+ compile_terms (q);
+ }
+
+ /* Maybe log this sucker */
+ if (gnc_should_log (log_module, GNC_LOG_DETAIL)) qof_query_print (q);
+
+ /* Now run the query over all the objects and save the results */
+ {
+ QofQueryCB qcb;
+
+ memset (&qcb, 0, sizeof (qcb));
+ qcb.query = q;
+
+ /* For each book */
+ for (node=q->books; node; node=node->next)
+ {
+ QofBook *book = node->data;
+ QofBackend *be = book->backend;
+
+ /* run the query in the backend */
+ if (be)
+ {
+ gpointer compiled_query = g_hash_table_lookup (q->be_compiled, book);
+
+ if (compiled_query && be->run_query)
+ {
+ (be->run_query) (be, compiled_query);
+ }
+ }
+
+ /* And then iterate over all the objects */
+ qof_object_foreach (q->search_for, book, (QofEntityForeachCB) check_item_cb, &qcb);
+ }
+
+ matching_objects = qcb.list;
+ object_count = qcb.count;
+ }
+ PINFO ("matching objects=%p count=%d", matching_objects, object_count);
+
+ /* There is no absolute need to reverse this list, since it's being
+ * sorted below. However, in the common case, we will be searching
+ * in a confined location where the objects are already in order,
+ * thus reversing will put us in the correct order we want and make
+ * the sorting go much faster.
+ */
+ matching_objects = g_list_reverse(matching_objects);
+
+ /* Now sort the matching objects based on the search criteria
+ * sortQuery is an unforgivable use of static global data...
+ * I just can't figure out how else to do this sanely.
+ */
+ if (q->primary_sort.comp_fcn || q->primary_sort.obj_cmp ||
+ (q->primary_sort.use_default && q->defaultSort))
+ {
+ sortQuery = q;
+ matching_objects = g_list_sort(matching_objects, sort_func);
+ sortQuery = NULL;
+ }
+
+ /* Crop the list to limit the number of splits. */
+ if((object_count > q->max_results) && (q->max_results > -1))
+ {
+ if(q->max_results > 0)
+ {
+ GList *mptr;
+
+ /* mptr is set to the first node of what will be the new list */
+ mptr = g_list_nth(matching_objects, object_count - q->max_results);
+ /* mptr should not be NULL, but let's be safe */
+ if (mptr != NULL)
+ {
+ if (mptr->prev != NULL) mptr->prev->next = NULL;
+ mptr->prev = NULL;
+ }
+ g_list_free(matching_objects);
+ matching_objects = mptr;
+ }
+ else
+ {
+ /* q->max_results == 0 */
+ g_list_free(matching_objects);
+ matching_objects = NULL;
+ }
+ object_count = q->max_results;
+ }
+
+ q->changed = 0;
+
+ g_list_free(q->results);
+ q->results = matching_objects;
+
+ LEAVE (" q=%p", q);
+ return matching_objects;
+}
+
+GList *
+qof_query_last_run (QofQuery *query)
+{
+ if (!query)
+ return NULL;
+
+ return query->results;
+}
+
+void qof_query_clear (QofQuery *query)
+{
+ QofQuery *q2 = qof_query_create ();
+ swap_terms (query, q2);
+ qof_query_destroy (q2);
+
+ g_list_free (query->books);
+ query->books = NULL;
+ g_list_free (query->results);
+ query->results = NULL;
+ query->changed = 1;
+}
+
+QofQuery * qof_query_create (void)
+{
+ QofQuery *qp = g_new0 (QofQuery, 1);
+ qp->be_compiled = g_hash_table_new (g_direct_hash, g_direct_equal);
+ query_init (qp, NULL);
+ return qp;
+}
+
+void qof_query_search_for (QofQuery *q, QofIdTypeConst obj_type)
+{
+ if (!q || !obj_type)
+ return;
+
+ if (safe_strcmp (q->search_for, obj_type)) {
+ q->search_for = (QofIdType) obj_type;
+ q->changed = 1;
+ }
+}
+
+QofQuery * qof_query_create_for (QofIdTypeConst obj_type)
+{
+ QofQuery *q;
+ if (!obj_type)
+ return NULL;
+ q = qof_query_create ();
+ qof_query_search_for (q, obj_type);
+ return q;
+}
+
+int qof_query_has_terms (QofQuery *q)
+{
+ if (!q) return 0;
+ return g_list_length (q->terms);
+}
+
+int qof_query_num_terms (QofQuery *q)
+{
+ GList *o;
+ int n = 0;
+ if (!q) return 0;
+ for (o = q->terms; o; o=o->next)
+ n += g_list_length(o->data);
+ return n;
+}
+
+gboolean qof_query_has_term_type (QofQuery *q, GSList *term_param)
+{
+ GList *or;
+ GList *and;
+
+ if (!q || !term_param)
+ return FALSE;
+
+ for(or = q->terms; or; or = or->next) {
+ for(and = or->data; and; and = and->next) {
+ QofQueryTerm *qt = and->data;
+ if (!param_list_cmp (term_param, qt->param_list))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+GSList * qof_query_get_term_type (QofQuery *q, GSList *term_param)
+{
+ GList *or;
+ GList *and;
+ GSList *results = NULL;
+
+ if (!q || !term_param)
+ return FALSE;
+
+ for(or = q->terms; or; or = or->next) {
+ for(and = or->data; and; and = and->next) {
+ QofQueryTerm *qt = and->data;
+ if (!param_list_cmp (term_param, qt->param_list))
+ results = g_slist_append(results, qt->pdata);
+ }
+ }
+
+ return results;
+}
+
+void qof_query_destroy (QofQuery *q)
+{
+ if (!q) return;
+ free_members (q);
+ query_clear_compiles (q);
+ g_hash_table_destroy (q->be_compiled);
+ g_free (q);
+}
+
+QofQuery * qof_query_copy (QofQuery *q)
+{
+ QofQuery *copy;
+ GHashTable *ht;
+
+ if (!q) return NULL;
+ copy = qof_query_create ();
+ ht = copy->be_compiled;
+ free_members (copy);
+
+ memcpy (copy, q, sizeof (QofQuery));
+
+ copy->be_compiled = ht;
+ copy->terms = copy_or_terms (q->terms);
+ copy->books = g_list_copy (q->books);
+ copy->results = g_list_copy (q->results);
+
+ copy_sort (&(copy->primary_sort), &(q->primary_sort));
+ copy_sort (&(copy->secondary_sort), &(q->secondary_sort));
+ copy_sort (&(copy->tertiary_sort), &(q->tertiary_sort));
+
+ copy->changed = 1;
+
+ return copy;
+}
+
+/********************************************************************
+ * qof_query_invert
+ * return a newly-allocated Query object which is the
+ * logical inverse of the original.
+ ********************************************************************/
+
+QofQuery * qof_query_invert (QofQuery *q)
+{
+ QofQuery * retval;
+ QofQuery * right, * left, * iright, * ileft;
+ QofQueryTerm * qt;
+ GList * aterms;
+ GList * cur;
+ GList * new_oterm;
+ int num_or_terms;
+
+ if (!q)
+ return NULL;
+
+ num_or_terms = g_list_length(q->terms);
+
+ switch(num_or_terms)
+ {
+ case 0:
+ retval = qof_query_create();
+ retval->max_results = q->max_results;
+ break;
+
+ /* This is the DeMorgan expansion for a single AND expression. */
+ /* !(abc) = !a + !b + !c */
+ case 1:
+ retval = qof_query_create();
+ retval->max_results = q->max_results;
+ retval->books = g_list_copy (q->books);
+ retval->search_for = q->search_for;
+ retval->changed = 1;
+
+ aterms = g_list_nth_data(q->terms, 0);
+ new_oterm = NULL;
+ for(cur=aterms; cur; cur=cur->next) {
+ qt = copy_query_term(cur->data);
+ qt->invert = !(qt->invert);
+ new_oterm = g_list_append(NULL, qt);
+
+ /* g_list_append() can take forever, so let's do this for speed
+ * in "large" queries.
+ */
+ retval->terms = g_list_reverse(retval->terms);
+ retval->terms = g_list_prepend(retval->terms, new_oterm);
+ retval->terms = g_list_reverse(retval->terms);
+ }
+ break;
+
+ /* If there are multiple OR-terms, we just recurse by
+ * breaking it down to !(a + b + c) =
+ * !a * !(b + c) = !a * !b * !c. */
+ default:
+ right = qof_query_create();
+ right->terms = copy_or_terms(g_list_nth(q->terms, 1));
+
+ left = qof_query_create();
+ left->terms = g_list_append(NULL,
+ copy_and_terms(g_list_nth_data(q->terms, 0)));
+
+ iright = qof_query_invert(right);
+ ileft = qof_query_invert(left);
+
+ retval = qof_query_merge(iright, ileft, QOF_QUERY_AND);
+ retval->books = g_list_copy (q->books);
+ retval->max_results = q->max_results;
+ retval->search_for = q->search_for;
+ retval->changed = 1;
+
+ qof_query_destroy(iright);
+ qof_query_destroy(ileft);
+ qof_query_destroy(right);
+ qof_query_destroy(left);
+ break;
+ }
+
+ return retval;
+}
+
+/********************************************************************
+ * qof_query_merge
+ * combine 2 Query objects by the logical operation in "op".
+ ********************************************************************/
+
+QofQuery *
+qof_query_merge(QofQuery *q1, QofQuery *q2, QofQueryOp op)
+{
+
+ QofQuery * retval = NULL;
+ QofQuery * i1, * i2;
+ QofQuery * t1, * t2;
+ GList * i, * j;
+ QofIdType search_for;
+
+ if(!q1) return q2;
+ if(!q2) return q1;
+
+ if (q1->search_for && q2->search_for)
+ g_return_val_if_fail (safe_strcmp (q1->search_for, q2->search_for) == 0,
+ NULL);
+
+ search_for = (q1->search_for ? q1->search_for : q2->search_for);
+
+ /* Avoid merge surprises if op==QOF_QUERY_AND but q1 is empty.
+ * The goal of this tweak is to all the user to start with
+ * an empty q1 and then append to it recursively
+ * (and q1 (and q2 (and q3 (and q4 ....))))
+ * without bombing out because the append started with an
+ * empty list.
+ * We do essentially the same check in qof_query_add_term()
+ * so that the first term added to an empty query doesn't screw up.
+ */
+ if ((QOF_QUERY_AND == op) && (0 == qof_query_has_terms (q1)))
+ {
+ op = QOF_QUERY_OR;
+ }
+
+ switch(op)
+ {
+ case QOF_QUERY_OR:
+ retval = qof_query_create();
+ retval->terms =
+ g_list_concat(copy_or_terms(q1->terms), copy_or_terms(q2->terms));
+ retval->books = merge_books (q1->books, q2->books);
+ retval->max_results = q1->max_results;
+ retval->changed = 1;
+ break;
+
+ case QOF_QUERY_AND:
+ retval = qof_query_create();
+ retval->books = merge_books (q1->books, q2->books);
+ retval->max_results = q1->max_results;
+ retval->changed = 1;
+
+ /* g_list_append() can take forever, so let's build the list in
+ * reverse and then reverse it at the end, to deal better with
+ * "large" queries.
+ */
+ for(i=q1->terms; i; i=i->next)
+ {
+ for(j=q2->terms; j; j=j->next)
+ {
+ retval->terms =
+ g_list_prepend(retval->terms,
+ g_list_concat
+ (copy_and_terms(i->data),
+ copy_and_terms(j->data)));
+ }
+ }
+ retval->terms = g_list_reverse(retval->terms);
+ break;
+
+ case QOF_QUERY_NAND:
+ /* !(a*b) = (!a + !b) */
+ i1 = qof_query_invert(q1);
+ i2 = qof_query_invert(q2);
+ retval = qof_query_merge(i1, i2, QOF_QUERY_OR);
+ qof_query_destroy(i1);
+ qof_query_destroy(i2);
+ break;
+
+ case QOF_QUERY_NOR:
+ /* !(a+b) = (!a*!b) */
+ i1 = qof_query_invert(q1);
+ i2 = qof_query_invert(q2);
+ retval = qof_query_merge(i1, i2, QOF_QUERY_AND);
+ qof_query_destroy(i1);
+ qof_query_destroy(i2);
+ break;
+
+ case QOF_QUERY_XOR:
+ /* a xor b = (a * !b) + (!a * b) */
+ i1 = qof_query_invert(q1);
+ i2 = qof_query_invert(q2);
+ t1 = qof_query_merge(q1, i2, QOF_QUERY_AND);
+ t2 = qof_query_merge(i1, q2, QOF_QUERY_AND);
+ retval = qof_query_merge(t1, t2, QOF_QUERY_OR);
+
+ qof_query_destroy(i1);
+ qof_query_destroy(i2);
+ qof_query_destroy(t1);
+ qof_query_destroy(t2);
+ break;
+ }
+
+ retval->search_for = search_for;
+ return retval;
+}
+
+void
+qof_query_merge_in_place(QofQuery *q1, QofQuery *q2, QofQueryOp op)
+{
+ QofQuery *tmp_q;
+
+ if (!q1 || !q2)
+ return;
+
+ tmp_q = qof_query_merge (q1, q2, op);
+ swap_terms (q1, tmp_q);
+ qof_query_destroy (tmp_q);
+}
+
+void
+qof_query_set_sort_order (QofQuery *q,
+ GSList *params1, GSList *params2, GSList *params3)
+{
+ if (!q) return;
+ if (q->primary_sort.param_list)
+ g_slist_free (q->primary_sort.param_list);
+ q->primary_sort.param_list = params1;
+ q->primary_sort.options = 0;
+
+ if (q->secondary_sort.param_list)
+ g_slist_free (q->secondary_sort.param_list);
+ q->secondary_sort.param_list = params2;
+ q->secondary_sort.options = 0;
+
+ if (q->tertiary_sort.param_list)
+ g_slist_free (q->tertiary_sort.param_list);
+ q->tertiary_sort.param_list = params3;
+ q->tertiary_sort.options = 0;
+
+ q->changed = 1;
+}
+
+void qof_query_set_sort_options (QofQuery *q, gint prim_op, gint sec_op,
+ gint tert_op)
+{
+ if (!q) return;
+ q->primary_sort.options = prim_op;
+ q->secondary_sort.options = sec_op;
+ q->tertiary_sort.options = tert_op;
+}
+
+void qof_query_set_sort_increasing (QofQuery *q, gboolean prim_inc,
+ gboolean sec_inc, gboolean tert_inc)
+{
+ if (!q) return;
+ q->primary_sort.increasing = prim_inc;
+ q->secondary_sort.increasing = sec_inc;
+ q->tertiary_sort.increasing = tert_inc;
+}
+
+void qof_query_set_max_results (QofQuery *q, int n)
+{
+ if (!q) return;
+ q->max_results = n;
+}
+
+void qof_query_add_guid_list_match (QofQuery *q, GSList *param_list,
+ GList *guid_list, QofGuidMatch options,
+ QofQueryOp op)
+{
+ QofQueryPredData *pdata;
+
+ if (!q || !param_list) return;
+
+ if (!guid_list)
+ g_return_if_fail (options == QOF_GUID_MATCH_NULL);
+
+ pdata = qof_query_guid_predicate (options, guid_list);
+ qof_query_add_term (q, param_list, pdata, op);
+}
+
+void qof_query_add_guid_match (QofQuery *q, GSList *param_list,
+ const GUID *guid, QofQueryOp op)
+{
+ GList *g = NULL;
+
+ if (!q || !param_list) return;
+
+ if (guid)
+ g = g_list_prepend (g, (gpointer)guid);
+
+ qof_query_add_guid_list_match (q, param_list, g,
+ g ? QOF_GUID_MATCH_ANY : QOF_GUID_MATCH_NULL, op);
+
+ g_list_free (g);
+}
+
+void qof_query_set_book (QofQuery *q, QofBook *book)
+{
+ GSList *slist = NULL;
+ if (!q || !book) return;
+
+ /* Make sure this book is only in the list once */
+ if (g_list_index (q->books, book) == -1)
+ q->books = g_list_prepend (q->books, book);
+
+ g_slist_prepend (slist, QOF_PARAM_GUID);
+ g_slist_prepend (slist, QOF_PARAM_BOOK);
+ qof_query_add_guid_match (q, slist,
+ qof_book_get_guid(book), QOF_QUERY_AND);
+}
+
+GList * qof_query_get_books (QofQuery *q)
+{
+ if (!q) return NULL;
+ return q->books;
+}
+
+void qof_query_add_boolean_match (QofQuery *q, GSList *param_list, gboolean value,
+ QofQueryOp op)
+{
+ QofQueryPredData *pdata;
+ if (!q || !param_list) return;
+
+ pdata = qof_query_boolean_predicate (QOF_COMPARE_EQUAL, value);
+ qof_query_add_term (q, param_list, pdata, op);
+}
+
+/**********************************************************************/
+/* PRIVATE PUBLISHED API FUNCTIONS */
+
+void qof_query_init (void)
+{
+ ENTER (" ");
+ qof_query_core_init ();
+ qof_class_init ();
+}
+
+void qof_query_shutdown (void)
+{
+ qof_class_shutdown ();
+ qof_query_core_shutdown ();
+}
+
+int qof_query_get_max_results (QofQuery *q)
+{
+ if (!q) return 0;
+ return q->max_results;
+}
+
+QofIdType qof_query_get_search_for (QofQuery *q)
+{
+ if (!q) return NULL;
+ return q->search_for;
+}
+
+GList * qof_query_get_terms (QofQuery *q)
+{
+ if (!q) return NULL;
+ return q->terms;
+}
+
+GSList * qof_query_term_get_param_path (QofQueryTerm *qt)
+{
+ if (!qt)
+ return NULL;
+ return qt->param_list;
+}
+
+QofQueryPredData *qof_query_term_get_pred_data (QofQueryTerm *qt)
+{
+ if (!qt)
+ return NULL;
+ return qt->pdata;
+}
+
+gboolean qof_query_term_is_inverted (QofQueryTerm *qt)
+{
+ if (!qt)
+ return FALSE;
+ return qt->invert;
+}
+
+void qof_query_get_sorts (QofQuery *q, QofQuerySort **primary,
+ QofQuerySort **secondary, QofQuerySort **tertiary)
+{
+ if (!q)
+ return;
+ if (primary)
+ *primary = &(q->primary_sort);
+ if (secondary)
+ *secondary = &(q->secondary_sort);
+ if (tertiary)
+ *tertiary = &(q->tertiary_sort);
+}
+
+GSList * qof_query_sort_get_param_path (QofQuerySort *qs)
+{
+ if (!qs)
+ return NULL;
+ return qs->param_list;
+}
+
+gint qof_query_sort_get_sort_options (QofQuerySort *qs)
+{
+ if (!qs)
+ return 0;
+ return qs->options;
+}
+
+gboolean qof_query_sort_get_increasing (QofQuerySort *qs)
+{
+ if (!qs)
+ return FALSE;
+ return qs->increasing;
+}
+
+static gboolean
+qof_query_term_equal (QofQueryTerm *qt1, QofQueryTerm *qt2)
+{
+ if (qt1 == qt2) return TRUE;
+ if (!qt1 || !qt2) return FALSE;
+
+ if (qt1->invert != qt2->invert) return FALSE;
+ if (param_list_cmp (qt1->param_list, qt2->param_list)) return FALSE;
+ return qof_query_core_predicate_equal (qt1->pdata, qt2->pdata);
+}
+
+static gboolean
+qof_query_sort_equal (QofQuerySort* qs1, QofQuerySort* qs2)
+{
+ if (qs1 == qs2) return TRUE;
+ if (!qs1 || !qs2) return FALSE;
+
+ /* "Empty" sorts are equivalent, regardless of the flags */
+ if (!qs1->param_list && !qs2->param_list) return TRUE;
+
+ if (qs1->options != qs2->options) return FALSE;
+ if (qs1->increasing != qs2->increasing) return FALSE;
+ return (param_list_cmp (qs1->param_list, qs2->param_list) == 0);
+}
+
+gboolean qof_query_equal (QofQuery *q1, QofQuery *q2)
+{
+ GList *or1, *or2;
+
+ if (q1 == q2) return TRUE;
+ if (!q1 || !q2) return FALSE;
+
+ if (g_list_length (q1->terms) != g_list_length (q2->terms)) return FALSE;
+ if (q1->max_results != q2->max_results) return FALSE;
+
+ for (or1 = q1->terms, or2 = q2->terms; or1;
+ or1 = or1->next, or2 = or2->next)
+ {
+ GList *and1, *and2;
+
+ and1 = or1->data;
+ and2 = or2->data;
+
+ if (g_list_length (and1) != g_list_length (and2)) return FALSE;
+
+ for ( ; and1; and1 = and1->next, and2 = and2->next)
+ if (!qof_query_term_equal (and1->data, and2->data))
+ return FALSE;
+ }
+
+ if (!qof_query_sort_equal (&(q1->primary_sort), &(q2->primary_sort)))
+ return FALSE;
+ if (!qof_query_sort_equal (&(q1->secondary_sort), &(q2->secondary_sort)))
+ return FALSE;
+ if (!qof_query_sort_equal (&(q1->tertiary_sort), &(q2->tertiary_sort)))
+ return FALSE;
+
+ return TRUE;
+}
+
+/***************************************************************************/
+/***************************************************************************/
+/* Query Print functions. qof_query_print is public; everthing else supports
+ * that.
+ * Just call qof_query_print(QofQuery *q), and it will print out the query
+ * contents to stderr.
+*/
+
+/* Static prototypes */
+static GList *qof_query_printSearchFor (QofQuery * query, GList * output);
+static GList *qof_query_printTerms (QofQuery * query, GList * output);
+static GList *qof_query_printSorts (QofQuerySort *s[], const gint numSorts,
+ GList * output);
+static GList *qof_query_printAndTerms (GList * terms, GList * output);
+static gchar *qof_query_printStringForHow (QofQueryCompare how);
+static gchar *qof_query_printStringMatch (QofStringMatch s);
+static gchar *qof_query_printDateMatch (QofDateMatch d);
+static gchar *qof_query_printNumericMatch (QofNumericMatch n);
+static gchar *qof_query_printGuidMatch (QofGuidMatch g);
+static gchar *qof_query_printCharMatch (QofCharMatch c);
+static GString *qof_query_printPredData (QofQueryPredData *pd);
+static GString *qof_query_printParamPath (GSList * parmList);
+static void qof_query_printValueForParam (QofQueryPredData *pd, GString * gs);
+static void qof_query_printOutput (GList * output);
+
+/*
+ This function cycles through a QofQuery object, and
+ prints out the values of the various members of the query
+*/
+void
+qof_query_print (QofQuery * query)
+{
+ GList *output;
+ GString *str;
+ QofQuerySort *s[3];
+ gint maxResults = 0, numSorts = 3;
+
+ ENTER (" ");
+
+ if (!query) {
+ LEAVE("query is (null)");
+ return;
+ }
+
+ output = NULL;
+ str = NULL;
+ maxResults = qof_query_get_max_results (query);
+
+ output = qof_query_printSearchFor (query, output);
+ output = qof_query_printTerms (query, output);
+
+ qof_query_get_sorts (query, &s[0], &s[1], &s[2]);
+
+ if (s[0])
+ {
+ output = qof_query_printSorts (s, numSorts, output);
+ }
+
+ str = g_string_new (" ");
+ g_string_sprintf (str, "Maximum number of results: %d", maxResults);
+ output = g_list_append (output, str);
+
+ qof_query_printOutput (output);
+ LEAVE (" ");
+}
+
+static void
+qof_query_printOutput (GList * output)
+{
+ GList *lst;
+
+ for (lst = output; lst; lst = lst->next)
+ {
+ GString *line = (GString *) lst->data;
+
+ fprintf (stderr, "%s\n", line->str);
+ g_string_free (line, TRUE);
+ line = NULL;
+ }
+}
+
+/*
+ Get the search_for type--This is the type of Object
+ we are searching for (SPLIT, TRANS, etc)
+*/
+static GList *
+qof_query_printSearchFor (QofQuery * query, GList * output)
+{
+ QofIdType searchFor;
+ GString *gs;
+
+ searchFor = qof_query_get_search_for (query);
+ gs = g_string_new ("Query Object Type: ");
+ g_string_append (gs, (NULL == searchFor)? "(null)" : searchFor);
+ output = g_list_append (output, gs);
+
+ return output;
+} /* qof_query_printSearchFor */
+
+/*
+ Run through the terms of the query. This is a outer-inner
+ loop. The elements of the outer loop are ORed, and the
+ elements of the inner loop are ANDed.
+*/
+static GList *
+qof_query_printTerms (QofQuery * query, GList * output)
+{
+
+ GList *terms, *lst;
+
+ terms = qof_query_get_terms (query);
+
+ for (lst = terms; lst; lst = lst->next)
+ {
+ output = g_list_append (output, g_string_new ("OR and AND Terms:"));
+
+ if (lst->data)
+ {
+ output = qof_query_printAndTerms (lst->data, output);
+ }
+ else
+ {
+ output =
+ g_list_append (output, g_string_new (" No data for AND terms"));
+ }
+ }
+
+ return output;
+} /* qof_query_printTerms */
+
+/*
+ Process the sort parameters
+ If this function is called, the assumption is that the first sort
+ not null.
+*/
+static GList *
+qof_query_printSorts (QofQuerySort *s[], const gint numSorts, GList * output)
+{
+ GSList *gsl, *n = NULL;
+ gint curSort;
+ GString *gs = g_string_new (" Sort Parameters:\n");
+
+ for (curSort = 0; curSort < numSorts; curSort++)
+ {
+ gboolean increasing;
+ if (!s[curSort])
+ {
+ break;
+ }
+ increasing = qof_query_sort_get_increasing (s[curSort]);
+
+ gsl = qof_query_sort_get_param_path (s[curSort]);
+ if (gsl) g_string_sprintfa (gs, " Param: ");
+ for (n=gsl; n; n = n->next)
+ {
+ QofIdType param_name = n->data;
+ if (gsl != n)g_string_sprintfa (gs, "\n ");
+ g_string_sprintfa (gs, "%s", param_name);
+ }
+ if (gsl)
+ {
+ g_string_sprintfa (gs, " %s\n", increasing ? "DESC" : "ASC");
+ g_string_sprintfa (gs, " Options: 0x%x\n", s[curSort]->options);
+ }
+ }
+
+ output = g_list_append (output, gs);
+ return output;
+
+} /* qof_query_printSorts */
+
+/*
+ Process the AND terms of the query. This is a GList
+ of WHERE terms that will be ANDed
+*/
+static GList *
+qof_query_printAndTerms (GList * terms, GList * output)
+{
+ const char *prefix = " AND Terms:";
+ QofQueryTerm *qt;
+ QofQueryPredData *pd;
+ GSList *path;
+ GList *lst;
+ gboolean invert;
+
+ output = g_list_append (output, g_string_new (prefix));
+ for (lst = terms; lst; lst = lst->next)
+ {
+ qt = (QofQueryTerm *) lst->data;
+ pd = qof_query_term_get_pred_data (qt);
+ path = qof_query_term_get_param_path (qt);
+ invert = qof_query_term_is_inverted (qt);
+
+ if (invert) output = g_list_append (output,
+ g_string_new(" INVERT SENSE "));
+ output = g_list_append (output, qof_query_printParamPath (path));
+ output = g_list_append (output, qof_query_printPredData (pd));
+ output = g_list_append (output, g_string_new("\n"));
+ }
+
+ return output;
+} /* qof_query_printAndTerms */
+
+/*
+ Process the parameter types of the predicate data
+*/
+static GString *
+qof_query_printParamPath (GSList * parmList)
+{
+ GSList *list = NULL;
+ GString *gs = g_string_new (" Param List:\n");
+ g_string_append (gs, " ");
+ for (list = parmList; list; list = list->next)
+ {
+ g_string_append (gs, (gchar *) list->data);
+ if (list->next)
+ g_string_append (gs, "->");
+ }
+
+ return gs;
+} /* qof_query_printParamPath */
+
+/*
+ Process the PredData of the AND terms
+*/
+static GString *
+qof_query_printPredData (QofQueryPredData *pd)
+{
+ GString *gs;
+
+ gs = g_string_new (" Pred Data:\n ");
+ g_string_append (gs, (gchar *) pd->type_name);
+
+ /* Char Predicate and GUID predicate don't use the 'how' field. */
+ if (safe_strcmp (pd->type_name, QOF_TYPE_CHAR) &&
+ safe_strcmp (pd->type_name, QOF_TYPE_GUID))
+ {
+ g_string_sprintfa (gs, "\n how: %s",
+ qof_query_printStringForHow (pd->how));
+ }
+
+ qof_query_printValueForParam (pd, gs);
+
+ return gs;
+} /* qof_query_printPredData */
+
+/*
+ Get a string representation for the
+ QofCompareFunc enum type.
+*/
+static gchar *
+qof_query_printStringForHow (QofQueryCompare how)
+{
+
+ switch (how)
+ {
+ case QOF_COMPARE_LT:
+ return "QOF_COMPARE_LT";
+ case QOF_COMPARE_LTE:
+ return "QOF_COMPARE_LTE";
+ case QOF_COMPARE_EQUAL:
+ return "QOF_COMPARE_EQUAL";
+ case QOF_COMPARE_GT:
+ return "QOF_COMPARE_GT";
+ case QOF_COMPARE_GTE:
+ return "QOF_COMPARE_GTE";
+ case QOF_COMPARE_NEQ:
+ return "QOF_COMPARE_NEQ";
+ }
+
+ return "INVALID HOW";
+} /* qncQueryPrintStringForHow */
+
+
+static void
+qof_query_printValueForParam (QofQueryPredData *pd, GString * gs)
+{
+
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_GUID))
+ {
+ GList *node;
+ query_guid_t pdata = (query_guid_t) pd;
+ g_string_sprintfa (gs, "\n Match type %s",
+ qof_query_printGuidMatch (pdata->options));
+ for (node = pdata->guids; node; node = node->next)
+ {
+ g_string_sprintfa (gs, ", guids: %s",
+ guid_to_string ((GUID *) node->data));
+ }
+ return;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_STRING))
+ {
+ query_string_t pdata = (query_string_t) pd;
+ g_string_sprintfa (gs, "\n Match type %s",
+ qof_query_printStringMatch (pdata->options));
+ g_string_sprintfa (gs, " %s string: %s",
+ pdata->is_regex ? "Regex" : "Not regex",
+ pdata->matchstring);
+ return;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_NUMERIC))
+ {
+ query_numeric_t pdata = (query_numeric_t) pd;
+ g_string_sprintfa (gs, "\n Match type %s",
+ qof_query_printNumericMatch (pdata->options));
+ g_string_sprintfa (gs, " gnc_numeric: %s",
+ gnc_num_dbg_to_string (pdata->amount));
+ return;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_KVP))
+ {
+ GSList *node;
+ query_kvp_t pdata = (query_kvp_t) pd;
+ g_string_sprintfa (gs, "\n kvp path: ");
+ for (node = pdata->path; node; node = node->next)
+ {
+ g_string_sprintfa (gs, "/%s", (gchar *) node->data);
+ }
+ g_string_sprintfa (gs, "\n");
+ g_string_sprintfa (gs, " kvp value: %s\n",
+ kvp_value_to_string (pdata->value));
+ return;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_INT64))
+ {
+ query_int64_t pdata = (query_int64_t) pd;
+ g_string_sprintfa (gs, " int64: %" G_GINT64_FORMAT, pdata->val);
+ return;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_INT32))
+ {
+ query_int32_t pdata = (query_int32_t) pd;
+ g_string_sprintfa (gs, " int32: %d", pdata->val);
+ return;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_DOUBLE))
+ {
+ query_double_t pdata = (query_double_t) pd;
+ g_string_sprintfa (gs, " double: %.18g", pdata->val);
+ return;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_DATE))
+ {
+ query_date_t pdata = (query_date_t) pd;
+ g_string_sprintfa (gs, "\n Match type %s",
+ qof_query_printDateMatch (pdata->options));
+ g_string_sprintfa (gs, " query_date: %s", gnc_print_date (pdata->date));
+ return;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_CHAR))
+ {
+ query_char_t pdata = (query_char_t) pd;
+ g_string_sprintfa (gs, "\n Match type %s",
+ qof_query_printCharMatch (pdata->options));
+ g_string_sprintfa (gs, " char list: %s", pdata->char_list);
+ return;
+ }
+ if (!safe_strcmp (pd->type_name, QOF_TYPE_BOOLEAN))
+ {
+ query_boolean_t pdata = (query_boolean_t) pd;
+ g_string_sprintfa (gs, " boolean: %s", pdata->val?"TRUE":"FALSE");
+ return;
+ }
+ /** \todo QOF_TYPE_COLLECT */
+ return;
+} /* qof_query_printValueForParam */
+
+/*
+ * Print out a string representation of the
+ * QofStringMatch enum
+ */
+static gchar *
+qof_query_printStringMatch (QofStringMatch s)
+{
+ switch (s)
+ {
+ case QOF_STRING_MATCH_NORMAL:
+ return "QOF_STRING_MATCH_NORMAL";
+ case QOF_STRING_MATCH_CASEINSENSITIVE:
+ return "QOF_STRING_MATCH_CASEINSENSITIVE";
+ }
+ return "UNKNOWN MATCH TYPE";
+} /* qof_query_printStringMatch */
+
+/*
+ * Print out a string representation of the
+ * QofDateMatch enum
+ */
+static gchar *
+qof_query_printDateMatch (QofDateMatch d)
+{
+ switch (d)
+ {
+ case QOF_DATE_MATCH_NORMAL:
+ return "QOF_DATE_MATCH_NORMAL";
+ case QOF_DATE_MATCH_DAY:
+ return "QOF_DATE_MATCH_DAY";
+ }
+ return "UNKNOWN MATCH TYPE";
+} /* qof_query_printDateMatch */
+
+/*
+ * Print out a string representation of the
+ * QofNumericMatch enum
+ */
+static gchar *
+qof_query_printNumericMatch (QofNumericMatch n)
+{
+ switch (n)
+ {
+ case QOF_NUMERIC_MATCH_DEBIT:
+ return "QOF_NUMERIC_MATCH_DEBIT";
+ case QOF_NUMERIC_MATCH_CREDIT:
+ return "QOF_NUMERIC_MATCH_CREDIT";
+ case QOF_NUMERIC_MATCH_ANY:
+ return "QOF_NUMERIC_MATCH_ANY";
+ }
+ return "UNKNOWN MATCH TYPE";
+} /* qof_query_printNumericMatch */
+
+/*
+ * Print out a string representation of the
+ * QofGuidMatch enum
+ */
+static gchar *
+qof_query_printGuidMatch (QofGuidMatch g)
+{
+ switch (g)
+ {
+ case QOF_GUID_MATCH_ANY:
+ return "QOF_GUID_MATCH_ANY";
+ case QOF_GUID_MATCH_ALL:
+ return "QOF_GUID_MATCH_ALL";
+ case QOF_GUID_MATCH_NONE:
+ return "QOF_GUID_MATCH_NONE";
+ case QOF_GUID_MATCH_NULL:
+ return "QOF_GUID_MATCH_NULL";
+ case QOF_GUID_MATCH_LIST_ANY:
+ return "QOF_GUID_MATCH_LIST_ANY";
+ }
+
+ return "UNKNOWN MATCH TYPE";
+} /* qof_query_printGuidMatch */
+
+/*
+ * Print out a string representation of the
+ * QofCharMatch enum
+ */
+static gchar *
+qof_query_printCharMatch (QofCharMatch c)
+{
+ switch (c)
+ {
+ case QOF_CHAR_MATCH_ANY:
+ return "QOF_CHAR_MATCH_ANY";
+ case QOF_CHAR_MATCH_NONE:
+ return "QOF_CHAR_MATCH_NONE";
+ }
+ return "UNKNOWN MATCH TYPE";
+} /* qof_query_printGuidMatch */
+
+/* ======================== END OF FILE =================== */
Added: gnucash/trunk/lib/libqof/qof/qofquery.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquery.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquery.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,373 @@
+/********************************************************************\
+ * qofquery.h -- find objects that match a certain expression. *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+/** @addtogroup Query
+
+BASIC QUERY API:
+With this API you can create arbitrary logical
+queries to find sets of arbitrary object. To make simple
+queries (1 term, such as a search for a parameter with one value),
+create the appropriate
+QueryTerm structure and stick it in a Query object using
+xaccInitQuery. The QueryTerm should be malloced but the Query object
+will handle freeing it. To make compound queries, make multiple
+simple queries and combine them using qof_query_merge() and the logical
+operations of your choice.
+
+SQL QUERY API:
+As an alternative to building queries one predicate at a time,
+you can use the SQL query interface. This interface will accept
+a string containing an SQL query, parse it, convert it into the
+core representation, and execute it.
+
+STRUCTURE OF A QUERY: A Query is a logical function of any number of
+QueryTerms. A QueryTerm consists of a C function pointer (the
+Predicate) and a PredicateData structure containing data passed to the
+predicate funtion. The PredicateData structure is a constant
+associated with the Term and is identical for every object that is
+tested.
+
+The terms of the Query may represent any logical function and are
+stored in canonical form, i.e. the function is expressed as a logical
+sum of logical products. So if you have QueryTerms a, b, c, d, e and
+you have the logical function a(b+c) + !(c(d+e)), it gets stored as
+ab + ac + !c + !c!e +!d!c + !d!e. This may not be optimal for evaluation
+of some functions but it's easy to store, easy to manipulate, and it
+doesn't require a complete algebra system to deal with.
+
+The representation is of a GList of GLists of QueryTerms. The
+"backbone" GList q->terms represents the OR-chain, and every item on
+the backbone is a GList of QueryTerms representing an AND-chain
+corresponding to a single product-term in the canonical
+representation. QueryTerms are duplicated when necessary to fill out
+the canonical form, and the same predicate may be evaluated multiple
+times per split for complex queries. This is a place where we could
+probably optimize.
+
+Evaluation of a Query (see qof_query_run()) is optimized as much as
+possible by short-circuited evaluation. The predicates in each
+AND-chain are sorted by predicate type, with Account queries sorted
+first to allow the evaluator to completely eliminate accounts from the
+search if there's no chance of them having splits that match.
+(XXX above no longer applies)
+
+ @{ */
+/** @file qofquery.h
+ @brief find objects that match a certain expression.
+ @author Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>
+ @author Copyright (C) 2003 Linas Vepstas <linas at linas.org>
+
+*/
+
+
+#ifndef QOF_QUERYNEW_H
+#define QOF_QUERYNEW_H
+
+#include "guid.h"
+#include "qofbook.h"
+#include "qofquerycore.h"
+#include "qofchoice.h"
+
+#define QOF_MOD_QUERY "qof-query"
+
+/** A Query */
+typedef struct _QofQuery QofQuery;
+
+/** Query Term Operators, for combining Query Terms */
+typedef enum {
+ QOF_QUERY_AND=1,
+ QOF_QUERY_OR,
+ QOF_QUERY_NAND,
+ QOF_QUERY_NOR,
+ QOF_QUERY_XOR
+} QofQueryOp;
+
+/* First/only term is same as 'and' */
+#define QOF_QUERY_FIRST_TERM QOF_QUERY_AND
+
+/** Default sort object type */
+#define QUERY_DEFAULT_SORT "QofQueryDefaultSort"
+
+/** "Known" Object Parameters -- all objects must support these */
+#define QOF_PARAM_BOOK "book"
+#define QOF_PARAM_GUID "guid"
+
+/** "Known" Object Parameters -- some objects might support these */
+#define QOF_PARAM_KVP "kvp"
+#define QOF_PARAM_ACTIVE "active"
+#define QOF_PARAM_VERSION "version"
+
+/* --------------------------------------------------------- */
+/** \name Query Subsystem Initialization and Shudown */
+// @{
+/** Subsystem initialization and shutdown. Call init() once
+ * to initalize the query subsytem; call shutdown() to free
+ * up any resources associated with the query subsystem.
+ * Typically called during application startup, shutdown.
+ */
+
+void qof_query_init (void);
+void qof_query_shutdown (void);
+// @}
+
+/* --------------------------------------------------------- */
+/** \name Low-Level API Functions */
+// @{
+
+GSList * qof_query_build_param_list (char const *param, ...);
+
+/** Create a new query.
+ * Before running the query, a 'search-for' type must be set
+ * otherwise nothing will be returned. The results of the query
+ * is a list of the indicated search-for type.
+ *
+ * Allocates and initializes a Query structure which must be
+ * freed by the user with qof_query_destroy(). A newly-allocated
+ * QofQuery object matches nothing (qof_query_run() will return NULL).
+ */
+QofQuery * qof_query_create (void);
+QofQuery * qof_query_create_for (QofIdTypeConst obj_type);
+
+/** Frees the resources associate with a Query object. */
+void qof_query_destroy (QofQuery *q);
+
+/** Set the object type to be searched for. The results of
+ * performuing the query will be a list of this obj_type.
+ */
+void qof_query_search_for (QofQuery *query, QofIdTypeConst obj_type);
+
+/** Set the book to be searched. Books contain/identify collections
+ * of objects; the search will be performed over those books
+ * specified with this function. If no books are set, no results
+ * will be returned (since there is nothing to search over).
+ *
+ * You can search multiple books. To specify multiple books, call
+ * this function multiple times with different arguments.
+ * XXX needed qof_query_clear_books() to reset the list ...
+ */
+void qof_query_set_book (QofQuery *q, QofBook *book);
+
+
+/** This is the general function that adds a new Query Term to a query.
+ * It will find the 'obj_type' object of the search item and compare
+ * the 'param_list' parameter to the predicate data via the comparitor.
+ *
+ * The param_list is a recursive list of parameters. For example, you
+ * can say 'split->memo' by creating a list of one element, "SPLIT_MEMO".
+ * You can say 'split->account->name' by creating a list of two elements,
+ * "SPLIT_ACCOUNT" and "ACCOUNT_NAME". The list becomes the property of
+ * the Query.
+ *
+ * For example:
+ *
+ * acct_name_pred_data = make_string_pred_data(QOF_STRING_MATCH_CASEINSENSITIVE,
+ * account_name);
+ * param_list = make_list (SPLIT_ACCOUNT, ACCOUNT_NAME, NULL);
+ * qof_query_add_term (query, param_list, QOF_COMPARE_EQUAL,
+ * acct_name_pred_data, QOF_QUERY_AND);
+ *
+ * Please note that QofQuery does not, at this time, support joins.
+ * That is, one cannot specify a predicate that is a parameter list.
+ * Put another way, one cannot search for objects where
+ * obja->thingy == objb->stuff
+ */
+
+void qof_query_add_term (QofQuery *query, GSList *param_list,
+ QofQueryPredData *pred_data, QofQueryOp op);
+
+/** DOCUMENT ME !! */
+void qof_query_add_guid_match (QofQuery *q, GSList *param_list,
+ const GUID *guid, QofQueryOp op);
+/** DOCUMENT ME !! */
+void qof_query_add_guid_list_match (QofQuery *q, GSList *param_list,
+ GList *guid_list, QofGuidMatch options,
+ QofQueryOp op);
+
+/** Handy-dandy convenience routines, avoids having to create
+ * a separate predicate for boolean matches. We might want to
+ * create handy-dandy sugar routines for the other predicate types
+ * as well. */
+void qof_query_add_boolean_match (QofQuery *q,
+ GSList *param_list,
+ gboolean value,
+ QofQueryOp op);
+
+/** Perform the query, return the results.
+ * The returned list is a list of the 'search-for' type that was
+ * previously set with the qof_query_search_for() or the
+ * qof_query_create_for() routines. The returned list will have
+ * been sorted using the indicated sort order, and trimed to the
+ * max_results length.
+ *
+ * Do NOT free the resulting list. This list is managed internally
+ * by QofQuery.
+ */
+GList * qof_query_run (QofQuery *query);
+
+/** Return the results of the last query, without causing the query to
+ * be re-run. Do NOT free the resulting list. This list is managed
+ * internally by QofQuery.
+ */
+GList * qof_query_last_run (QofQuery *query);
+
+/** Remove all query terms from query. query matches nothing
+ * after qof_query_clear().
+ */
+void qof_query_clear (QofQuery *query);
+
+/** Remove query terms of a particular type from q. The "type" of a term
+ * is determined by the type of data that gets passed to the predicate
+ * function.
+ * XXX ??? Huh? remove anything of that predicate type, or just
+ * the particular predicate ?
+ */
+void qof_query_purge_terms (QofQuery *q, GSList *param_list);
+
+/** Return boolean FALSE if there are no terms in the query
+ * Can be used as a predicate to see if the query has been
+ * initialized (return value > 0) or is "blank" (return value == 0).
+ */
+int qof_query_has_terms (QofQuery *q);
+
+/** Return the number of terms in the canonical form of the query.
+ */
+int qof_query_num_terms (QofQuery *q);
+
+/** DOCUMENT ME !! */
+gboolean qof_query_has_term_type (QofQuery *q, GSList *term_param);
+GSList * qof_query_get_term_type (QofQuery *q, GSList *term_param);
+
+/** Make a copy of the indicated query */
+QofQuery * qof_query_copy (QofQuery *q);
+
+/** Make a copy of the indicated query, inverting the sense
+ * of the search. In other words, if the original query search
+ * for all objects with a certain condition, the inverted query
+ * will search for all object with NOT that condition. The union
+ * of the results returned by the original and inverted queries
+ * equals the set of all searched objects. These to sets are
+ * disjoint (share no members in common).
+ *
+ * This will return a newly allocated QofQuery object, or NULL
+ * on error. Free it with qof_query_destroy() when no longer needed.
+ */
+QofQuery * qof_query_invert(QofQuery *q);
+
+/** Combine two queries together using the Boolean set (logical)
+ * operator 'op'. For example, if the operator 'op' is set to
+ * QUERY_AND, then the set of results returned by the query will
+ * will be the Boolean set intersection of the results returned
+ * by q1 and q2. Similarly, QUERY_OR maps to set union, etc.
+ *
+ * Both queries must have compatible
+ * search-types. If both queries are set, they must search for the
+ * same object type. If only one is set, the resulting query will
+ * search for the set type. If neither query has the search-type set,
+ * the result will be unset as well.
+ *
+ * This will return a newly allocated QofQuery object, or NULL
+ * on error. Free it with qof_query_destroy() when no longer needed.
+ */
+QofQuery * qof_query_merge(QofQuery *q1, QofQuery *q2, QofQueryOp op);
+
+/** Like qof_query_merge, but this will merge a copy of q2 into q1.
+ * q2 remains unchanged.
+ */
+void qof_query_merge_in_place(QofQuery *q1, QofQuery *q2, QofQueryOp op);
+
+/**
+ * When a query is run, the results are sorted before being returned.
+ * This routine can be used to set the paramters on which the sort will
+ * be performed. Two objects in the result list will be compared using
+ * the 'primary_sort_params', and sorted based on that order. If the
+ * comparison shows that they are equal, then the
+ * 'secondary_sort_params' will be used. If still equal, then the
+ * tertiary params will be compared. Any or all of these parameter
+ * lists may be NULL. Any of these parameter lists may be set to
+ * QUERY_DEFAULT_SORT.
+ *
+ * Note that if there are more results than the 'max-results' value,
+ * then only the *last* max-results will be returned. For example,
+ * if the sort is set to be increasing date order, then only the
+ * objects with the most recent dates will be returned.
+ *
+ * The input lists become the property of QofQuery and are managed
+ * by it. They will be freed when the query is destroyed (or when
+ * new lists are set).
+ */
+void qof_query_set_sort_order (QofQuery *q,
+ GSList *primary_sort_params,
+ GSList *secondary_sort_params,
+ GSList *tertiary_sort_params);
+
+void qof_query_set_sort_options (QofQuery *q, gint prim_op, gint sec_op,
+ gint tert_op);
+
+/**
+ * When a query is run, the results are sorted before being returned.
+ * This routine can be used to control the direction of the ordering.
+ * A value of TRUE indicates the sort will be in increasing order,
+ * a value of FALSE will order results in decreasing order.
+ *
+ * Note that if there are more results than the 'max-results' value,
+ * then only the *last* max-results will be returned. For example,
+ * if the sort is set to be increasing date order, then only the
+ * objects with the most recent dates will be returned.
+ */
+void qof_query_set_sort_increasing (QofQuery *q, gboolean prim_inc,
+ gboolean sec_inc, gboolean tert_inc);
+
+
+/**
+ * Set the maximum number of results that should be returned.
+ * If 'max-results' is set to -1, then all of the results are
+ * returned. If there are more results than 'max-results',
+ * then the result list is trimmed. Note that there is an
+ * important interplay between 'max-results' and the sort order:
+ * only the last bit of results are returned. For example,
+ * if the sort order is set to be increasing date order, then
+ * only the objects with the most recent dates will be returned.
+ */
+void qof_query_set_max_results (QofQuery *q, int n);
+
+/** Compare two queries for equality.
+ * Query terms are compared each to each.
+ * This is a simplistic
+ * implementation -- logical equivalences between different
+ * and/or trees are ignored.
+ */
+gboolean qof_query_equal (QofQuery *q1, QofQuery *q2);
+
+/** Print the Query in human-readable format.
+ * Useful for debugging and development.
+ */
+void qof_query_print (QofQuery *query);
+
+/** Return the type of data we're querying for */
+QofIdType qof_query_get_search_for (QofQuery *q);
+
+/** Return the list of books we're using */
+GList * qof_query_get_books (QofQuery *q);
+
+// @}
+/* @} */
+#endif /* QOF_QUERYNEW_H */
Added: gnucash/trunk/lib/libqof/qof/qofquerycore-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquerycore-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquerycore-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,146 @@
+/********************************************************************\
+ * qofquerycore-p.h -- Private API for providing core Query data types *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#ifndef QOF_QUERYCOREP_H
+#define QOF_QUERYCOREP_H
+
+#include <sys/types.h>
+#include <time.h>
+#include <glib.h>
+#include <regex.h>
+#include <string.h>
+
+#include "qofquerycore.h"
+
+/* Initalize the Query Core registry and install the default type handlers */
+void qof_query_core_init(void);
+void qof_query_core_shutdown (void);
+
+/*
+ * An arbitrary Query Predicate. Given the object and the
+ * particular parameter get-function (obtained from the registry by
+ * the Query internals), compare the object's parameter to the
+ * predicate data.
+ */
+typedef int (*QofQueryPredicateFunc) (gpointer object,
+ QofParam *getter,
+ QofQueryPredData *pdata);
+
+/* A callback for how to compare two (same-type) objects based on a
+ * common getter (parameter member), using the provided comparison
+ * options (which are the type-specific options).
+ */
+typedef int (*QofCompareFunc) (gpointer a, gpointer b,
+ gint compare_options,
+ QofParam *getter);
+
+/* Lookup functions */
+QofQueryPredicateFunc qof_query_core_get_predicate (char const *type);
+QofCompareFunc qof_query_core_get_compare (char const *type);
+
+/* Compare two predicates */
+gboolean qof_query_core_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2);
+
+/* Predicate Data Structures:
+ *
+ * These are defined such that you can cast between these types and
+ * a QofQueryPredData.
+ *
+ * Note that these are provided for READ ONLY PURPOSES. You should NEVER
+ * write into these structures, change them, or use them to create a
+ * Query.
+ */
+
+typedef struct {
+ QofQueryPredData pd;
+ QofStringMatch options;
+ gboolean is_regex;
+ char * matchstring;
+ regex_t compiled;
+} query_string_def, *query_string_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ QofDateMatch options;
+ Timespec date;
+} query_date_def, *query_date_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ QofNumericMatch options;
+ gnc_numeric amount;
+} query_numeric_def, *query_numeric_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ QofGuidMatch options;
+ GList * guids;
+} query_guid_def, *query_guid_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ gint32 val;
+} query_int32_def, *query_int32_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ gint64 val;
+} query_int64_def, *query_int64_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ double val;
+} query_double_def, *query_double_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ gboolean val;
+} query_boolean_def, *query_boolean_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ QofCharMatch options;
+ char * char_list;
+} query_char_def, *query_char_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ GSList * path;
+ KvpValue * value;
+} query_kvp_def, *query_kvp_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ QofGuidMatch options;
+ QofCollection *coll;
+ GList *guids;
+} query_coll_def, *query_coll_t;
+
+typedef struct {
+ QofQueryPredData pd;
+ QofGuidMatch options;
+ const GUID *guid;
+ GList * guids;
+} query_choice_def, *query_choice_t;
+
+#endif /* QOF_QUERYCOREP_H */
Added: gnucash/trunk/lib/libqof/qof/qofquerycore.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquerycore.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquerycore.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1834 @@
+/********************************************************************\
+ * QueryCore.c -- API for providing core Query data types *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "qofquery.h"
+#include "qofquerycore.h"
+#include "qofquerycore-p.h"
+
+static QofLogModule log_module = QOF_MOD_QUERY;
+
+/* A function to destroy a query predicate's pdata */
+typedef void (*QueryPredDataFree) (QofQueryPredData *pdata);
+
+/* A function to copy a query's predicate data */
+typedef QofQueryPredData *(*QueryPredicateCopyFunc) (QofQueryPredData *pdata);
+
+/* A function to take the object, apply the getter->param_getfcn,
+ * and return a printable string. Note that this QofParam->getfnc
+ * function should be returning a type equal to this core object type.
+ *
+ * Note that this string MUST be freed by the caller.
+ */
+typedef char * (*QueryToString) (gpointer object, QofParam *getter);
+
+/* A function to test for equality of predicate data */
+typedef gboolean (*QueryPredicateEqual) (QofQueryPredData *p1,
+ QofQueryPredData *p2);
+
+static QueryPredicateCopyFunc qof_query_copy_predicate (QofType type);
+static QueryPredDataFree qof_query_predicate_free (QofType type);
+
+/* Core Type Predicate helpers */
+typedef const char * (*query_string_getter) (gpointer, QofParam *);
+static const char * query_string_type = QOF_TYPE_STRING;
+
+typedef Timespec (*query_date_getter) (gpointer, QofParam *);
+static const char * query_date_type = QOF_TYPE_DATE;
+
+typedef gnc_numeric (*query_numeric_getter) (gpointer, QofParam *);
+static const char * query_numeric_type = QOF_TYPE_NUMERIC;
+
+typedef GList * (*query_glist_getter) (gpointer, QofParam *);
+typedef const GUID * (*query_guid_getter) (gpointer, QofParam *);
+static const char * query_guid_type = QOF_TYPE_GUID;
+
+typedef gint32 (*query_int32_getter) (gpointer, QofParam *);
+static const char * query_int32_type = QOF_TYPE_INT32;
+
+typedef gint64 (*query_int64_getter) (gpointer, QofParam *);
+static const char * query_int64_type = QOF_TYPE_INT64;
+
+typedef double (*query_double_getter) (gpointer, QofParam *);
+static const char * query_double_type = QOF_TYPE_DOUBLE;
+
+typedef gboolean (*query_boolean_getter) (gpointer, QofParam *);
+static const char * query_boolean_type = QOF_TYPE_BOOLEAN;
+
+typedef char (*query_char_getter) (gpointer, QofParam *);
+static const char * query_char_type = QOF_TYPE_CHAR;
+
+typedef KvpFrame * (*query_kvp_getter) (gpointer, QofParam *);
+static const char * query_kvp_type = QOF_TYPE_KVP;
+
+typedef QofCollection * (*query_collect_getter) (gpointer, QofParam*);
+static const char * query_collect_type = QOF_TYPE_COLLECT;
+
+typedef const GUID * (*query_choice_getter) (gpointer, QofParam *);
+static const char * query_choice_type = QOF_TYPE_CHOICE;
+
+/* Tables for predicate storage and lookup */
+static gboolean initialized = FALSE;
+static GHashTable *predTable = NULL;
+static GHashTable *cmpTable = NULL;
+static GHashTable *copyTable = NULL;
+static GHashTable *freeTable = NULL;
+static GHashTable *toStringTable = NULL;
+static GHashTable *predEqualTable = NULL;
+
+#define COMPARE_ERROR -3
+#define PREDICATE_ERROR -2
+
+#define VERIFY_PDATA(str) { \
+ g_return_if_fail (pd != NULL); \
+ g_return_if_fail (pd->type_name == str || \
+ !safe_strcmp (str, pd->type_name)); \
+}
+#define VERIFY_PDATA_R(str) { \
+ g_return_val_if_fail (pd != NULL, NULL); \
+ g_return_val_if_fail (pd->type_name == str || \
+ !safe_strcmp (str, pd->type_name), \
+ NULL); \
+}
+#define VERIFY_PREDICATE(str) { \
+ g_return_val_if_fail (getter != NULL, PREDICATE_ERROR); \
+ g_return_val_if_fail (getter->param_getfcn != NULL, PREDICATE_ERROR); \
+ g_return_val_if_fail (pd != NULL, PREDICATE_ERROR); \
+ g_return_val_if_fail (pd->type_name == str || \
+ !safe_strcmp (str, pd->type_name), \
+ PREDICATE_ERROR); \
+}
+
+/********************************************************************/
+/* TYPE-HANDLING FUNCTIONS */
+
+/* QOF_TYPE_STRING */
+
+static int
+string_match_predicate (gpointer object,
+ QofParam *getter,
+ QofQueryPredData *pd)
+{
+ query_string_t pdata = (query_string_t) pd;
+ const char *s;
+ int ret = 0;
+
+ VERIFY_PREDICATE (query_string_type);
+
+ s = ((query_string_getter)getter->param_getfcn) (object, getter);
+
+ if (!s) s = "";
+
+ if (pdata->is_regex) {
+ regmatch_t match;
+ if (!regexec (&pdata->compiled, s, 1, &match, 0))
+ ret = 1;
+
+ } else if (pdata->options == QOF_STRING_MATCH_CASEINSENSITIVE) {
+ if (strcasestr (s, pdata->matchstring))
+ ret = 1;
+
+ } else {
+ if (strstr (s, pdata->matchstring))
+ ret = 1;
+ }
+
+ switch (pd->how) {
+ case QOF_COMPARE_EQUAL:
+ return ret;
+ case QOF_COMPARE_NEQ:
+ return !ret;
+ default:
+ PWARN ("bad match type: %d", pd->how);
+ return 0;
+ }
+}
+
+static int
+string_compare_func (gpointer a, gpointer b, gint options,
+ QofParam *getter)
+{
+ const char *s1, *s2;
+ g_return_val_if_fail (a && b && getter &&getter->param_getfcn, COMPARE_ERROR);
+
+ s1 = ((query_string_getter)getter->param_getfcn) (a, getter);
+ s2 = ((query_string_getter)getter->param_getfcn) (b, getter);
+
+ if (options == QOF_STRING_MATCH_CASEINSENSITIVE)
+ return safe_strcasecmp (s1, s2);
+
+ return safe_strcmp (s1, s2);
+}
+
+static void
+string_free_pdata (QofQueryPredData *pd)
+{
+ query_string_t pdata = (query_string_t) pd;
+
+ VERIFY_PDATA (query_string_type);
+
+ if (pdata->is_regex)
+ regfree (&pdata->compiled);
+ else
+ g_free (pdata->matchstring);
+
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+string_copy_predicate (QofQueryPredData *pd)
+{
+ query_string_t pdata = (query_string_t) pd;
+
+ VERIFY_PDATA_R (query_string_type);
+
+ return qof_query_string_predicate (pd->how, pdata->matchstring,
+ pdata->options,
+ pdata->is_regex);
+}
+
+static gboolean
+string_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_string_t pd1 = (query_string_t) p1;
+ query_string_t pd2 = (query_string_t) p2;
+
+ if (pd1->options != pd2->options) return FALSE;
+ if (pd1->is_regex != pd2->is_regex) return FALSE;
+ return (safe_strcmp (pd1->matchstring, pd2->matchstring) == 0);
+}
+
+QofQueryPredData *
+qof_query_string_predicate (QofQueryCompare how,
+ const char *str, QofStringMatch options,
+ gboolean is_regex)
+{
+ query_string_t pdata;
+
+ g_return_val_if_fail (str, NULL);
+ g_return_val_if_fail (*str != '\0', NULL);
+ g_return_val_if_fail (how == QOF_COMPARE_EQUAL || how == QOF_COMPARE_NEQ, NULL);
+
+ pdata = g_new0 (query_string_def, 1);
+ pdata->pd.type_name = query_string_type;
+ pdata->pd.how = how;
+ pdata->options = options;
+ pdata->matchstring = g_strdup (str);
+
+ if (is_regex) {
+ int flags = REG_EXTENDED;
+ if (options == QOF_STRING_MATCH_CASEINSENSITIVE)
+ flags |= REG_ICASE;
+
+ regcomp(&pdata->compiled, str, flags);
+ pdata->is_regex = TRUE;
+ }
+
+ return ((QofQueryPredData*)pdata);
+}
+
+static char *
+string_to_string (gpointer object, QofParam *getter)
+{
+ const char *res;
+ res = ((query_string_getter)getter->param_getfcn)(object, getter);
+ if (res)
+ return g_strdup (res);
+ return NULL;
+}
+
+/* QOF_TYPE_DATE =================================================== */
+
+static int
+date_compare (Timespec ta, Timespec tb, QofDateMatch options)
+{
+
+ if (options == QOF_DATE_MATCH_DAY) {
+ ta = timespecCanonicalDayTime (ta);
+ tb = timespecCanonicalDayTime (tb);
+ }
+
+ if (ta.tv_sec < tb.tv_sec)
+ return -1;
+ if (ta.tv_sec > tb.tv_sec)
+ return 1;
+
+ if (ta.tv_nsec < tb.tv_nsec)
+ return -1;
+ if (ta.tv_nsec > tb.tv_nsec)
+ return 1;
+
+ return 0;
+}
+
+static int
+date_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData *pd)
+{
+ query_date_t pdata = (query_date_t)pd;
+ Timespec objtime;
+ int compare;
+
+ VERIFY_PREDICATE (query_date_type);
+
+ objtime = ((query_date_getter)getter->param_getfcn) (object, getter);
+ compare = date_compare (objtime, pdata->date, pdata->options);
+
+ switch (pd->how) {
+ case QOF_COMPARE_LT:
+ return (compare < 0);
+ case QOF_COMPARE_LTE:
+ return (compare <= 0);
+ case QOF_COMPARE_EQUAL:
+ return (compare == 0);
+ case QOF_COMPARE_GT:
+ return (compare > 0);
+ case QOF_COMPARE_GTE:
+ return (compare >= 0);
+ case QOF_COMPARE_NEQ:
+ return (compare != 0);
+ default:
+ PWARN ("bad match type: %d", pd->how);
+ return 0;
+ }
+}
+
+static int
+date_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
+{
+ Timespec ta, tb;
+
+ g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+ ta = ((query_date_getter)getter->param_getfcn) (a, getter);
+ tb = ((query_date_getter)getter->param_getfcn) (b, getter);
+
+ return date_compare (ta, tb, options);
+}
+
+static void
+date_free_pdata (QofQueryPredData *pd)
+{
+ query_date_t pdata = (query_date_t)pd;
+
+ VERIFY_PDATA (query_date_type);
+
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+date_copy_predicate (QofQueryPredData *pd)
+{
+ query_date_t pdata = (query_date_t)pd;
+
+ VERIFY_PDATA_R (query_date_type);
+
+ return qof_query_date_predicate (pd->how, pdata->options, pdata->date);
+}
+
+static gboolean
+date_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_date_t pd1 = (query_date_t) p1;
+ query_date_t pd2 = (query_date_t) p2;
+
+ if (pd1->options != pd2->options) return FALSE;
+ return timespec_equal (&(pd1->date), &(pd2->date));
+}
+
+QofQueryPredData *
+qof_query_date_predicate (QofQueryCompare how,
+ QofDateMatch options, Timespec date)
+{
+ query_date_t pdata;
+
+ pdata = g_new0 (query_date_def, 1);
+ pdata->pd.type_name = query_date_type;
+ pdata->pd.how = how;
+ pdata->options = options;
+ pdata->date = date;
+ return ((QofQueryPredData*)pdata);
+}
+
+gboolean
+qof_query_date_predicate_get_date (QofQueryPredData *pd, Timespec *date)
+{
+ query_date_t pdata = (query_date_t)pd;
+
+ if (pdata->pd.type_name != query_date_type)
+ return FALSE;
+ *date = pdata->date;
+ return TRUE;
+}
+
+static char *
+date_to_string (gpointer object, QofParam *getter)
+{
+ Timespec ts = ((query_date_getter)getter->param_getfcn)(object, getter);
+
+ if (ts.tv_sec || ts.tv_nsec)
+ return g_strdup (gnc_print_date (ts));
+
+ return NULL;
+}
+
+/* QOF_TYPE_NUMERIC ================================================= */
+
+static int
+numeric_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData* pd)
+{
+ query_numeric_t pdata = (query_numeric_t)pd;
+ gnc_numeric obj_val;
+ int compare;
+
+ VERIFY_PREDICATE (query_numeric_type);
+
+ obj_val = ((query_numeric_getter)getter->param_getfcn) (object, getter);
+
+ switch (pdata->options) {
+ case QOF_NUMERIC_MATCH_CREDIT:
+ if (gnc_numeric_positive_p (obj_val)) return 0;
+ break;
+ case QOF_NUMERIC_MATCH_DEBIT:
+ if (gnc_numeric_negative_p (obj_val)) return 0;
+ break;
+ default:
+ break;
+ }
+
+ /* Amounts are considered to be 'equal' if they match to
+ * four decimal places. (epsilon=1/10000) */
+ if (pd->how == QOF_COMPARE_EQUAL || pd->how == QOF_COMPARE_NEQ) {
+ gnc_numeric cmp_val = gnc_numeric_create (1, 10000);
+ compare =
+ (gnc_numeric_compare (gnc_numeric_abs
+ (gnc_numeric_sub (gnc_numeric_abs (obj_val),
+ gnc_numeric_abs (pdata->amount),
+ 100000, GNC_HOW_RND_ROUND)),
+ cmp_val) < 0);
+ } else
+ compare = gnc_numeric_compare (gnc_numeric_abs (obj_val), pdata->amount);
+
+ switch (pd->how) {
+ case QOF_COMPARE_LT:
+ return (compare < 0);
+ case QOF_COMPARE_LTE:
+ return (compare <= 0);
+ case QOF_COMPARE_EQUAL:
+ return compare;
+ case QOF_COMPARE_GT:
+ return (compare > 0);
+ case QOF_COMPARE_GTE:
+ return (compare >= 0);
+ case QOF_COMPARE_NEQ:
+ return !compare;
+ default:
+ PWARN ("bad match type: %d", pd->how);
+ return 0;
+ }
+}
+
+static int
+numeric_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
+{
+ gnc_numeric va, vb;
+
+ g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+ va = ((query_numeric_getter)getter->param_getfcn) (a, getter);
+ vb = ((query_numeric_getter)getter->param_getfcn) (b, getter);
+
+ return gnc_numeric_compare (va, vb);
+}
+
+static void
+numeric_free_pdata (QofQueryPredData* pd)
+{
+ query_numeric_t pdata = (query_numeric_t)pd;
+ VERIFY_PDATA (query_numeric_type);
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+numeric_copy_predicate (QofQueryPredData *pd)
+{
+ query_numeric_t pdata = (query_numeric_t)pd;
+ VERIFY_PDATA_R (query_numeric_type);
+ return qof_query_numeric_predicate (pd->how, pdata->options, pdata->amount);
+}
+
+static gboolean
+numeric_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_numeric_t pd1 = (query_numeric_t) p1;
+ query_numeric_t pd2 = (query_numeric_t) p2;
+
+ if (pd1->options != pd2->options) return FALSE;
+ return gnc_numeric_equal (pd1->amount, pd2->amount);
+}
+
+QofQueryPredData *
+qof_query_numeric_predicate (QofQueryCompare how,
+ QofNumericMatch options,
+ gnc_numeric value)
+{
+ query_numeric_t pdata;
+ pdata = g_new0 (query_numeric_def, 1);
+ pdata->pd.type_name = query_numeric_type;
+ pdata->pd.how = how;
+ pdata->options = options;
+ pdata->amount = value;
+ return ((QofQueryPredData*)pdata);
+}
+
+static char *
+numeric_to_string (gpointer object, QofParam *getter)
+{
+ gnc_numeric num;
+ num = ((query_numeric_getter)getter->param_getfcn)(object, getter);
+
+ return gnc_numeric_to_string (num);
+}
+
+static char *
+debcred_to_string (gpointer object, QofParam *getter)
+{
+ gnc_numeric num;
+ num = ((query_numeric_getter)getter->param_getfcn)(object, getter);
+
+ return gnc_numeric_to_string (num);
+}
+
+/* QOF_TYPE_GUID =================================================== */
+
+static int
+guid_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData *pd)
+{
+ query_guid_t pdata = (query_guid_t)pd;
+ GList *node, *o_list;
+ const GUID *guid = NULL;
+
+ VERIFY_PREDICATE (query_guid_type);
+
+ switch (pdata->options) {
+
+ case QOF_GUID_MATCH_ALL:
+ /* object is a GList of objects; param_getfcn must be called on each one.
+ * See if every guid in the predicate is accounted-for in the
+ * object list
+ */
+
+ for (node = pdata->guids; node; node = node->next)
+ {
+ /* See if this GUID matches the object's guid */
+ for (o_list = object; o_list; o_list = o_list->next)
+ {
+ guid = ((query_guid_getter)getter->param_getfcn) (o_list->data, getter);
+ if (guid_equal (node->data, guid))
+ break;
+ }
+
+ /*
+ * If o_list is NULL, we've walked the whole list without finding
+ * a match. Therefore break out now, the match has failed.
+ */
+ if (o_list == NULL)
+ break;
+ }
+
+ /*
+ * The match is complete. If node == NULL then we've succesfully
+ * found a match for all the guids in the predicate. Return
+ * appropriately below.
+ */
+
+ break;
+
+ case QOF_GUID_MATCH_LIST_ANY:
+ /* object is a single object, getter returns a GList* of GUID*
+ *
+ * See if any GUID* in the returned list matches any guid in the
+ * predicate match list.
+ */
+
+ o_list = ((query_glist_getter)getter->param_getfcn) (object, getter);
+
+ for (node = o_list; node; node = node->next)
+ {
+ GList *node2;
+
+ /* Search the predicate data for a match */
+ for (node2 = pdata->guids; node2; node2 = node2->next)
+ {
+ if (guid_equal (node->data, node2->data))
+ break;
+ }
+
+ /* Check to see if we found a match. If so, break now */
+ if (node2 != NULL)
+ break;
+ }
+
+ g_list_free(o_list);
+
+ /* yea, node may point to an invalid location, but that's ok.
+ * we're not _USING_ the value, just checking that it's non-NULL
+ */
+
+ break;
+
+ default:
+ /* object is a single object, getter returns a GUID*
+ *
+ * See if the guid is in the list
+ */
+
+ guid = ((query_guid_getter)getter->param_getfcn) (object, getter);
+ for (node = pdata->guids; node; node = node->next)
+ {
+ if (guid_equal (node->data, guid))
+ break;
+ }
+ }
+
+ switch (pdata->options) {
+ case QOF_GUID_MATCH_ANY:
+ case QOF_GUID_MATCH_LIST_ANY:
+ return (node != NULL);
+ break;
+ case QOF_GUID_MATCH_NONE:
+ case QOF_GUID_MATCH_ALL:
+ return (node == NULL);
+ break;
+ case QOF_GUID_MATCH_NULL:
+ return (guid == NULL);
+ break;
+ default:
+ PWARN ("bad match type");
+ return 0;
+ }
+}
+
+static void
+guid_free_pdata (QofQueryPredData *pd)
+{
+ query_guid_t pdata = (query_guid_t)pd;
+ GList *node;
+ VERIFY_PDATA (query_guid_type);
+ for (node = pdata->guids; node; node = node->next)
+ {
+ guid_free (node->data);
+ }
+ g_list_free (pdata->guids);
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+guid_copy_predicate (QofQueryPredData *pd)
+{
+ query_guid_t pdata = (query_guid_t)pd;
+ VERIFY_PDATA_R (query_guid_type);
+ return qof_query_guid_predicate (pdata->options, pdata->guids);
+}
+
+static gboolean
+guid_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_guid_t pd1 = (query_guid_t) p1;
+ query_guid_t pd2 = (query_guid_t) p2;
+ GList *l1 = pd1->guids, *l2 = pd2->guids;
+
+ if (pd1->options != pd2->options) return FALSE;
+ if (g_list_length (l1) != g_list_length (l2)) return FALSE;
+ for ( ; l1 ; l1 = l1->next, l2 = l2->next)
+ {
+ if (!guid_equal (l1->data, l2->data))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+QofQueryPredData *
+qof_query_guid_predicate (QofGuidMatch options, GList *guid_list)
+{
+ query_guid_t pdata;
+ GList *node;
+
+ if (NULL == guid_list) return NULL;
+
+ pdata = g_new0 (query_guid_def, 1);
+ pdata->pd.how = QOF_COMPARE_EQUAL;
+ pdata->pd.type_name = query_guid_type;
+ pdata->options = options;
+
+ pdata->guids = g_list_copy (guid_list);
+ for (node = pdata->guids; node; node = node->next)
+ {
+ GUID *guid = guid_malloc ();
+ *guid = *((GUID *)node->data);
+ node->data = guid;
+ }
+ return ((QofQueryPredData*)pdata);
+}
+
+/* ================================================================ */
+/* QOF_TYPE_INT32 */
+
+static int
+int32_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData *pd)
+{
+ gint32 val;
+ query_int32_t pdata = (query_int32_t)pd;
+
+ VERIFY_PREDICATE (query_int32_type);
+
+ val = ((query_int32_getter)getter->param_getfcn) (object, getter);
+
+ switch (pd->how) {
+ case QOF_COMPARE_LT:
+ return (val < pdata->val);
+ case QOF_COMPARE_LTE:
+ return (val <= pdata->val);
+ case QOF_COMPARE_EQUAL:
+ return (val == pdata->val);
+ case QOF_COMPARE_GT:
+ return (val > pdata->val);
+ case QOF_COMPARE_GTE:
+ return (val >= pdata->val);
+ case QOF_COMPARE_NEQ:
+ return (val != pdata->val);
+ default:
+ PWARN ("bad match type: %d", pd->how);
+ return 0;
+ }
+}
+
+static int
+int32_compare_func (gpointer a, gpointer b, gint options,
+ QofParam *getter)
+{
+ gint32 v1, v2;
+ g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+ v1 = ((query_int32_getter)getter->param_getfcn)(a, getter);
+ v2 = ((query_int32_getter)getter->param_getfcn)(b, getter);
+
+ if (v1 < v2) return -1;
+ if (v1 > v2) return 1;
+ return 0;
+}
+
+static void
+int32_free_pdata (QofQueryPredData *pd)
+{
+ query_int32_t pdata = (query_int32_t)pd;
+ VERIFY_PDATA (query_int32_type);
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+int32_copy_predicate (QofQueryPredData *pd)
+{
+ query_int32_t pdata = (query_int32_t)pd;
+ VERIFY_PDATA_R (query_int32_type);
+ return qof_query_int32_predicate (pd->how, pdata->val);
+}
+
+static gboolean
+int32_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_int32_t pd1 = (query_int32_t) p1;
+ query_int32_t pd2 = (query_int32_t) p2;
+
+ return (pd1->val == pd2->val);
+}
+
+QofQueryPredData *
+qof_query_int32_predicate (QofQueryCompare how, gint32 val)
+{
+ query_int32_t pdata = g_new0 (query_int32_def, 1);
+ pdata->pd.type_name = query_int32_type;
+ pdata->pd.how = how;
+ pdata->val = val;
+ return ((QofQueryPredData*)pdata);
+}
+
+static char *
+int32_to_string (gpointer object, QofParam *getter)
+{
+ gint32 num = ((query_int32_getter)getter->param_getfcn)(object, getter);
+
+ return g_strdup_printf ("%d", num);
+}
+
+/* ================================================================ */
+/* QOF_TYPE_INT64 */
+
+static int
+int64_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData *pd)
+{
+ gint64 val;
+ query_int64_t pdata = (query_int64_t)pd;
+
+ VERIFY_PREDICATE (query_int64_type);
+
+ val = ((query_int64_getter)getter->param_getfcn) (object, getter);
+
+ switch (pd->how) {
+ case QOF_COMPARE_LT:
+ return (val < pdata->val);
+ case QOF_COMPARE_LTE:
+ return (val <= pdata->val);
+ case QOF_COMPARE_EQUAL:
+ return (val == pdata->val);
+ case QOF_COMPARE_GT:
+ return (val > pdata->val);
+ case QOF_COMPARE_GTE:
+ return (val >= pdata->val);
+ case QOF_COMPARE_NEQ:
+ return (val != pdata->val);
+ default:
+ PWARN ("bad match type: %d", pd->how);
+ return 0;
+ }
+}
+
+static int
+int64_compare_func (gpointer a, gpointer b, gint options,
+ QofParam *getter)
+{
+ gint64 v1, v2;
+ g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+ v1 = ((query_int64_getter)getter->param_getfcn)(a, getter);
+ v2 = ((query_int64_getter)getter->param_getfcn)(b, getter);
+
+ if (v1 < v2) return -1;
+ if (v1 > v2) return 1;
+ return 0;
+}
+
+static void
+int64_free_pdata (QofQueryPredData *pd)
+{
+ query_int64_t pdata = (query_int64_t)pd;
+ VERIFY_PDATA (query_int64_type);
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+int64_copy_predicate (QofQueryPredData *pd)
+{
+ query_int64_t pdata = (query_int64_t)pd;
+ VERIFY_PDATA_R (query_int64_type);
+ return qof_query_int64_predicate (pd->how, pdata->val);
+}
+
+static gboolean
+int64_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_int64_t pd1 = (query_int64_t) p1;
+ query_int64_t pd2 = (query_int64_t) p2;
+
+ return (pd1->val == pd2->val);
+}
+
+QofQueryPredData *
+qof_query_int64_predicate (QofQueryCompare how, gint64 val)
+{
+ query_int64_t pdata = g_new0 (query_int64_def, 1);
+ pdata->pd.type_name = query_int64_type;
+ pdata->pd.how = how;
+ pdata->val = val;
+ return ((QofQueryPredData*)pdata);
+}
+
+static char *
+int64_to_string (gpointer object, QofParam *getter)
+{
+ gint64 num = ((query_int64_getter)getter->param_getfcn)(object, getter);
+
+ return g_strdup_printf ("%" G_GINT64_FORMAT, num);
+}
+
+/* ================================================================ */
+/* QOF_TYPE_DOUBLE */
+
+static int
+double_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData *pd)
+{
+ double val;
+ query_double_t pdata = (query_double_t)pd;
+
+ VERIFY_PREDICATE (query_double_type);
+
+ val = ((query_double_getter)getter->param_getfcn) (object, getter);
+
+ switch (pd->how) {
+ case QOF_COMPARE_LT:
+ return (val < pdata->val);
+ case QOF_COMPARE_LTE:
+ return (val <= pdata->val);
+ case QOF_COMPARE_EQUAL:
+ return (val == pdata->val);
+ case QOF_COMPARE_GT:
+ return (val > pdata->val);
+ case QOF_COMPARE_GTE:
+ return (val >= pdata->val);
+ case QOF_COMPARE_NEQ:
+ return (val != pdata->val);
+ default:
+ PWARN ("bad match type: %d", pd->how);
+ return 0;
+ }
+}
+
+static int
+double_compare_func (gpointer a, gpointer b, gint options,
+ QofParam *getter)
+{
+ double v1, v2;
+ g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+ v1 = ((query_double_getter)getter->param_getfcn) (a, getter);
+ v2 = ((query_double_getter)getter->param_getfcn) (b, getter);
+
+ if (v1 < v2) return -1;
+ if (v1 > v2) return 1;
+ return 0;
+}
+
+static void
+double_free_pdata (QofQueryPredData *pd)
+{
+ query_double_t pdata = (query_double_t)pd;
+ VERIFY_PDATA (query_double_type);
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+double_copy_predicate (QofQueryPredData *pd)
+{
+ query_double_t pdata = (query_double_t)pd;
+ VERIFY_PDATA_R (query_double_type);
+ return qof_query_double_predicate (pd->how, pdata->val);
+}
+
+static gboolean
+double_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_double_t pd1 = (query_double_t) p1;
+ query_double_t pd2 = (query_double_t) p2;
+
+ return (pd1->val == pd2->val);
+}
+
+QofQueryPredData *
+qof_query_double_predicate (QofQueryCompare how, double val)
+{
+ query_double_t pdata = g_new0 (query_double_def, 1);
+ pdata->pd.type_name = query_double_type;
+ pdata->pd.how = how;
+ pdata->val = val;
+ return ((QofQueryPredData*)pdata);
+}
+
+static char *
+double_to_string (gpointer object, QofParam *getter)
+{
+ double num = ((query_double_getter)getter->param_getfcn)(object, getter);
+
+ return g_strdup_printf ("%f", num);
+}
+
+/* QOF_TYPE_BOOLEAN =================================================== */
+
+static int
+boolean_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData *pd)
+{
+ gboolean val;
+ query_boolean_t pdata = (query_boolean_t)pd;
+
+ VERIFY_PREDICATE (query_boolean_type);
+
+ val = ((query_boolean_getter)getter->param_getfcn) (object, getter);
+
+ switch (pd->how) {
+ case QOF_COMPARE_EQUAL:
+ return (val == pdata->val);
+ case QOF_COMPARE_NEQ:
+ return (val != pdata->val);
+ default:
+ PWARN ("bad match type: %d", pd->how);
+ return 0;
+ }
+}
+
+static int
+boolean_compare_func (gpointer a, gpointer b, gint options,
+ QofParam *getter)
+{
+ gboolean va, vb;
+ g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+ va = ((query_boolean_getter)getter->param_getfcn) (a, getter);
+ vb = ((query_boolean_getter)getter->param_getfcn) (b, getter);
+ if (!va && vb) return -1;
+ if (va && !vb) return 1;
+ return 0;
+}
+
+static void
+boolean_free_pdata (QofQueryPredData *pd)
+{
+ query_boolean_t pdata = (query_boolean_t)pd;
+ VERIFY_PDATA (query_boolean_type);
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+boolean_copy_predicate (QofQueryPredData *pd)
+{
+ query_boolean_t pdata = (query_boolean_t)pd;
+ VERIFY_PDATA_R (query_boolean_type);
+ return qof_query_boolean_predicate (pd->how, pdata->val);
+}
+
+static gboolean
+boolean_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_boolean_t pd1 = (query_boolean_t) p1;
+ query_boolean_t pd2 = (query_boolean_t) p2;
+
+ return (pd1->val == pd2->val);
+}
+
+QofQueryPredData *
+qof_query_boolean_predicate (QofQueryCompare how, gboolean val)
+{
+ query_boolean_t pdata;
+ g_return_val_if_fail (how == QOF_COMPARE_EQUAL || how == QOF_COMPARE_NEQ, NULL);
+
+ pdata = g_new0 (query_boolean_def, 1);
+ pdata->pd.type_name = query_boolean_type;
+ pdata->pd.how = how;
+ pdata->val = val;
+ return ((QofQueryPredData*)pdata);
+}
+
+static char *
+boolean_to_string (gpointer object, QofParam *getter)
+{
+ gboolean num = ((query_boolean_getter)getter->param_getfcn)(object, getter);
+
+ return g_strdup_printf ("%s", (num ? "X" : ""));
+}
+
+/* QOF_TYPE_CHAR =================================================== */
+
+static int
+char_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData *pd)
+{
+ char c;
+ query_char_t pdata = (query_char_t)pd;
+
+ VERIFY_PREDICATE (query_char_type);
+
+ c = ((query_char_getter)getter->param_getfcn) (object, getter);
+
+ switch (pdata->options) {
+ case QOF_CHAR_MATCH_ANY:
+ if (strchr (pdata->char_list, c)) return 1;
+ return 0;
+ case QOF_CHAR_MATCH_NONE:
+ if (!strchr (pdata->char_list, c)) return 1;
+ return 0;
+ default:
+ PWARN ("bad match type");
+ return 0;
+ }
+}
+
+static int
+char_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
+{
+ char va, vb;
+ g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+ va = ((query_char_getter)getter->param_getfcn)(a, getter);
+ vb = ((query_char_getter)getter->param_getfcn)(b, getter);
+ return (va-vb);
+}
+
+static void
+char_free_pdata (QofQueryPredData *pd)
+{
+ query_char_t pdata = (query_char_t)pd;
+ VERIFY_PDATA (query_char_type);
+ g_free (pdata->char_list);
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+char_copy_predicate (QofQueryPredData *pd)
+{
+ query_char_t pdata = (query_char_t)pd;
+ VERIFY_PDATA_R (query_char_type);
+ return qof_query_char_predicate (pdata->options, pdata->char_list);
+}
+
+static gboolean
+char_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_char_t pd1 = (query_char_t) p1;
+ query_char_t pd2 = (query_char_t) p2;
+
+ if (pd1->options != pd2->options) return FALSE;
+ return (safe_strcmp (pd1->char_list, pd2->char_list) == 0);
+}
+
+QofQueryPredData *
+qof_query_char_predicate (QofCharMatch options, const char *chars)
+{
+ query_char_t pdata;
+ g_return_val_if_fail (chars, NULL);
+ pdata = g_new0 (query_char_def, 1);
+ pdata->pd.type_name = query_char_type;
+ pdata->pd.how = QOF_COMPARE_EQUAL;
+ pdata->options = options;
+ pdata->char_list = g_strdup (chars);
+ return ((QofQueryPredData*)pdata);
+}
+
+static char *
+char_to_string (gpointer object, QofParam *getter)
+{
+ char num = ((query_char_getter)getter->param_getfcn)(object, getter);
+
+ return g_strdup_printf ("%c", num);
+}
+
+/* QOF_TYPE_KVP ================================================ */
+
+static int
+kvp_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData *pd)
+{
+ int compare;
+ KvpFrame *kvp;
+ KvpValue *value;
+ query_kvp_t pdata = (query_kvp_t)pd;
+
+ VERIFY_PREDICATE (query_kvp_type);
+
+ kvp = ((query_kvp_getter)getter->param_getfcn) (object, getter);
+ if (!kvp)
+ return 0;
+
+ value = kvp_frame_get_slot_path_gslist (kvp, pdata->path);
+ if (!value)
+ return 0;
+
+ if (kvp_value_get_type (value) != kvp_value_get_type (pdata->value))
+ return 0;
+
+ compare = kvp_value_compare (value, pdata->value);
+
+ switch (pd->how)
+ {
+ case QOF_COMPARE_LT:
+ return (compare < 0);
+ case QOF_COMPARE_LTE:
+ return (compare <= 0);
+ case QOF_COMPARE_EQUAL:
+ return (compare == 0);
+ case QOF_COMPARE_GTE:
+ return (compare >= 0);
+ case QOF_COMPARE_GT:
+ return (compare > 0);
+ case QOF_COMPARE_NEQ:
+ return (compare != 0);
+ default:
+ PWARN ("bad match type: %d", pd->how);
+ return 0;
+ }
+}
+
+static void
+kvp_free_pdata (QofQueryPredData *pd)
+{
+ query_kvp_t pdata = (query_kvp_t)pd;
+ GSList *node;
+
+ VERIFY_PDATA (query_kvp_type);
+ kvp_value_delete (pdata->value);
+ for (node = pdata->path; node; node = node->next)
+ {
+ g_free (node->data);
+ node->data = NULL;
+ }
+ g_slist_free (pdata->path);
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+kvp_copy_predicate (QofQueryPredData *pd)
+{
+ query_kvp_t pdata = (query_kvp_t)pd;
+ VERIFY_PDATA_R (query_kvp_type);
+ return qof_query_kvp_predicate (pd->how, pdata->path, pdata->value);
+}
+
+static gboolean
+kvp_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_kvp_t pd1 = (query_kvp_t) p1;
+ query_kvp_t pd2 = (query_kvp_t) p2;
+ GSList *n1, *n2;
+
+ n1 = pd1->path;
+ n2 = pd2->path;
+
+ for ( ; n1 && n2; n1 = n1->next, n2 = n2->next)
+ {
+ if (safe_strcmp (n1->data, n2->data) != 0)
+ return FALSE;
+ }
+
+ if (n1 || n2)
+ return FALSE;
+
+ return (kvp_value_compare (pd1->value, pd2->value) == 0);
+}
+
+QofQueryPredData *
+qof_query_kvp_predicate (QofQueryCompare how,
+ GSList *path, const KvpValue *value)
+{
+ query_kvp_t pdata;
+ GSList *node;
+
+ g_return_val_if_fail (path && value, NULL);
+
+ pdata = g_new0 (query_kvp_def, 1);
+ pdata->pd.type_name = query_kvp_type;
+ pdata->pd.how = how;
+ pdata->value = kvp_value_copy (value);
+ pdata->path = g_slist_copy (path);
+ for (node = pdata->path; node; node = node->next)
+ node->data = g_strdup (node->data);
+
+ return ((QofQueryPredData*)pdata);
+}
+
+QofQueryPredData *
+qof_query_kvp_predicate_path (QofQueryCompare how,
+ const char *path, const KvpValue *value)
+{
+ QofQueryPredData *pd;
+ GSList *spath = NULL;
+ char *str, *p;
+
+ if (!path) return NULL;
+
+ str = g_strdup (path);
+ p = str;
+ if (0 == *p) return NULL;
+ if ('/' == *p) p++;
+
+ while (p)
+ {
+ spath = g_slist_append (spath, p);
+ p = strchr (p, '/');
+ if (p) { *p = 0; p++; }
+ }
+
+ pd = qof_query_kvp_predicate (how, spath, value);
+ g_free (str);
+ return pd;
+}
+
+
+/* QOF_TYPE_COLLECT =============================================== */
+
+static int
+collect_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData *pd)
+{
+ query_coll_t pdata;
+ QofCollection *coll;
+ GList *node, *node2, *o_list;
+ const GUID *guid;
+
+ pdata = (query_coll_t)pd;
+ VERIFY_PREDICATE (query_collect_type);
+ coll = ((query_collect_getter)getter->param_getfcn) (object, getter);
+ guid = NULL;
+ switch(pdata->options) {
+ case QOF_GUID_MATCH_ALL : {
+ for (node = pdata->guids; node; node = node->next)
+ {
+ for (o_list = object; o_list; o_list = o_list->next)
+ {
+ guid = ((query_guid_getter)getter->param_getfcn)
+ (o_list->data, getter);
+ if (guid_equal (node->data, guid)) {
+ break;
+ }
+ }
+ if (o_list == NULL) {
+ break;
+ }
+ }
+ break;
+ }
+ case QOF_GUID_MATCH_LIST_ANY : {
+ o_list = ((query_glist_getter)getter->param_getfcn) (object, getter);
+ for (node = o_list; node; node = node->next)
+ {
+ for (node2 = pdata->guids; node2; node2 = node2->next)
+ {
+ if (guid_equal (node->data, node2->data)) {
+ break;
+ }
+ }
+ if (node2 != NULL) {
+ break;
+ }
+ }
+ g_list_free(o_list);
+ break;
+ }
+ default : {
+ guid = ((query_guid_getter)getter->param_getfcn) (object, getter);
+ for (node = pdata->guids; node; node = node->next)
+ {
+ if (guid_equal (node->data, guid)) {
+ break;
+ }
+ }
+ }
+ switch (pdata->options) {
+ case QOF_GUID_MATCH_ANY :
+ case QOF_GUID_MATCH_LIST_ANY : {
+ return (node != NULL);
+ break;
+ }
+ case QOF_GUID_MATCH_NONE :
+ case QOF_GUID_MATCH_ALL : {
+ return (node == NULL);
+ break;
+ }
+ case QOF_GUID_MATCH_NULL : {
+ return (guid == NULL);
+ break;
+ }
+ default : {
+ PWARN ("bad match type");
+ return 0;
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+collect_compare_func (gpointer a, gpointer b, gint options, QofParam *getter)
+{
+ gint result;
+ QofCollection *c1, *c2;
+
+ c1 = ((query_collect_getter)getter->param_getfcn) (a, getter);
+ c2 = ((query_collect_getter)getter->param_getfcn) (b, getter);
+ result = qof_collection_compare(c1, c2);
+ return result;
+}
+
+static void
+collect_free_pdata (QofQueryPredData *pd)
+{
+ query_coll_t pdata;
+ GList *node;
+
+ node = NULL;
+ pdata = (query_coll_t) pd;
+ VERIFY_PDATA (query_collect_type);
+ for (node = pdata->guids; node; node = node->next)
+ {
+ guid_free (node->data);
+ }
+ qof_collection_destroy(pdata->coll);
+ g_list_free (pdata->guids);
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+collect_copy_predicate (QofQueryPredData *pd)
+{
+ query_coll_t pdata = (query_coll_t) pd;
+
+ VERIFY_PDATA_R (query_collect_type);
+ return qof_query_collect_predicate (pdata->options, pdata->coll);
+}
+
+static gboolean
+collect_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_coll_t pd1;
+ query_coll_t pd2;
+ gint result;
+
+ pd1 = (query_coll_t) p1;
+ pd2 = (query_coll_t) p2;
+ result = qof_collection_compare(pd1->coll, pd2->coll);
+ if(result == 0) { return TRUE; }
+ return FALSE;
+}
+
+static void
+query_collect_cb(QofEntity* ent, gpointer user_data)
+{
+ query_coll_t pdata;
+ GUID *guid;
+
+ guid = guid_malloc();
+ guid = (GUID*)qof_entity_get_guid(ent);
+ pdata = (query_coll_t)user_data;
+ pdata->guids = g_list_append(pdata->guids, guid);
+}
+
+QofQueryPredData *
+qof_query_collect_predicate (QofGuidMatch options, QofCollection *coll)
+{
+ query_coll_t pdata;
+
+ g_return_val_if_fail (coll, NULL);
+ pdata = g_new0 (query_coll_def, 1);
+ pdata->pd.type_name = query_collect_type;
+ pdata->options = options;
+ qof_collection_foreach(coll, query_collect_cb, pdata);
+ if (NULL == pdata->guids) { return NULL; }
+ return ((QofQueryPredData*)pdata);
+}
+
+/* QOF_TYPE_CHOICE */
+
+static int
+choice_match_predicate (gpointer object, QofParam *getter,
+ QofQueryPredData *pd)
+{
+ query_choice_t pdata = (query_choice_t)pd;
+ GList *node, *o_list;
+ const GUID *guid = NULL;
+
+ VERIFY_PREDICATE (query_choice_type);
+
+ switch (pdata->options) {
+
+ case QOF_GUID_MATCH_ALL:
+ /* object is a GList of objects; param_getfcn must be called on each one.
+ * See if every guid in the predicate is accounted-for in the
+ * object list
+ */
+
+ for (node = pdata->guids; node; node = node->next)
+ {
+ /* See if this GUID matches the object's guid */
+ for (o_list = object; o_list; o_list = o_list->next)
+ {
+ guid = ((query_choice_getter)getter->param_getfcn) (o_list->data, getter);
+ if (guid_equal (node->data, guid))
+ break;
+ }
+
+ /*
+ * If o_list is NULL, we've walked the whole list without finding
+ * a match. Therefore break out now, the match has failed.
+ */
+ if (o_list == NULL)
+ break;
+ }
+
+ /*
+ * The match is complete. If node == NULL then we've succesfully
+ * found a match for all the guids in the predicate. Return
+ * appropriately below.
+ */
+
+ break;
+
+ case QOF_GUID_MATCH_LIST_ANY:
+
+ o_list = ((query_glist_getter)getter->param_getfcn) (object, getter);
+
+ for (node = o_list; node; node = node->next)
+ {
+ GList *node2;
+
+ for (node2 = pdata->guids; node2; node2 = node2->next)
+ {
+ if (guid_equal (node->data, node2->data))
+ break;
+ }
+
+ if (node2 != NULL)
+ break;
+ }
+
+ g_list_free(o_list);
+
+ break;
+
+ default:
+ /* object is a single object, getter returns a GUID*
+ *
+ * See if the guid is in the list
+ */
+
+ guid = ((query_choice_getter)getter->param_getfcn) (object, getter);
+ for (node = pdata->guids; node; node = node->next)
+ {
+ if (guid_equal (node->data, guid))
+ break;
+ }
+ }
+
+ switch (pdata->options) {
+ case QOF_GUID_MATCH_ANY:
+ case QOF_GUID_MATCH_LIST_ANY:
+ return (node != NULL);
+ break;
+ case QOF_GUID_MATCH_NONE:
+ case QOF_GUID_MATCH_ALL:
+ return (node == NULL);
+ break;
+ case QOF_GUID_MATCH_NULL:
+ return (guid == NULL);
+ break;
+ default:
+ PWARN ("bad match type");
+ return 0;
+ }
+}
+
+static void
+choice_free_pdata (QofQueryPredData *pd)
+{
+ query_choice_t pdata = (query_choice_t)pd;
+ GList *node;
+ VERIFY_PDATA (query_choice_type);
+ for (node = pdata->guids; node; node = node->next)
+ {
+ guid_free (node->data);
+ }
+ g_list_free (pdata->guids);
+ g_free (pdata);
+}
+
+static QofQueryPredData *
+choice_copy_predicate (QofQueryPredData *pd)
+{
+ query_choice_t pdata = (query_choice_t)pd;
+ VERIFY_PDATA_R (query_choice_type);
+ return qof_query_choice_predicate (pdata->options, pdata->guids);
+}
+
+static gboolean
+choice_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ query_choice_t pd1 = (query_choice_t) p1;
+ query_choice_t pd2 = (query_choice_t) p2;
+ GList *l1 = pd1->guids, *l2 = pd2->guids;
+
+ if (pd1->options != pd2->options) return FALSE;
+ if (g_list_length (l1) != g_list_length (l2)) return FALSE;
+ for ( ; l1 ; l1 = l1->next, l2 = l2->next)
+ {
+ if (!guid_equal (l1->data, l2->data))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+QofQueryPredData *
+qof_query_choice_predicate (QofGuidMatch options, GList *guid_list)
+{
+ query_choice_t pdata;
+ GList *node;
+
+ if (NULL == guid_list) return NULL;
+
+ pdata = g_new0 (query_choice_def, 1);
+ pdata->pd.how = QOF_COMPARE_EQUAL;
+ pdata->pd.type_name = query_choice_type;
+ pdata->options = options;
+
+ pdata->guids = g_list_copy (guid_list);
+ for (node = pdata->guids; node; node = node->next)
+ {
+ GUID *guid = guid_malloc ();
+ *guid = *((GUID *)node->data);
+ node->data = guid;
+ }
+ return ((QofQueryPredData*)pdata);
+}
+
+
+/* initialization ================================================== */
+/** This function registers a new Core Object with the QofQuery
+ * subsystem. It maps the "core_name" object to the given
+ * query_predicate, predicate_copy, and predicate_data_free functions.
+ *
+ * An example:
+ * qof_query_register_core_object (QOF_TYPE_STRING, string_match_predicate,
+ * string_compare_fcn, string_free_pdata,
+ * string_print_fcn, pred_equal_fcn);
+ */
+
+
+static void
+qof_query_register_core_object (QofType core_name,
+ QofQueryPredicateFunc pred,
+ QofCompareFunc comp,
+ QueryPredicateCopyFunc copy,
+ QueryPredDataFree pd_free,
+ QueryToString toString,
+ QueryPredicateEqual pred_equal)
+{
+ g_return_if_fail (core_name);
+ g_return_if_fail (*core_name != '\0');
+
+ if (pred)
+ g_hash_table_insert (predTable, (char *)core_name, pred);
+
+ if (comp)
+ g_hash_table_insert (cmpTable, (char *)core_name, comp);
+
+ if (copy)
+ g_hash_table_insert (copyTable, (char *)core_name, copy);
+
+ if (pd_free)
+ g_hash_table_insert (freeTable, (char *)core_name, pd_free);
+
+ if (toString)
+ g_hash_table_insert (toStringTable, (char *)core_name, toString);
+
+ if (pred_equal)
+ g_hash_table_insert (predEqualTable, (char *)core_name, pred_equal);
+}
+
+static void init_tables (void)
+{
+ unsigned int i;
+ struct
+ {
+ QofType name;
+ QofQueryPredicateFunc pred;
+ QofCompareFunc comp;
+ QueryPredicateCopyFunc copy;
+ QueryPredDataFree pd_free;
+ QueryToString toString;
+ QueryPredicateEqual pred_equal;
+ } knownTypes[] =
+ {
+ { QOF_TYPE_STRING, string_match_predicate, string_compare_func,
+ string_copy_predicate, string_free_pdata, string_to_string,
+ string_predicate_equal },
+ { QOF_TYPE_DATE, date_match_predicate, date_compare_func,
+ date_copy_predicate, date_free_pdata, date_to_string,
+ date_predicate_equal },
+ { QOF_TYPE_DEBCRED, numeric_match_predicate, numeric_compare_func,
+ numeric_copy_predicate, numeric_free_pdata, debcred_to_string,
+ numeric_predicate_equal },
+ { QOF_TYPE_NUMERIC, numeric_match_predicate, numeric_compare_func,
+ numeric_copy_predicate, numeric_free_pdata, numeric_to_string,
+ numeric_predicate_equal },
+ { QOF_TYPE_GUID, guid_match_predicate, NULL,
+ guid_copy_predicate, guid_free_pdata, NULL,
+ guid_predicate_equal },
+ { QOF_TYPE_INT32, int32_match_predicate, int32_compare_func,
+ int32_copy_predicate, int32_free_pdata, int32_to_string,
+ int32_predicate_equal },
+ { QOF_TYPE_INT64, int64_match_predicate, int64_compare_func,
+ int64_copy_predicate, int64_free_pdata, int64_to_string,
+ int64_predicate_equal },
+ { QOF_TYPE_DOUBLE, double_match_predicate, double_compare_func,
+ double_copy_predicate, double_free_pdata, double_to_string,
+ double_predicate_equal },
+ { QOF_TYPE_BOOLEAN, boolean_match_predicate, boolean_compare_func,
+ boolean_copy_predicate, boolean_free_pdata, boolean_to_string,
+ boolean_predicate_equal },
+ { QOF_TYPE_CHAR, char_match_predicate, char_compare_func,
+ char_copy_predicate, char_free_pdata, char_to_string,
+ char_predicate_equal },
+ { QOF_TYPE_KVP, kvp_match_predicate, NULL, kvp_copy_predicate,
+ kvp_free_pdata, NULL, kvp_predicate_equal },
+ { QOF_TYPE_COLLECT, collect_match_predicate, collect_compare_func,
+ collect_copy_predicate, collect_free_pdata, NULL,
+ collect_predicate_equal },
+ { QOF_TYPE_CHOICE, choice_match_predicate, NULL,
+ choice_copy_predicate, choice_free_pdata, NULL, choice_predicate_equal },
+ };
+
+ /* Register the known data types */
+ for (i = 0; i < (sizeof(knownTypes)/sizeof(*knownTypes)); i++)
+ {
+ qof_query_register_core_object (knownTypes[i].name,
+ knownTypes[i].pred,
+ knownTypes[i].comp,
+ knownTypes[i].copy,
+ knownTypes[i].pd_free,
+ knownTypes[i].toString,
+ knownTypes[i].pred_equal);
+ }
+}
+
+static QueryPredicateCopyFunc
+qof_query_copy_predicate (QofType type)
+{
+ QueryPredicateCopyFunc rc;
+ g_return_val_if_fail (type, NULL);
+ rc = g_hash_table_lookup (copyTable, type);
+ return rc;
+}
+
+static QueryPredDataFree
+qof_query_predicate_free (QofType type)
+{
+ g_return_val_if_fail (type, NULL);
+ return g_hash_table_lookup (freeTable, type);
+}
+
+/********************************************************************/
+/* PUBLISHED API FUNCTIONS */
+
+void qof_query_core_init (void)
+{
+ /* Only let us initialize once */
+ if (initialized) return;
+ initialized = TRUE;
+
+ /* Create the tables */
+ predTable = g_hash_table_new (g_str_hash, g_str_equal);
+ cmpTable = g_hash_table_new (g_str_hash, g_str_equal);
+ copyTable = g_hash_table_new (g_str_hash, g_str_equal);
+ freeTable = g_hash_table_new (g_str_hash, g_str_equal);
+ toStringTable = g_hash_table_new (g_str_hash, g_str_equal);
+ predEqualTable = g_hash_table_new (g_str_hash, g_str_equal);
+
+ init_tables ();
+}
+
+void qof_query_core_shutdown (void)
+{
+ if (!initialized) return;
+ initialized = FALSE;
+
+ g_hash_table_destroy (predTable);
+ g_hash_table_destroy (cmpTable);
+ g_hash_table_destroy (copyTable);
+ g_hash_table_destroy (freeTable);
+ g_hash_table_destroy (toStringTable);
+ g_hash_table_destroy (predEqualTable);
+}
+
+QofQueryPredicateFunc
+qof_query_core_get_predicate (QofType type)
+{
+ g_return_val_if_fail (type, NULL);
+ return g_hash_table_lookup (predTable, type);
+}
+
+QofCompareFunc
+qof_query_core_get_compare (QofType type)
+{
+ g_return_val_if_fail (type, NULL);
+ return g_hash_table_lookup (cmpTable, type);
+}
+
+void
+qof_query_core_predicate_free (QofQueryPredData *pdata)
+{
+ QueryPredDataFree free_fcn;
+
+ g_return_if_fail (pdata);
+ g_return_if_fail (pdata->type_name);
+
+ free_fcn = qof_query_predicate_free (pdata->type_name);
+ free_fcn (pdata);
+}
+
+QofQueryPredData *
+qof_query_core_predicate_copy (QofQueryPredData *pdata)
+{
+ QueryPredicateCopyFunc copy;
+
+ g_return_val_if_fail (pdata, NULL);
+ g_return_val_if_fail (pdata->type_name, NULL);
+
+ copy = qof_query_copy_predicate (pdata->type_name);
+ return (copy (pdata));
+}
+
+char *
+qof_query_core_to_string (QofType type, gpointer object,
+ QofParam *getter)
+{
+ QueryToString toString;
+
+ g_return_val_if_fail (type, NULL);
+ g_return_val_if_fail (object, NULL);
+ g_return_val_if_fail (getter, NULL);
+
+ toString = g_hash_table_lookup (toStringTable, type);
+ g_return_val_if_fail (toString, NULL);
+
+ return toString (object, getter);
+}
+
+gboolean
+qof_query_core_predicate_equal (QofQueryPredData *p1, QofQueryPredData *p2)
+{
+ QueryPredicateEqual pred_equal;
+
+ if (p1 == p2) return TRUE;
+ if (!p1 || !p2) return FALSE;
+
+ if (p1->how != p2->how) return FALSE;
+ if (safe_strcmp (p1->type_name, p2->type_name)) return FALSE;
+
+ pred_equal = g_hash_table_lookup (predEqualTable, p1->type_name);
+ g_return_val_if_fail (pred_equal, FALSE);
+
+ return pred_equal (p1, p2);
+}
Added: gnucash/trunk/lib/libqof/qof/qofquerycore.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofquerycore.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofquerycore.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,196 @@
+/********************************************************************\
+ * qofquerycore.h -- API for providing core Query data types *
+ * Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+/** @addtogroup Query
+ @{ */
+
+/** @file qofquerycore.h
+ @brief API for providing core Query data types
+ @author Copyright (C) 2002 Derek Atkins <warlord at MIT.EDU>
+*/
+
+#ifndef QOF_QUERYCORE_H
+#define QOF_QUERYCORE_H
+
+#include <glib.h>
+
+#include "gnc-numeric.h"
+#include "gnc-date.h"
+#include "kvp_frame.h"
+#include "qofclass.h"
+
+/**
+ * PREDICATE DATA TYPES: All the predicate data types are rolled up into
+ * the union type PredicateData. The "type" field specifies which type
+ * the union is.
+ */
+typedef struct _QofQueryPredData QofQueryPredData;
+
+/** Standard Query comparitors, for how to compare objects in a predicate.
+ * Note that not all core types implement all comparitors
+ */
+typedef enum {
+ QOF_COMPARE_LT = 1,
+ QOF_COMPARE_LTE,
+ QOF_COMPARE_EQUAL,
+ QOF_COMPARE_GT,
+ QOF_COMPARE_GTE,
+ QOF_COMPARE_NEQ
+} QofQueryCompare;
+
+/** List of known core query data-types...
+ * Each core query type defines it's set of optional "comparitor qualifiers".
+ */
+/* Comparisons for QOF_TYPE_STRING */
+typedef enum {
+ QOF_STRING_MATCH_NORMAL = 1,
+ QOF_STRING_MATCH_CASEINSENSITIVE
+} QofStringMatch;
+
+/** Comparisons for QOF_TYPE_DATE
+ * The QOF_DATE_MATCH_DAY comparison rounds the two time
+ * values to mid-day and then compares these rounded values.
+ * The QOF_DATE_MATCH_NORMAL comparison matches the time values,
+ * down to the second.
+ */
+/* XXX remove these deprecated old names .. */
+//#define QOF_DATE_MATCH_ROUNDED QOF_DATE_MATCH_DAY
+//#define QOF_DATE_MATCH_NORMAL QOF_DATE_MATCH_TIME
+typedef enum {
+ QOF_DATE_MATCH_NORMAL = 1,
+ QOF_DATE_MATCH_DAY
+} QofDateMatch;
+
+/** Comparisons for QOF_TYPE_NUMERIC, QOF_TYPE_DEBCRED
+ *
+ * XXX Should be deprecated, or at least wrapped up as a convnience
+ * function, this is based on the old bill gribble code, which assumed
+ * the amount was always positive, and then specified a funds-flow
+ * direction (credit, debit, or either).
+ *
+ * The point being that 'match credit' is equivalent to the compound
+ * predicate (amount >= 0) && (amount 'op' value) while the 'match
+ * debit' predicate is equivalent to (amount <= 0) && (abs(amount) 'op' value)
+*/
+
+typedef enum {
+ QOF_NUMERIC_MATCH_DEBIT = 1,
+ QOF_NUMERIC_MATCH_CREDIT,
+ QOF_NUMERIC_MATCH_ANY
+} QofNumericMatch;
+
+/* Comparisons for QOF_TYPE_GUID */
+typedef enum {
+ /** These expect a single object and expect the
+ * QofAccessFunc returns GUID* */
+ QOF_GUID_MATCH_ANY = 1,
+ QOF_GUID_MATCH_NONE,
+ QOF_GUID_MATCH_NULL,
+ /** These expect a GList* of objects and calls the QofAccessFunc routine
+ * on each item in the list to obtain a GUID* for each object */
+ QOF_GUID_MATCH_ALL,
+ /** These expect a single object and expect the QofAccessFunc function
+ * to return a GList* of GUID* (the list is the property of the caller) */
+ QOF_GUID_MATCH_LIST_ANY,
+} QofGuidMatch;
+
+/** A CHAR type is for a RECNCell, Comparisons for QOF_TYPE_CHAR
+ * 'ANY' will match any charagter in the string.
+ *
+ * Match 'ANY' is a convenience/performance-enhanced predicate
+ * for the compound statement (value==char1) || (value==char2) || etc.
+ * Match 'NONE' is equivalent to
+ * (value != char1) && (value != char2) && etc.
+ */
+typedef enum {
+ QOF_CHAR_MATCH_ANY = 1,
+ QOF_CHAR_MATCH_NONE
+} QofCharMatch;
+
+/** No extended comparisons for QOF_TYPE_INT32, QOF_TYPE_INT64,
+ * QOF_TYPE_DOUBLE, QOF_TYPE_BOOLEAN, QOF_TYPE_KVP
+ */
+
+/** Head of Predicate Data structures. All PData must start like this. */
+struct _QofQueryPredData {
+ QofType type_name; /* QOF_TYPE_* */
+ QofQueryCompare how;
+};
+
+
+/** @name Core Data Type Predicates
+ @{ */
+QofQueryPredData *qof_query_string_predicate (QofQueryCompare how,
+ const char *str,
+ QofStringMatch options,
+ gboolean is_regex);
+
+QofQueryPredData *qof_query_date_predicate (QofQueryCompare how,
+ QofDateMatch options,
+ Timespec date);
+
+QofQueryPredData *qof_query_numeric_predicate (QofQueryCompare how,
+ QofNumericMatch options,
+ gnc_numeric value);
+
+QofQueryPredData *qof_query_guid_predicate (QofGuidMatch options, GList *guids);
+QofQueryPredData *qof_query_int32_predicate (QofQueryCompare how, gint32 val);
+QofQueryPredData *qof_query_int64_predicate (QofQueryCompare how, gint64 val);
+QofQueryPredData *qof_query_double_predicate (QofQueryCompare how, double val);
+QofQueryPredData *qof_query_boolean_predicate (QofQueryCompare how, gboolean val);
+QofQueryPredData *qof_query_char_predicate (QofCharMatch options,
+ const char *chars);
+QofQueryPredData *qof_query_collect_predicate (QofGuidMatch options,
+ QofCollection *coll);
+QofQueryPredData *qof_query_choice_predicate (QofGuidMatch options, GList *guids);
+
+/** The qof_query_kvp_predicate() matches the object that has
+ * the value 'value' located at the path 'path'. In a certain
+ * sense, the 'path' is handled as if it were a paramter.
+ */
+QofQueryPredData *qof_query_kvp_predicate (QofQueryCompare how,
+ GSList *path,
+ const KvpValue *value);
+
+/** Same predicate as above, except that 'path' is assumed to be
+ * a string containing slash-separated pathname. */
+QofQueryPredData *qof_query_kvp_predicate_path (QofQueryCompare how,
+ const char *path,
+ const KvpValue *value);
+
+/** Copy a predicate. */
+QofQueryPredData *qof_query_core_predicate_copy (QofQueryPredData *pdata);
+
+/** Destroy a predicate. */
+void qof_query_core_predicate_free (QofQueryPredData *pdata);
+
+/** Retrieve a predicate. */
+gboolean qof_query_date_predicate_get_date (QofQueryPredData *pd, Timespec *date);
+/** Return a printable string for a core data object. Caller needs
+ * to g_free() the returned string.
+ */
+char * qof_query_core_to_string (QofType, gpointer object, QofParam *getter);
+
+#endif /* QOF_QUERYCORE_H */
+/* @} */
+/* @} */
Added: gnucash/trunk/lib/libqof/qof/qofsession-p.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofsession-p.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofsession-p.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,76 @@
+/********************************************************************\
+ * qofsession-p.h -- private functions for QOF sessions. *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+/*
+ * HISTORY:
+ * Copyright (c) 2001 Linux Developers Group
+ * Copyright (c) 1998-2003 Linas Vepstas <linas at linas.org>
+ */
+
+#ifndef QOF_SESSION_P_H
+#define QOF_SESSION_P_H
+
+#include "qofbook.h"
+#include "qofsession.h"
+
+struct _QofSession
+{
+ /* This is just a "fake" entry point to allow me to pass a Session as
+ * an Entity. NOTE: THIS IS NOT AN ENTITY! THE ONLY PART OF ENTITY
+ * THAT IS VALID IS E_TYPE!
+ */
+ QofEntity entity;
+
+ /* A book holds pointers to the various types of datasets.
+ * A session may have multiple books. */
+ GList *books;
+
+ /* The requested book id, in the form or a URI, such as
+ * file:/some/where, or sql:server.host.com:555
+ */
+ char *book_id;
+
+ /* If any book subroutine failed, this records the failure reason
+ * (file not found, etc).
+ * This is a 'stack' that is one deep. (Should be deeper ??)
+ * FIXME: Each backend has its own error stack. The session
+ * and the backends should all be using (or making it look like)
+ * there is only one stack.
+ */
+ QofBackendError last_err;
+ char *error_message;
+
+ /* ---------------------------------------------------- */
+ /* Pointer to the backend that is actually used to move data
+ * between the persistant store and the local engine. */
+ QofBackend *backend;
+};
+
+
+QofBackend * qof_session_get_backend (QofSession *session);
+
+void qof_session_push_error (QofSession *session, QofBackendError err,
+ const char *message);
+
+QofBackend* gncBackendInit_file(const char *book_id, void *data);
+
+#endif
Added: gnucash/trunk/lib/libqof/qof/qofsession.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofsession.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofsession.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,1458 @@
+/********************************************************************\
+ * qofsesssion.c -- session access (connection to backend) *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+\********************************************************************/
+
+/**
+ * @file qofsession.c
+ * @brief Encapsulate a connection to a storage backend.
+ *
+ * HISTORY:
+ * Created by Linas Vepstas December 1998
+
+ @author Copyright (c) 1998-2004 Linas Vepstas <linas at linas.org>
+ @author Copyright (c) 2000 Dave Peticolas
+ @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+ */
+
+#include "config.h"
+
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include "qofla-dir.h"
+#include "gnc-trace.h"
+#include "gnc-engine-util.h"
+#include "gnc-event.h"
+#include "qofsession.h"
+#include "qofbackend-p.h"
+#include "qof-be-utils.h"
+#include "qofbook.h"
+#include "qofbook-p.h"
+#include "qofobject.h"
+#include "qofsession-p.h"
+
+/** \deprecated should not be static */
+static QofSession * current_session = NULL;
+static GHookList * session_closed_hooks = NULL;
+static QofLogModule log_module = QOF_MOD_SESSION;
+static GSList *provider_list = NULL;
+
+/* ====================================================================== */
+
+void
+qof_backend_register_provider (QofBackendProvider *prov)
+{
+ provider_list = g_slist_prepend (provider_list, prov);
+}
+
+/* ====================================================================== */
+
+/* hook routines */
+
+void
+qof_session_add_close_hook (GFunc fn, gpointer data)
+{
+ GHook *hook;
+
+ if (session_closed_hooks == NULL) {
+ session_closed_hooks = malloc(sizeof(GHookList));
+ g_hook_list_init (session_closed_hooks, sizeof(GHook));
+ }
+
+ hook = g_hook_alloc(session_closed_hooks);
+ if (!hook)
+ return;
+
+ hook->func = (GHookFunc)fn;
+ hook->data = data;
+ g_hook_append(session_closed_hooks, hook);
+}
+
+void
+qof_session_call_close_hooks (QofSession *session)
+{
+ GHook *hook;
+ GFunc fn;
+
+ if (session_closed_hooks == NULL)
+ return;
+
+ hook = g_hook_first_valid (session_closed_hooks, FALSE);
+ while (hook) {
+ fn = (GFunc)hook->func;
+ fn(session, hook->data);
+ hook = g_hook_next_valid (session_closed_hooks, hook, FALSE);
+ }
+}
+
+/* ====================================================================== */
+/* error handling routines */
+
+static void
+qof_session_clear_error (QofSession *session)
+{
+ QofBackendError err;
+
+ session->last_err = ERR_BACKEND_NO_ERR;
+ g_free(session->error_message);
+ session->error_message = NULL;
+
+ /* pop the stack on the backend as well. */
+ if (session->backend)
+ {
+ do
+ {
+ err = qof_backend_get_error (session->backend);
+ } while (ERR_BACKEND_NO_ERR != err);
+ }
+}
+
+void
+qof_session_push_error (QofSession *session, QofBackendError err,
+ const char *message)
+{
+ if (!session) return;
+
+ g_free (session->error_message);
+
+ session->last_err = err;
+ session->error_message = g_strdup (message);
+}
+
+QofBackendError
+qof_session_get_error (QofSession * session)
+{
+ QofBackendError err;
+
+ if (!session) return ERR_BACKEND_NO_BACKEND;
+
+ /* if we have a local error, return that. */
+ if (ERR_BACKEND_NO_ERR != session->last_err)
+ {
+ return session->last_err;
+ }
+
+ /* maybe we should return a no-backend error ??? */
+ if (! session->backend) return ERR_BACKEND_NO_ERR;
+
+ err = qof_backend_get_error (session->backend);
+ session->last_err = err;
+ return err;
+}
+
+static const char *
+get_default_error_message(QofBackendError err)
+{
+ return "";
+}
+
+const char *
+qof_session_get_error_message(QofSession *session)
+{
+ if(!session) return "";
+ if(!session->error_message)
+ return get_default_error_message(session->last_err);
+ return session->error_message;
+}
+
+QofBackendError
+qof_session_pop_error (QofSession * session)
+{
+ QofBackendError err;
+
+ if (!session) return ERR_BACKEND_NO_BACKEND;
+
+ err = qof_session_get_error(session);
+ qof_session_clear_error(session);
+
+ return err;
+}
+
+/* ====================================================================== */
+
+static void
+qof_session_init (QofSession *session)
+{
+ if (!session) return;
+
+ session->entity.e_type = QOF_ID_SESSION;
+ session->books = g_list_append (NULL, qof_book_new ());
+ session->book_id = NULL;
+ session->backend = NULL;
+
+ qof_session_clear_error (session);
+}
+
+QofSession *
+qof_session_new (void)
+{
+ QofSession *session = g_new0(QofSession, 1);
+ qof_session_init(session);
+ return session;
+}
+
+/** \deprecated Each application should keep
+their \b own session context. */
+QofSession *
+qof_session_get_current_session (void)
+{
+ if (!current_session)
+ {
+ gnc_engine_suspend_events ();
+ current_session = qof_session_new ();
+ gnc_engine_resume_events ();
+ }
+
+ return current_session;
+}
+
+/** \deprecated Each application should keep
+their \b own session context. */
+void
+qof_session_set_current_session (QofSession *session)
+{
+ current_session = session;
+}
+
+QofBook *
+qof_session_get_book (QofSession *session)
+{
+ GList *node;
+ if (!session) return NULL;
+
+ for (node=session->books; node; node=node->next)
+ {
+ QofBook *book = node->data;
+ if ('y' == book->book_open) return book;
+ }
+ return NULL;
+}
+
+void
+qof_session_add_book (QofSession *session, QofBook *addbook)
+{
+ GList *node;
+ if (!session) return;
+
+ ENTER (" sess=%p book=%p", session, addbook);
+
+ /* See if this book is already there ... */
+ for (node=session->books; node; node=node->next)
+ {
+ QofBook *book = node->data;
+ if (addbook == book) return;
+ }
+
+ if ('y' == addbook->book_open)
+ {
+ /* hack alert -- someone should free all the books in the list,
+ * but it should probably not be us ... since the books backends
+ * should be shutdown first, etc */
+/* XXX this should probably be an error XXX */
+ g_list_free (session->books);
+ session->books = g_list_append (NULL, addbook);
+ }
+ else
+ {
+/* XXX Need to tell the backend to add a book as well */
+ session->books = g_list_append (session->books, addbook);
+ }
+
+ qof_book_set_backend (addbook, session->backend);
+ LEAVE (" ");
+}
+
+QofBackend *
+qof_session_get_backend (QofSession *session)
+{
+ if (!session) return NULL;
+ return session->backend;
+}
+
+const char *
+qof_session_get_file_path (QofSession *session)
+{
+ if (!session) return NULL;
+ if (!session->backend) return NULL;
+ return session->backend->fullpath;
+}
+
+const char *
+qof_session_get_url (QofSession *session)
+{
+ if (!session) return NULL;
+ return session->book_id;
+}
+
+/* =============================================================== */
+
+typedef struct qof_entity_copy_data {
+ QofEntity *from;
+ QofEntity *to;
+ GList *referenceList;
+ GSList *param_list;
+ QofSession *new_session;
+ gboolean error;
+}QofEntityCopyData;
+
+static void
+qof_book_set_partial(QofBook *book)
+{
+ gboolean partial;
+
+ partial =
+ (gboolean)GPOINTER_TO_INT(qof_book_get_data(book, PARTIAL_QOFBOOK));
+ if(!partial) {
+ qof_book_set_data(book, PARTIAL_QOFBOOK, (gboolean*)TRUE);
+ }
+}
+
+void
+qof_session_update_reference_list(QofSession *session, QofEntityReference *reference)
+{
+ QofBook *book;
+ GList *book_ref_list;
+
+ book = qof_session_get_book(session);
+ book_ref_list = (GList*)qof_book_get_data(book, ENTITYREFERENCE);
+ book_ref_list = g_list_append(book_ref_list, reference);
+ qof_book_set_data(book, ENTITYREFERENCE, book_ref_list);
+ qof_book_set_partial(book);
+}
+
+static void
+qof_entity_param_cb(QofParam *param, gpointer data)
+{
+ QofEntityCopyData *qecd;
+
+ g_return_if_fail(data != NULL);
+ qecd = (QofEntityCopyData*)data;
+ g_return_if_fail(param != NULL);
+ if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
+ qecd->param_list = g_slist_prepend(qecd->param_list, param);
+ }
+}
+
+QofEntityReference*
+qof_entity_get_reference_from(QofEntity *ent, const QofParam *param)
+{
+ QofEntityReference *reference;
+ QofEntity *ref_ent;
+ const GUID *cm_guid;
+ char cm_sa[GUID_ENCODING_LENGTH + 1];
+ gchar *cm_string;
+
+ g_return_val_if_fail(param, NULL);
+ ref_ent = (QofEntity*)param->param_getfcn(ent, param);
+ if(ref_ent != NULL) {
+ reference = g_new0(QofEntityReference, 1);
+ reference->type = ent->e_type;
+ reference->ref_guid = g_new(GUID, 1);
+ reference->ent_guid = &ent->guid;
+ reference->param = qof_class_get_parameter(ent->e_type, param->param_name);
+ cm_guid = qof_entity_get_guid(ref_ent);
+ guid_to_string_buff(cm_guid, cm_sa);
+ cm_string = g_strdup(cm_sa);
+ if(TRUE == string_to_guid(cm_string, reference->ref_guid)) {
+ return reference;
+ }
+ }
+ return NULL;
+}
+
+static void
+qof_entity_foreach_copy(gpointer data, gpointer user_data)
+{
+ QofEntity *importEnt, *targetEnt, *referenceEnt;
+ QofEntityCopyData *context;
+ QofEntityReference *reference;
+ gboolean registered_type;
+ /* cm_ prefix used for variables that hold the data to commit */
+ QofParam *cm_param;
+ gchar *cm_string, *cm_char;
+ const GUID *cm_guid;
+ KvpFrame *cm_kvp;
+ /* function pointers and variables for parameter getters that don't use pointers normally */
+ gnc_numeric cm_numeric, (*numeric_getter) (QofEntity*, QofParam*);
+ double cm_double, (*double_getter) (QofEntity*, QofParam*);
+ gboolean cm_boolean, (*boolean_getter) (QofEntity*, QofParam*);
+ gint32 cm_i32, (*int32_getter) (QofEntity*, QofParam*);
+ gint64 cm_i64, (*int64_getter) (QofEntity*, QofParam*);
+ Timespec cm_date, (*date_getter) (QofEntity*, QofParam*);
+ /* function pointers to the parameter setters */
+ void (*string_setter) (QofEntity*, const char*);
+ void (*date_setter) (QofEntity*, Timespec);
+ void (*numeric_setter) (QofEntity*, gnc_numeric);
+ void (*guid_setter) (QofEntity*, const GUID*);
+ void (*double_setter) (QofEntity*, double);
+ void (*boolean_setter) (QofEntity*, gboolean);
+ void (*i32_setter) (QofEntity*, gint32);
+ void (*i64_setter) (QofEntity*, gint64);
+ void (*char_setter) (QofEntity*, char*);
+ void (*kvp_frame_setter) (QofEntity*, KvpFrame*);
+
+ g_return_if_fail(user_data != NULL);
+ context = (QofEntityCopyData*) user_data;
+ cm_date.tv_nsec = 0;
+ cm_date.tv_sec = 0;
+ importEnt = context->from;
+ targetEnt = context->to;
+ registered_type = FALSE;
+ cm_param = (QofParam*) data;
+ g_return_if_fail(cm_param != NULL);
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_STRING) == 0) {
+ cm_string = g_strdup((gchar*)cm_param->param_getfcn(importEnt, cm_param));
+ string_setter = (void(*)(QofEntity*, const char*))cm_param->param_setfcn;
+ if(string_setter != NULL) { string_setter(targetEnt, cm_string); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_DATE) == 0) {
+ date_getter = (Timespec (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+ cm_date = date_getter(importEnt, cm_param);
+ date_setter = (void(*)(QofEntity*, Timespec))cm_param->param_setfcn;
+ if(date_setter != NULL) { date_setter(targetEnt, cm_date); }
+ registered_type = TRUE;
+ }
+ if((safe_strcmp(cm_param->param_type, QOF_TYPE_NUMERIC) == 0) ||
+ (safe_strcmp(cm_param->param_type, QOF_TYPE_DEBCRED) == 0)) {
+ numeric_getter = (gnc_numeric (*)(QofEntity*, QofParam*))cm_param->param_getfcn;
+ cm_numeric = numeric_getter(importEnt, cm_param);
+ numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_param->param_setfcn;
+ if(numeric_setter != NULL) { numeric_setter(targetEnt, cm_numeric); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_GUID) == 0) {
+ cm_guid = (const GUID*)cm_param->param_getfcn(importEnt, cm_param);
+ guid_setter = (void(*)(QofEntity*, const GUID*))cm_param->param_setfcn;
+ if(guid_setter != NULL) { guid_setter(targetEnt, cm_guid); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_INT32) == 0) {
+ int32_getter = (gint32 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_i32 = int32_getter(importEnt, cm_param);
+ i32_setter = (void(*)(QofEntity*, gint32))cm_param->param_setfcn;
+ if(i32_setter != NULL) { i32_setter(targetEnt, cm_i32); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_INT64) == 0) {
+ int64_getter = (gint64 (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_i64 = int64_getter(importEnt, cm_param);
+ i64_setter = (void(*)(QofEntity*, gint64))cm_param->param_setfcn;
+ if(i64_setter != NULL) { i64_setter(targetEnt, cm_i64); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_DOUBLE) == 0) {
+ double_getter = (double (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_double = double_getter(importEnt, cm_param);
+ double_setter = (void(*)(QofEntity*, double))cm_param->param_setfcn;
+ if(double_setter != NULL) { double_setter(targetEnt, cm_double); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_BOOLEAN) == 0){
+ boolean_getter = (gboolean (*)(QofEntity*, QofParam*)) cm_param->param_getfcn;
+ cm_boolean = boolean_getter(importEnt, cm_param);
+ boolean_setter = (void(*)(QofEntity*, gboolean))cm_param->param_setfcn;
+ if(boolean_setter != NULL) { boolean_setter(targetEnt, cm_boolean); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_KVP) == 0) {
+ cm_kvp = kvp_frame_copy((KvpFrame*)cm_param->param_getfcn(importEnt,cm_param));
+ kvp_frame_setter = (void(*)(QofEntity*, KvpFrame*))cm_param->param_setfcn;
+ if(kvp_frame_setter != NULL) { kvp_frame_setter(targetEnt, cm_kvp); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_CHAR) == 0) {
+ cm_char = (gchar*)cm_param->param_getfcn(importEnt,cm_param);
+ char_setter = (void(*)(QofEntity*, char*))cm_param->param_setfcn;
+ if(char_setter != NULL) { char_setter(targetEnt, cm_char); }
+ registered_type = TRUE;
+ }
+ if(registered_type == FALSE) {
+ referenceEnt = (QofEntity*)cm_param->param_getfcn(importEnt, cm_param);
+ if(!referenceEnt || !referenceEnt->e_type) { return; }
+ reference = qof_entity_get_reference_from(importEnt, cm_param);
+ if(reference) {
+ qof_session_update_reference_list(context->new_session, reference);
+ }
+ }
+}
+
+static gboolean
+qof_entity_guid_match(QofSession *new_session, QofEntity *original)
+{
+ QofEntity *copy;
+ const GUID *g;
+ QofIdTypeConst type;
+ QofBook *targetBook;
+ QofCollection *coll;
+
+ copy = NULL;
+ g_return_val_if_fail(original != NULL, FALSE);
+ targetBook = qof_session_get_book(new_session);
+ g_return_val_if_fail(targetBook != NULL, FALSE);
+ g = qof_entity_get_guid(original);
+ type = g_strdup(original->e_type);
+ coll = qof_book_get_collection(targetBook, type);
+ copy = qof_collection_lookup_entity(coll, g);
+ if(copy) { return TRUE; }
+ return FALSE;
+}
+
+static void
+qof_entity_list_foreach(gpointer data, gpointer user_data)
+{
+ QofEntityCopyData *qecd;
+ QofEntity *original;
+ QofInstance *inst;
+ QofBook *book;
+ const GUID *g;
+
+ g_return_if_fail(data != NULL);
+ original = (QofEntity*)data;
+ g_return_if_fail(user_data != NULL);
+ qecd = (QofEntityCopyData*)user_data;
+ if(qof_entity_guid_match(qecd->new_session, original)) { return; }
+ qecd->from = original;
+ book = qof_session_get_book(qecd->new_session);
+ inst = (QofInstance*)qof_object_new_instance(original->e_type, book);
+ qecd->to = &inst->entity;
+ g = qof_entity_get_guid(original);
+ qof_entity_set_guid(qecd->to, g);
+ if(qecd->param_list != NULL) {
+ g_slist_free(qecd->param_list);
+ qecd->param_list = NULL;
+ }
+ qof_class_param_foreach(original->e_type, qof_entity_param_cb, qecd);
+ qof_begin_edit(inst);
+ g_slist_foreach(qecd->param_list, qof_entity_foreach_copy, qecd);
+ qof_commit_edit(inst);
+}
+
+static void
+qof_entity_coll_foreach(QofEntity *original, gpointer user_data)
+{
+ QofEntityCopyData *qecd;
+ const GUID *g;
+ QofBook *targetBook;
+ QofCollection *coll;
+ QofEntity *copy;
+
+ g_return_if_fail(user_data != NULL);
+ copy = NULL;
+ qecd = (QofEntityCopyData*)user_data;
+ targetBook = qof_session_get_book(qecd->new_session);
+ g = qof_entity_get_guid(original);
+ coll = qof_book_get_collection(targetBook, original->e_type);
+ copy = qof_collection_lookup_entity(coll, g);
+ if(copy) { qecd->error = TRUE; }
+}
+
+static void
+qof_entity_coll_copy(QofEntity *original, gpointer user_data)
+{
+ QofEntityCopyData *qecd;
+ QofBook *book;
+ QofInstance *inst;
+ const GUID *g;
+
+ g_return_if_fail(user_data != NULL);
+ qecd = (QofEntityCopyData*)user_data;
+ book = qof_session_get_book(qecd->new_session);
+ inst = (QofInstance*)qof_object_new_instance(original->e_type, book);
+ qecd->to = &inst->entity;
+ qecd->from = original;
+ g = qof_entity_get_guid(original);
+ qof_entity_set_guid(qecd->to, g);
+ qof_begin_edit(inst);
+ g_slist_foreach(qecd->param_list, qof_entity_foreach_copy, qecd);
+ qof_commit_edit(inst);
+}
+
+gboolean qof_entity_copy_to_session(QofSession* new_session, QofEntity* original)
+{
+ QofEntityCopyData qecd;
+ QofInstance *inst;
+ QofBook *book;
+
+ if(!new_session || !original) { return FALSE; }
+ if(qof_entity_guid_match(new_session, original)) { return FALSE; }
+ gnc_engine_suspend_events();
+ qecd.param_list = NULL;
+ book = qof_session_get_book(new_session);
+ qecd.new_session = new_session;
+ qof_book_set_partial(book);
+ inst = (QofInstance*)qof_object_new_instance(original->e_type, book);
+ qecd.to = &inst->entity;
+ qecd.from = original;
+ qof_entity_set_guid(qecd.to, qof_entity_get_guid(original));
+ qof_begin_edit(inst);
+ qof_class_param_foreach(original->e_type, qof_entity_param_cb, &qecd);
+ qof_commit_edit(inst);
+ if(g_slist_length(qecd.param_list) == 0) { return FALSE; }
+ g_slist_foreach(qecd.param_list, qof_entity_foreach_copy, &qecd);
+ g_slist_free(qecd.param_list);
+ gnc_engine_resume_events();
+ return TRUE;
+}
+
+gboolean qof_entity_copy_list(QofSession *new_session, GList *entity_list)
+{
+ QofEntityCopyData *qecd;
+
+ if(!new_session || !entity_list) { return FALSE; }
+ ENTER (" list=%d", g_list_length(entity_list));
+ qecd = g_new0(QofEntityCopyData, 1);
+ gnc_engine_suspend_events();
+ qecd->param_list = NULL;
+ qecd->new_session = new_session;
+ qof_book_set_partial(qof_session_get_book(new_session));
+ g_list_foreach(entity_list, qof_entity_list_foreach, qecd);
+ gnc_engine_resume_events();
+ g_free(qecd);
+ LEAVE (" ");
+ return TRUE;
+}
+
+gboolean qof_entity_copy_coll(QofSession *new_session, QofCollection *entity_coll)
+{
+ QofEntityCopyData qecd;
+
+ gnc_engine_suspend_events();
+ qecd.param_list = NULL;
+ qecd.new_session = new_session;
+ qof_book_set_partial(qof_session_get_book(qecd.new_session));
+ qof_collection_foreach(entity_coll, qof_entity_coll_foreach, &qecd);
+ qof_class_param_foreach(qof_collection_get_type(entity_coll), qof_entity_param_cb, &qecd);
+ qof_collection_foreach(entity_coll, qof_entity_coll_copy, &qecd);
+ if(qecd.param_list != NULL) { g_slist_free(qecd.param_list); }
+ gnc_engine_resume_events();
+ return TRUE;
+}
+
+struct recurse_s
+{
+ QofSession *session;
+ gboolean success;
+ GList *ref_list;
+ GList *ent_list;
+};
+
+static void
+recurse_collection_cb (QofEntity *ent, gpointer user_data)
+{
+ struct recurse_s *store;
+
+ if(user_data == NULL) { return; }
+ store = (struct recurse_s*)user_data;
+ if(!ent || !store) { return; }
+ store->success = qof_entity_copy_to_session(store->session, ent);
+ if(store->success) {
+ store->ent_list = g_list_append(store->ent_list, ent);
+ }
+}
+
+static void
+recurse_ent_cb(QofEntity *ent, gpointer user_data)
+{
+ GList *ref_list, *i, *j, *ent_list, *child_list;
+ QofParam *ref_param;
+ QofEntity *ref_ent, *child_ent;
+ QofSession *session;
+ struct recurse_s *store;
+ gboolean success;
+
+ if(user_data == NULL) { return; }
+ store = (struct recurse_s*)user_data;
+ session = store->session;
+ success = store->success;
+ ref_list = NULL;
+ child_ent = NULL;
+ ref_list = g_list_copy(store->ref_list);
+ if((!session)||(!ent)) { return; }
+ ent_list = NULL;
+ child_list = NULL;
+ i = NULL;
+ j = NULL;
+ for(i = ref_list; i != NULL; i=i->next)
+ {
+ if(i->data == NULL) { continue; }
+ ref_param = (QofParam*)i->data;
+ if(ref_param->param_name == NULL) { continue; }
+ if(ref_param->param_type == QOF_TYPE_COLLECT) {
+ QofCollection *col;
+ col = ref_param->param_getfcn(ent, ref_param);
+ qof_collection_foreach(col, recurse_collection_cb, store);
+ continue;
+ }
+ ref_ent = (QofEntity*)ref_param->param_getfcn(ent, ref_param);
+ if((ref_ent)&&(ref_ent->e_type))
+ {
+ store->success = qof_entity_copy_to_session(session, ref_ent);
+ if(store->success) { ent_list = g_list_append(ent_list, ref_ent); }
+ }
+ }
+ for(i = ent_list; i != NULL; i = i->next)
+ {
+ if(i->data == NULL) { continue; }
+ child_ent = (QofEntity*)i->data;
+ if(child_ent == NULL) { continue; }
+ ref_list = qof_class_get_referenceList(child_ent->e_type);
+ for(j = ref_list; j != NULL; j = j->next)
+ {
+ if(j->data == NULL) { continue; }
+ ref_param = (QofParam*)j->data;
+ ref_ent = ref_param->param_getfcn(child_ent, ref_param);
+ if(ref_ent != NULL)
+ {
+ success = qof_entity_copy_to_session(session, ref_ent);
+ if(success) { child_list = g_list_append(child_list, ref_ent); }
+ }
+ }
+ }
+ for(i = child_list; i != NULL; i = i->next)
+ {
+ if(i->data == NULL) { continue; }
+ ref_ent = (QofEntity*)i->data;
+ if(ref_ent == NULL) { continue; }
+ ref_list = qof_class_get_referenceList(ref_ent->e_type);
+ for(j = ref_list; j != NULL; j = j->next)
+ {
+ if(j->data == NULL) { continue; }
+ ref_param = (QofParam*)j->data;
+ child_ent = ref_param->param_getfcn(ref_ent, ref_param);
+ if(child_ent != NULL)
+ {
+ qof_entity_copy_to_session(session, child_ent);
+ }
+ }
+ }
+}
+
+gboolean
+qof_entity_copy_coll_r(QofSession *new_session, QofCollection *coll)
+{
+ struct recurse_s store;
+ gboolean success;
+
+ if((!new_session)||(!coll)) { return FALSE; }
+ store.session = new_session;
+ success = TRUE;
+ store.success = success;
+ store.ent_list = NULL;
+ store.ref_list = qof_class_get_referenceList(qof_collection_get_type(coll));
+ success = qof_entity_copy_coll(new_session, coll);
+ if(success){ qof_collection_foreach(coll, recurse_ent_cb, &store); }
+ return success;
+}
+
+gboolean qof_entity_copy_one_r(QofSession *new_session, QofEntity *ent)
+{
+ struct recurse_s store;
+ QofCollection *coll;
+ gboolean success;
+
+ if((!new_session)||(!ent)) { return FALSE; }
+ store.session = new_session;
+ success = TRUE;
+ store.success = success;
+ store.ref_list = qof_class_get_referenceList(ent->e_type);
+ success = qof_entity_copy_to_session(new_session, ent);
+ if(success == TRUE) {
+ coll = qof_book_get_collection(qof_session_get_book(new_session), ent->e_type);
+ qof_collection_foreach(coll, recurse_ent_cb, &store);
+ }
+ return success;
+}
+
+
+/* ====================================================================== */
+
+/* Programs that use their own backends also need to call
+the default QOF ones. The backends specified here are
+loaded only by applications that do not have their own. */
+struct backend_providers
+{
+ const char *libdir;
+ const char *filename;
+ const char *init_fcn;
+};
+
+/* All available QOF backends need to be described here
+and the last entry must be three NULL's.
+Remember: Use the libdir from the current build environment
+and use the .la NOT the .so - .so is not portable! */
+struct backend_providers backend_list[] = {
+ { QOF_LIB_DIR, "libqof-backend-qsf.la", "qsf_provider_init" },
+#ifdef HAVE_DWI
+ { QOF_LIB_DIR, "libqof_backend_dwi.la", "dwiend_provider_init" },
+#endif
+ { NULL, NULL, NULL }
+};
+
+static void
+qof_session_load_backend(QofSession * session, char * access_method)
+{
+ GSList *p;
+ GList *node;
+ QofBackendProvider *prov;
+ QofBook *book;
+ char *msg;
+ gint num;
+ gboolean prov_type;
+ gboolean (*type_check) (const char*);
+
+ ENTER (" list=%d", g_slist_length(provider_list));
+ prov_type = FALSE;
+ if (NULL == provider_list)
+ {
+ for (num = 0; backend_list[num].filename != NULL; num++) {
+ if(!qof_load_backend_library(backend_list[num].libdir,
+ backend_list[num].filename, backend_list[num].init_fcn))
+ {
+ PWARN (" failed to load %s from %s using %s",
+ backend_list[num].filename, backend_list[num].libdir,
+ backend_list[num].init_fcn);
+ }
+ }
+ }
+ p = g_slist_copy(provider_list);
+ while(p != NULL)
+ {
+ prov = p->data;
+ /* Does this provider handle the desired access method? */
+ if (0 == strcasecmp (access_method, prov->access_method))
+ {
+ /* More than one backend could provide this
+ access method, check file type compatibility. */
+ type_check = (gboolean (*)(const char*)) prov->check_data_type;
+ prov_type = (type_check)(session->book_id);
+ if(!prov_type)
+ {
+ PINFO(" %s not usable", prov->provider_name);
+ p = p->next;
+ continue;
+ }
+ PINFO (" selected %s", prov->provider_name);
+ if (NULL == prov->backend_new)
+ {
+ p = p->next;
+ continue;
+ }
+ /* Use the providers creation callback */
+ session->backend = (*(prov->backend_new))();
+ session->backend->provider = prov;
+ /* Tell the books about the backend that they'll be using. */
+ for (node=session->books; node; node=node->next)
+ {
+ book = node->data;
+ qof_book_set_backend (book, session->backend);
+ }
+ LEAVE (" ");
+ return;
+ }
+ p = p->next;
+ }
+ msg = g_strdup_printf("failed to load '%s' using access_method", access_method);
+ qof_session_push_error (session, ERR_BACKEND_NO_HANDLER, msg);
+ LEAVE (" ");
+}
+
+/* ====================================================================== */
+
+static void
+qof_session_destroy_backend (QofSession *session)
+{
+ g_return_if_fail (session);
+
+ if (session->backend)
+ {
+ /* clear any error message */
+ char * msg = qof_backend_get_message (session->backend);
+ g_free (msg);
+
+ /* Then destroy the backend */
+ if (session->backend->destroy_backend)
+ {
+ session->backend->destroy_backend(session->backend);
+ }
+ else
+ {
+ g_free(session->backend);
+ }
+ }
+
+ session->backend = NULL;
+}
+
+void
+qof_session_begin (QofSession *session, const char * book_id,
+ gboolean ignore_lock, gboolean create_if_nonexistent)
+{
+ char *p, *access_method, *msg;
+ int err;
+
+ if (!session) return;
+
+ ENTER (" sess=%p ignore_lock=%d, book-id=%s",
+ session, ignore_lock,
+ book_id ? book_id : "(null)");
+
+ /* Clear the error condition of previous errors */
+ qof_session_clear_error (session);
+
+ /* Check to see if this session is already open */
+ if (session->book_id)
+ {
+ qof_session_push_error (session, ERR_BACKEND_LOCKED, NULL);
+ LEAVE("push error book is already open ");
+ return;
+ }
+
+ /* seriously invalid */
+ if (!book_id)
+ {
+ qof_session_push_error (session, ERR_BACKEND_BAD_URL, NULL);
+ LEAVE("push error missing book_id");
+ return;
+ }
+
+ /* Store the session URL */
+ session->book_id = g_strdup (book_id);
+
+ /* destroy the old backend */
+ qof_session_destroy_backend(session);
+
+ /* Look for something of the form of "file:/", "http://" or
+ * "postgres://". Everything before the colon is the access
+ * method. Load the first backend found for that access method.
+ */
+ p = strchr (book_id, ':');
+ if (p)
+ {
+ access_method = g_strdup (book_id);
+ p = strchr (access_method, ':');
+ *p = 0;
+ qof_session_load_backend(session, access_method);
+ g_free (access_method);
+ }
+ else
+ {
+ /* If no colon found, assume it must be a file-path */
+ qof_session_load_backend(session, "file");
+ }
+
+ /* No backend was found. That's bad. */
+ if (NULL == session->backend)
+ {
+ qof_session_push_error (session, ERR_BACKEND_BAD_URL, NULL);
+ LEAVE (" BAD: no backend: sess=%p book-id=%s",
+ session, book_id ? book_id : "(null)");
+ return;
+ }
+
+ /* If there's a begin method, call that. */
+ if (session->backend->session_begin)
+ {
+
+ (session->backend->session_begin)(session->backend, session,
+ session->book_id, ignore_lock,
+ create_if_nonexistent);
+ PINFO("Done running session_begin on backend");
+ err = qof_backend_get_error(session->backend);
+ msg = qof_backend_get_message(session->backend);
+ if (err != ERR_BACKEND_NO_ERR)
+ {
+ g_free(session->book_id);
+ session->book_id = NULL;
+ qof_session_push_error (session, err, msg);
+ LEAVE(" backend error %d %s", err, msg);
+ return;
+ }
+ if (msg != NULL)
+ {
+ PWARN("%s", msg);
+ g_free(msg);
+ }
+ }
+
+ LEAVE (" sess=%p book-id=%s",
+ session, book_id ? book_id : "(null)");
+}
+
+/* ====================================================================== */
+
+void
+qof_session_load (QofSession *session,
+ QofPercentageFunc percentage_func)
+{
+ QofBook *newbook, *ob;
+ QofBookList *oldbooks, *node;
+ QofBackend *be;
+ QofBackendError err;
+
+ if (!session) return;
+ if (!session->book_id) return;
+
+ ENTER ("sess=%p book_id=%s", session, session->book_id
+ ? session->book_id : "(null)");
+
+ /* At this point, we should are supposed to have a valid book
+ * id and a lock on the file. */
+
+ oldbooks = session->books;
+
+ /* XXX why are we creating a book here? I think the books
+ * need to be handled by the backend ... especially since
+ * the backend may need to load multiple books ... XXX. FIXME.
+ */
+ newbook = qof_book_new();
+ session->books = g_list_append (NULL, newbook);
+ PINFO ("new book=%p", newbook);
+
+ qof_session_clear_error (session);
+
+ /* This code should be sufficient to initialize *any* backend,
+ * whether http, postgres, or anything else that might come along.
+ * Basically, the idea is that by now, a backend has already been
+ * created & set up. At this point, we only need to get the
+ * top-level account group out of the backend, and that is a
+ * generic, backend-independent operation.
+ */
+ be = session->backend;
+ qof_book_set_backend(newbook, be);
+
+ /* Starting the session should result in a bunch of accounts
+ * and currencies being downloaded, but probably no transactions;
+ * The GUI will need to do a query for that.
+ */
+ if (be)
+ {
+ be->percentage = percentage_func;
+
+ if (be->load)
+ {
+ be->load (be, newbook);
+ qof_session_push_error (session, qof_backend_get_error(be), NULL);
+ }
+ }
+
+ /* XXX if the load fails, then we try to restore the old set of books;
+ * however, we don't undo the session id (the URL). Thus if the
+ * user attempts to save after a failed load, they weill be trying to
+ * save to some bogus URL. This is wrong. XXX FIXME.
+ */
+ err = qof_session_get_error(session);
+ if ((err != ERR_BACKEND_NO_ERR) &&
+ (err != ERR_FILEIO_FILE_TOO_OLD) &&
+ (err != ERR_SQL_DB_TOO_OLD))
+ {
+ /* Something broke, put back the old stuff */
+ qof_book_set_backend (newbook, NULL);
+ qof_book_destroy (newbook);
+ g_list_free (session->books);
+ session->books = oldbooks;
+ LEAVE("error from backend %d", qof_session_get_error(session));
+ return;
+ }
+
+ for (node=oldbooks; node; node=node->next)
+ {
+ ob = node->data;
+ qof_book_set_backend (ob, NULL);
+ qof_book_destroy (ob);
+ }
+
+ LEAVE ("sess = %p, book_id=%s", session, session->book_id
+ ? session->book_id : "(null)");
+}
+
+/* ====================================================================== */
+
+gboolean
+qof_session_save_may_clobber_data (QofSession *session)
+{
+ if (!session) return FALSE;
+ if (!session->backend) return FALSE;
+ if (!session->backend->save_may_clobber_data) return FALSE;
+
+ return (*(session->backend->save_may_clobber_data)) (session->backend);
+}
+
+static gboolean
+save_error_handler(QofBackend *be, QofSession *session)
+{
+ int err;
+ err = qof_backend_get_error(be);
+
+ if (ERR_BACKEND_NO_ERR != err)
+ {
+ qof_session_push_error (session, err, NULL);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void
+qof_session_save (QofSession *session,
+ QofPercentageFunc percentage_func)
+{
+ GList *node;
+ QofBackend *be;
+ gboolean partial, change_backend;
+ QofBackendProvider *prov;
+ GSList *p;
+ QofBook *book, *abook;
+ int err;
+ gint num;
+ char *msg, *book_id;
+
+ if (!session) return;
+ ENTER ("sess=%p book_id=%s",
+ session, session->book_id ? session->book_id : "(null)");
+ /* Partial book handling. */
+ book = qof_session_get_book(session);
+ partial = (gboolean)GPOINTER_TO_INT(qof_book_get_data(book, PARTIAL_QOFBOOK));
+ change_backend = FALSE;
+ msg = g_strdup_printf(" ");
+ book_id = g_strdup(session->book_id);
+ if(partial == TRUE)
+ {
+ if(session->backend && session->backend->provider) {
+ prov = session->backend->provider;
+ if(TRUE == prov->partial_book_supported)
+ {
+ /* if current backend supports partial, leave alone. */
+ change_backend = FALSE;
+ }
+ else { change_backend = TRUE; }
+ }
+ /* If provider is undefined, assume partial not supported. */
+ else { change_backend = TRUE; }
+ }
+ if(change_backend == TRUE)
+ {
+ qof_session_destroy_backend(session);
+ if (NULL == provider_list)
+ {
+ for (num = 0; backend_list[num].filename != NULL; num++) {
+ qof_load_backend_library(backend_list[num].libdir,
+ backend_list[num].filename, backend_list[num].init_fcn);
+ }
+ }
+ p = g_slist_copy(provider_list);
+ while(p != NULL)
+ {
+ prov = p->data;
+ if(TRUE == prov->partial_book_supported)
+ {
+ /** \todo check the access_method too, not in scope here, yet. */
+ /* if((TRUE == prov->partial_book_supported) &&
+ (0 == strcasecmp (access_method, prov->access_method)))
+ {*/
+ if (NULL == prov->backend_new) continue;
+ /* Use the providers creation callback */
+ session->backend = (*(prov->backend_new))();
+ session->backend->provider = prov;
+ if (session->backend->session_begin)
+ {
+ /* Call begin - backend has been changed,
+ so make sure a file can be written,
+ use ignore_lock and create_if_nonexistent */
+ g_free(session->book_id);
+ session->book_id = NULL;
+ (session->backend->session_begin)(session->backend, session,
+ book_id, TRUE, TRUE);
+ PINFO("Done running session_begin on changed backend");
+ err = qof_backend_get_error(session->backend);
+ msg = qof_backend_get_message(session->backend);
+ if (err != ERR_BACKEND_NO_ERR)
+ {
+ g_free(session->book_id);
+ session->book_id = NULL;
+ qof_session_push_error (session, err, msg);
+ LEAVE("changed backend error %d", err);
+ return;
+ }
+ if (msg != NULL)
+ {
+ PWARN("%s", msg);
+ g_free(msg);
+ }
+ }
+ /* Tell the books about the backend that they'll be using. */
+ for (node=session->books; node; node=node->next)
+ {
+ book = node->data;
+ qof_book_set_backend (book, session->backend);
+ }
+ p = NULL;
+ }
+ if(p) {
+ p = p->next;
+ }
+ }
+ if(!session->backend)
+ {
+ msg = g_strdup_printf("failed to load backend");
+ qof_session_push_error(session, ERR_BACKEND_NO_HANDLER, msg);
+ return;
+ }
+ }
+ /* If there is a backend, and the backend is reachable
+ * (i.e. we can communicate with it), then synchronize with
+ * the backend. If we cannot contact the backend (e.g.
+ * because we've gone offline, the network has crashed, etc.)
+ * then give the user the option to save to the local disk.
+ *
+ * hack alert -- FIXME -- XXX the code below no longer
+ * does what the words above say. This needs fixing.
+ */
+ be = session->backend;
+ if (be)
+ {
+ for (node = session->books; node; node=node->next)
+ {
+ abook = node->data;
+ /* if invoked as SaveAs(), then backend not yet set */
+ qof_book_set_backend (abook, be);
+ be->percentage = percentage_func;
+ if (be->sync)
+ {
+ (be->sync)(be, abook);
+ if (save_error_handler(be, session)) return;
+ }
+ }
+ /* If we got to here, then the backend saved everything
+ * just fine, and we are done. So return. */
+ /* Return the book_id to previous value. */
+ qof_session_clear_error (session);
+ LEAVE("Success");
+ return;
+ }
+ else
+ {
+ msg = g_strdup_printf("failed to load backend");
+ qof_session_push_error(session, ERR_BACKEND_NO_HANDLER, msg);
+ }
+ LEAVE("error -- No backend!");
+}
+
+/* ====================================================================== */
+/* XXX This exports the list of accounts to a file. It does not export
+ * any transactions. Its a place-holder until full book-closing is implemented.
+ */
+
+#ifdef GNUCASH_MAJOR_VERSION
+
+gboolean
+qof_session_export (QofSession *tmp_session,
+ QofSession *real_session,
+ QofPercentageFunc percentage_func)
+{
+ QofBook *book;
+ QofBackend *be;
+
+ if ((!tmp_session) || (!real_session)) return FALSE;
+
+ book = qof_session_get_book (real_session);
+ ENTER ("tmp_session=%p real_session=%p book=%p book_id=%s",
+ tmp_session, real_session, book,
+ tmp_session -> book_id
+ ? tmp_session->book_id : "(null)");
+
+ /* There must be a backend or else. (It should always be the file
+ * backend too.)
+ */
+ be = tmp_session->backend;
+ if (!be)
+ return FALSE;
+
+ be->percentage = percentage_func;
+ if (be->export)
+ {
+
+ (be->export)(be, book);
+ if (save_error_handler(be, tmp_session)) return FALSE;
+ }
+
+ return TRUE;
+}
+#endif /* GNUCASH_MAJOR_VERSION */
+
+/* ====================================================================== */
+
+void
+qof_session_end (QofSession *session)
+{
+ if (!session) return;
+
+ ENTER ("sess=%p book_id=%s", session, session->book_id
+ ? session->book_id : "(null)");
+
+ /* close down the backend first */
+ if (session->backend && session->backend->session_end)
+ {
+ (session->backend->session_end)(session->backend);
+ }
+
+ qof_session_clear_error (session);
+
+ g_free (session->book_id);
+ session->book_id = NULL;
+
+ LEAVE ("sess=%p book_id=%s", session, session->book_id
+ ? session->book_id : "(null)");
+}
+
+void
+qof_session_destroy (QofSession *session)
+{
+ GList *node;
+ if (!session) return;
+
+ ENTER ("sess=%p book_id=%s", session, session->book_id
+ ? session->book_id : "(null)");
+
+ qof_session_end (session);
+
+ /* destroy the backend */
+ qof_session_destroy_backend(session);
+
+ for (node=session->books; node; node=node->next)
+ {
+ QofBook *book = node->data;
+ qof_book_set_backend (book, NULL);
+ qof_book_destroy (book);
+ }
+
+ session->books = NULL;
+ if (session == current_session)
+ current_session = NULL;
+
+ g_free (session);
+
+ LEAVE ("sess=%p", session);
+}
+
+/* ====================================================================== */
+/* this call is weird. */
+
+void
+qof_session_swap_data (QofSession *session_1, QofSession *session_2)
+{
+ GList *books_1, *books_2, *node;
+
+ if (session_1 == session_2) return;
+ if (!session_1 || !session_2) return;
+
+ ENTER ("sess1=%p sess2=%p", session_1, session_2);
+
+ books_1 = session_1->books;
+ books_2 = session_2->books;
+
+ session_1->books = books_2;
+ session_2->books = books_1;
+
+ for (node=books_1; node; node=node->next)
+ {
+ QofBook *book_1 = node->data;
+ qof_book_set_backend (book_1, session_2->backend);
+ }
+ for (node=books_2; node; node=node->next)
+ {
+ QofBook *book_2 = node->data;
+ qof_book_set_backend (book_2, session_1->backend);
+ }
+
+ LEAVE (" ");
+}
+
+/* ====================================================================== */
+
+gboolean
+qof_session_events_pending (QofSession *session)
+{
+ if (!session) return FALSE;
+ if (!session->backend) return FALSE;
+ if (!session->backend->events_pending) return FALSE;
+
+ return session->backend->events_pending (session->backend);
+}
+
+gboolean
+qof_session_process_events (QofSession *session)
+{
+ if (!session) return FALSE;
+ if (!session->backend) return FALSE;
+ if (!session->backend->process_events) return FALSE;
+
+ return session->backend->process_events (session->backend);
+}
+
+/* ====================================================================== */
+
+#ifdef GNUCASH_MAJOR_VERSION
+
+/* this should go in a separate binary to create a rpc server */
+
+void
+gnc_run_rpc_server (void)
+{
+ const char * dll_err;
+ void * dll_handle;
+ int (*rpc_run)(short);
+ int ret;
+
+ /* open and resolve all symbols now (we don't want mystery
+ * failure later) */
+#ifndef RTLD_NOW
+# ifdef RTLD_LAZY
+# define RTLD_NOW RTLD_LAZY
+# endif
+#endif
+ dll_handle = dlopen ("libgnc_rpc.so", RTLD_NOW);
+ if (! dll_handle)
+ {
+ dll_err = dlerror();
+ PWARN (" can't load library: %s\n", dll_err ? dll_err : "");
+ return;
+ }
+
+ rpc_run = dlsym (dll_handle, "rpc_server_run");
+ dll_err = dlerror();
+ if (dll_err)
+ {
+ dll_err = dlerror();
+ PWARN (" can't find symbol: %s\n", dll_err ? dll_err : "");
+ return;
+ }
+
+ ret = (*rpc_run)(0);
+
+ /* XXX How do we force an exit? */
+}
+#endif /* GNUCASH_MAJOR_VERSION */
+
+/* =================== END OF FILE ====================================== */
Added: gnucash/trunk/lib/libqof/qof/qofsession.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofsession.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofsession.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,558 @@
+/********************************************************************\
+ * qofsession.h -- session access (connection to backend) *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+/** @addtogroup Backend
+ *
+ * The QOF Session
+ * encapsulates a connection to a storage backend. That is, it
+ * manages the connection to a persistant data store; whereas
+ * the backend is the thing that performs the actual datastore
+ * access.
+ *
+ * This class provides several important services:
+ *
+ * 1) It resolves and loads the appropriate backend, based on
+ * the URL.
+ *
+ * 2) It reports backend errors (e.g. network errors, storage
+ * corruption errors) through a single, backend-independent
+ * API.
+ *
+ * 3) It reports non-error events received from the backend.
+ *
+ * 4) It helps manage global dataset locks. For example, for the
+ * file backend, the lock prevents multiple users from editing
+ * the same file at the same time, thus avoiding lost data due
+ * to race conditions. Thus, an open session implies that the
+ * associated file is locked.
+ *
+ * 5) Misc utilities, such as a search path for the file to be
+ * edited, and/or other URL resolution utilities. This should
+ * simplify install & maintenance problems for naive users who
+ * may not have a good grasp on what a file system is, or where
+ * they want to keep their data files.
+ *
+ * 6) In the future, this class is probably a good place to manage
+ * a portion of the user authentication process, and hold user
+ * credentials/cookies/keys/tokens. This is because at the
+ * coarsest level, authorization can happen at the datastore
+ * level: i.e. does this user even have the authority to connect
+ * to and open this datastore?
+ *
+ * A brief note about books & sessions:
+ * A book encapsulates the datasets manipulated by GnuCash. A book
+ * holds the actual data. By contrast, the session mediates the
+ * connection between a book (the thing that lives in virtual memory
+ * in the local process) and the datastore (the place where book
+ * data lives permanently, e.g., file, database).
+ *
+ * In the current design, a session may hold multiple books. For
+ * now, exactly what this means is somewhat vague, and code in
+ * various places makes some implicit assumptions: first, only
+ * one book is 'current' and open for editing. Next, its assumed
+ * that all of the books in a session are related in some way.
+ * i.e. that they are all earlier accounting periods of the
+ * currently open book. In particular, the backends probably
+ * make that assumption, in order to store the different accounting
+ * periods in a clump so that one can be found, given another.
+ *
+
+ The session now calls QofBackendProvider->check_data_type
+ to check that the incoming path contains data that the
+ backend provider can open. The backend provider should
+ also check if it can contact it's storage media (disk,
+ network, server, etc.) and abort if it can't. Malformed
+ file URL's would be handled the same way.
+
+ @{
+ */
+
+/** @file qofsession.h
+ * @brief Encapsulates a connection to a backend (persistent store)
+ * @author Copyright (c) 1998, 1999, 2001, 2002 Linas Vepstas <linas at linas.org>
+ * @author Copyright (c) 2000 Dave Peticolas
+ * @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+ */
+
+#ifndef QOF_SESSION_H
+#define QOF_SESSION_H
+
+#include "qofbackend.h"
+#include "qofbook.h"
+#include "qofclass.h"
+#include "qofobject.h"
+
+#define QOF_MOD_SESSION "qof-session"
+
+/* PROTOTYPES ******************************************************/
+
+typedef struct _QofSession QofSession;
+
+QofSession * qof_session_new (void);
+void qof_session_destroy (QofSession *session);
+QofSession * qof_session_get_current_session (void);
+void qof_session_set_current_session (QofSession *session);
+
+/** The qof_session_swap_data () method swaps the book of
+ * the two given sessions. It is useful
+ * for 'Save As' type functionality. */
+void qof_session_swap_data (QofSession *session_1, QofSession *session_2);
+
+/** The qof_session_begin () method begins a new session.
+ * It takes as an argument the book id. The book id must be a string
+ * in the form of a URI/URL.
+ * In the current implementation, the following URL's are supported
+ * -- File URI of the form
+ * "file:/home/somewhere/somedir/file.xac"
+ * The path part must be a valid path. The file-part must be
+ * a valid old-style-xacc or new-style-gnucash-format file. Paths
+ * may be relative or absolute. If the path is relative; that is,
+ * if the argument is "file:somefile.xac" then a sequence of
+ * search paths are checked for a file of this name.
+ *
+ * -- Postgres URI of the form
+ * "postgres://hostname.com/dbname"
+ * See the src/backend/postgres subdirectory for more info.
+ *
+ * -- RPC URI of the form rpc://hostname.com/rpcserver.
+ *
+ * The 'ignore_lock' argument, if set to TRUE, will cause this routine
+ * to ignore any global-datastore locks (e.g. file locks) that it finds.
+ * If set to FALSE, then file/database-global locks will be tested and
+ * obeyed.
+ *
+ * If the datastore exists, can be reached (e.g over the net),
+ * connected to, opened and read, and a lock can be obtained then
+ * a lock will be obtained. Note that multi-user datastores
+ * (e.g. the SQL backend) typically will not need to get a global
+ * lock, and thus, the user will not be locked out. That's the
+ * whole point of 'multi-user'.
+ *
+ * If the file/database doesn't exist, and the create_if_nonexistent
+ * flag is set to TRUE, then the database is created.
+ *
+ * If an error occurs, it will be pushed onto the session error
+ * stack, and that is where it should be examined.
+ */
+void qof_session_begin (QofSession *session, const char * book_id,
+ gboolean ignore_lock, gboolean create_if_nonexistent);
+
+
+/**
+ * The qof_session_load() method causes the QofBook to be made ready to
+ * to use with this URL/datastore. When the URL points at a file,
+ * then this routine would load the data from the file. With remote
+ * backends, e.g. network or SQL, this would load only enough data
+ * to make the book actually usable; it would not cause *all* of the
+ * data to be loaded.
+ *
+ * XXX the current design tries to accomodate multiple calls to 'load'
+ * for each session, each time wiping out the old books; this seems
+ * wrong to me, and should be restricted to allow only one load per
+ * session.
+ */
+typedef void (*QofPercentageFunc) (const char *message, double percent);
+void qof_session_load (QofSession *session,
+ QofPercentageFunc percentage_func);
+
+/** @name Session Errors
+ @{ */
+/** The qof_session_get_error() routine can be used to obtain the reason
+ * for any failure. Calling this routine returns the current error.
+ */
+QofBackendError qof_session_get_error (QofSession *session);
+const char * qof_session_get_error_message(QofSession *session);
+
+/**
+ * The qof_session_pop_error() routine can be used to obtain the reason
+ * for any failure. Calling this routine resets the error value.
+ *
+ * This routine allows an implementation of multiple error values,
+ * e.g. in a stack, where this routine pops the top value. The current
+ * implementation has a stack that is one-deep.
+ *
+ * See qofbackend.h for a listing of returned errors.
+ */
+QofBackendError qof_session_pop_error (QofSession *session);
+/** @} */
+
+/** The qof_session_add_book() allows additional books to be added to
+ * a session.
+ * XXX Under construction, clarify the following when done:
+ * XXX There must already be an open book in the session already!?
+ * XXX Only one open book at a time per session is allowed!?
+ * XXX each book gets its own unique backend ???
+ */
+void qof_session_add_book (QofSession *session, QofBook *book);
+
+QofBook * qof_session_get_book (QofSession *session);
+
+/**
+ * The qof_session_get_file_path() routine returns the fully-qualified file
+ * path for the session. That is, if a relative or partial filename
+ * was for the session, then it had to have been fully resolved to
+ * open the session. This routine returns the result of this resolution.
+ * The path is always guaranteed to reside in the local file system,
+ * even if the session itself was opened as a URL. (currently, the
+ * filepath is derived from the url by substituting commas for
+ * slashes).
+ *
+ * The qof_session_get_url() routine returns the url that was opened.
+ * URL's for local files take the form of
+ * file:/some/where/some/file.gml
+ */
+const char * qof_session_get_file_path (QofSession *session);
+
+const char * qof_session_get_url (QofSession *session);
+
+/**
+ * The qof_session_not_saved() subroutine will return TRUE
+ * if any data in the session hasn't been saved to long-term storage.
+ */
+gboolean qof_session_not_saved(QofSession *session);
+
+/** FIXME: This isn't as thorough as we might want it to be... */
+gboolean qof_session_save_may_clobber_data (QofSession *session);
+
+/** The qof_session_save() method will commit all changes that have been
+ * made to the session. For the file backend, this is nothing
+ * more than a write to the file of the current AccountGroup & etc.
+ * For the SQL backend, this is typically a no-op (since all data
+ * has already been written out to the database.
+ */
+void qof_session_save (QofSession *session,
+ QofPercentageFunc percentage_func);
+/**
+ * The qof_session_end() method will release the session lock. For the
+ * file backend, it will *not* save the account group to a file. Thus,
+ * this method acts as an "abort" or "rollback" primitive. However,
+ * for other backends, such as the sql backend, the data would have
+ * been written out before this, and so this routines wouldn't
+ * roll-back anything; it would just shut the connection.
+ */
+void qof_session_end (QofSession *session);
+
+/** @name Copying entities between sessions.
+
+Only certain backends can cope with selective copying of
+entities and only fully defined QOF entities can be copied
+between sessions - see the \ref QSF (QSF) documentation
+(::qsf_write_file) for more information.
+
+The recommended backend for the new session is QSF or a future
+SQL backend. Using any of these entity copy functions sets a
+flag in the backend that this is now a partial QofBook.
+When you save a session containing a partial QofBook,
+the session will check that the backend is able to handle the
+partial book. If not, the backend will be replaced by one that
+can handle partial books, preferably one using the same
+::access_method. Currently, this means that a book
+using the GnuCash XML v2 file backend will be switched to QSF.
+
+Copied entities are identical to the source entity, all parameters
+defined with ::QofAccessFunc and ::QofSetterFunc in QOF are copied
+and the ::GUID of the original ::QofEntity is set in the new entity.
+Sessions containing copied entities are intended for use
+as mechanisms for data export.
+
+It is acceptable to add entities to new_session in batches. Note that
+any of these calls will fail if an entity already exists in new_session
+with the same GUID as any entity to be copied.
+
+To merge a whole QofBook or where there is any possibility
+of collisions or requirement for user intervention,
+see \ref BookMerge
+
+@{
+
+*/
+
+/** \brief Copy a single QofEntity to another session
+
+Checks first that no entity in the session book contains
+the GUID of the source entity.
+
+ @param new_session - the target session
+ @param original - the QofEntity* to copy
+
+ at return FALSE without copying if the session contains an entity
+with the same GUID already, otherwise TRUE.
+*/
+
+gboolean qof_entity_copy_to_session(QofSession* new_session, QofEntity* original);
+
+/** @brief Copy a GList of entities to another session
+
+The QofBook in the new_session must \b not contain any entities
+with the same GUID as any of the source entities - there is
+no support for handling collisions, instead use \ref BookMerge
+
+Note that the GList (e.g. from ::qof_sql_query_run) can contain
+QofEntity pointers of any ::QofIdType, in any sequence. As long
+as all members of the list are ::QofEntity*, and all GUID's are
+unique, the list can be copied.
+
+ @param new_session - the target session
+ @param entity_list - a GList of QofEntity pointers of any type(s).
+
+ at return FALSE, without copying, if new_session contains any entities
+with the same GUID. Otherwise TRUE.
+
+*/
+gboolean qof_entity_copy_list(QofSession *new_session, GList *entity_list);
+
+/** @brief Copy a QofCollection of entities.
+
+The QofBook in the new_session must \b not contain any entities
+with the same GUID as any entities in the collection - there is
+no support for handling collisions - instead, use \ref BookMerge
+
+ at param new_session - the target session
+ at param entity_coll - a QofCollection of any QofIdType.
+
+ at return FALSE, without copying, if new_session contains any entities
+with the same GUID. Otherwise TRUE.
+*/
+
+gboolean qof_entity_copy_coll(QofSession *new_session, QofCollection *entity_coll);
+
+/** \brief Recursively copy a collection of entities to a session.
+
+\note This function creates a <b>partial QofBook</b>. See
+::qof_entity_copy_to_session for more information.
+
+The QofBook in the new_session must \b not contain any entities
+with the same GUID as any entities to be copied - there is
+no support for handling collisions - instead, use \ref BookMerge
+
+Objects can be defined solely in terms of QOF data types or
+as a mix of data types and other objects, which may in turn
+include other objects. These references can be copied recursively
+down to the third level. e.g. ::GncInvoice refers to ::GncOwner which
+refers to ::GncCustomer which refers to ::GncAddress. See
+::QofEntityReference.
+
+\note This is a deep recursive copy - every referenced entity is copied
+to the new session, including all parameters. The starting point is all
+entities in the top level collection. It can take some time.
+
+ at param coll A QofCollection of entities that may or may not have
+references.
+
+ at param new_session The QofSession to receive the copied entities.
+
+ at return TRUE on success; if any individual copy fails, returns FALSE.
+<b>Note</b> : Some entities may have been copied successfully even if
+one of the references fails to copy.
+
+*/
+gboolean
+qof_entity_copy_coll_r(QofSession *new_session, QofCollection *coll);
+
+/** \brief Recursively copy a single entity to a new session.
+
+Copy the single entity and all referenced entities to the second level.
+
+Only entities that are directly referenced by the top level entity are
+copied.
+
+This is a deep copy - all parameters of all referenced entities are copied. If
+the top level entity has no references, this is identical to
+::qof_entity_copy_to_session.
+
+ at param ent A single entity that may or may not have references.
+
+ at param new_session The QofSession to receive the copied entities.
+
+ at return TRUE on success; if any individual copy fails, returns FALSE.
+<b>Note</b> : Some entities may have been copied successfully even if
+one of the references fails to copy.
+*/
+gboolean
+qof_entity_copy_one_r(QofSession *new_session, QofEntity *ent);
+
+/** @}
+*/
+
+/** @name Using a partial QofBook.
+
+Part of the handling for partial books requires a storage mechanism for
+references to entities that are not within reach of the partial book.
+This requires a GList in the book data to contain the reference
+QofIdType and GUID so that when the book is written out, the
+reference can be included. See ::qof_book_get_data.
+
+When the file is imported back in, the list needs to be rebuilt.
+The QSF backend rebuilds the references by linking to real entities. Other
+backends can process the hash table in similar ways.
+
+The list stores the QofEntityReference to the referenced entity -
+a struct that contains the GUID and the QofIdType of the referenced entity
+as well as the parameter used to obtain the reference.
+
+Partial books need to be differentiated in the backend, the
+flag in the book data is used by qof_session_save to prevent a partial
+book being saved using a backend that requires a full book.
+
+ @{ */
+
+
+/** \brief External references in a partial QofBook.
+
+For use by any session that deals with partial QofBooks.
+It is used by the entity copy functions and by the QSF backend.
+Creates a GList stored in the Book hashtable to contain
+repeated references for a single entity.
+*/
+typedef struct qof_entity_reference {
+ QofIdType choice_type;/**< When the reference is a different type.*/
+ QofIdType type; /**< The type of entity */
+ GUID *ref_guid; /**< The GUID of the REFERENCE entity */
+ const QofParam *param; /**< The parameter name and type. */
+ const GUID *ent_guid; /**< The GUID of the original entity. */
+}QofEntityReference;
+
+/** \brief Get a reference from this entity to another entity.
+
+Used in the preparation of a partial QofBook when the known entity
+(the one currently being copied into the partial book) refers to
+any other entity, usually as a parent or child.
+The routine calls the param_getfcn of the supplied parameter,
+which must return an object (QofEntity*), not a known QOF data type, to
+retrieve the referenced entity and therefore the GUID. The GUID of
+both entities are stored in the reference which then needs to be added
+to the reference list which is added to the partial book data hash.
+The reference itself is used by the backend to preserve the relationship
+between entities within and outside the partial book.
+
+See also ::qof_class_get_referenceList to obtain the list of
+parameters that provide references to the known entity whilst
+excluding parameters that return known QOF data types.
+
+Note that even if the referenced entity \b exists in the partial
+book (or will exist later), a reference must still be obtained and
+added to the reference list for the book itself. This maintains
+the integrity of the partial book during sequential copy operations.
+
+ at param ent The known entity.
+ at param param The parameter to use to get the referenced entity.
+
+ at return FALSE on error, otherwise a pointer to the QofEntityReference.
+*/
+QofEntityReference*
+qof_entity_get_reference_from(QofEntity *ent, const QofParam *param);
+
+/** \brief Adds a new reference to the partial book data hash.
+
+Retrieves any existing reference list and appends the new reference.
+
+If the book is not already marked as partial, it will be marked as partial.
+*/
+void
+qof_session_update_reference_list(QofSession *session, QofEntityReference *reference);
+
+/** Used as the key value for the QofBook data hash.
+ *
+ * Retrieved later by QSF (or any other suitable backend) to
+ * rebuild the references from the QofEntityReference struct
+ * that contains the QofIdType and GUID of the referenced entity
+ * of the original QofBook as well as the parameter data and the
+ * GUID of the original entity.
+ * */
+#define ENTITYREFERENCE "QofEntityReference"
+
+/** \brief Flag indicating a partial QofBook.
+
+When set in the book data with a gboolean value of TRUE,
+the flag denotes that only a backend that supports partial
+books can be used to save this session.
+*/
+
+#define PARTIAL_QOFBOOK "PartialQofBook"
+
+/** @}
+*/
+
+/** \brief Allow session data to be printed to stdout
+
+book_id can't be NULL and we do need to have an access_method,
+so use one to solve the other.
+
+To print a session to stdout, use ::qof_session_begin. Example:
+
+\a qof_session_begin(session,QOF_STDOUT,TRUE,FALSE);
+
+When you call qof_session_save(session, NULL), the output will appear
+on stdout and can be piped or redirected to other processes.
+
+Currently, only the QSF backend supports writing to stdout, other
+backends may return a ::QofBackendError.
+*/
+#define QOF_STDOUT "file:"
+
+/** @name Event Handling
+
+ @{ */
+/** The qof_session_events_pending() method will return TRUE if the backend
+ * has pending events which must be processed to bring the engine
+ * up to date with the backend.
+ */
+gboolean qof_session_events_pending (QofSession *session);
+
+/** The qof_session_process_events() method will process any events indicated
+ * by the qof_session_events_pending() method. It returns TRUE if the
+ * engine was modified while engine events were suspended.
+ */
+gboolean qof_session_process_events (QofSession *session);
+/** @} */
+
+#ifdef GNUCASH_MAJOR_VERSION
+/** Run the RPC Server
+ * @deprecated will go away */
+void gnc_run_rpc_server (void);
+
+/** XXX session_export really doesn't belong here .
+ * This functino exports the list of accounts to a file. Its a stop-gap
+ * measure until full book-closing is implemented.
+ */
+gboolean qof_session_export (QofSession *tmp_session,
+ QofSession *real_session,
+ QofPercentageFunc percentage_func);
+
+#endif /* GNUCASH_MAJOR_VERSION */
+
+/** Register a function to be called just before a session is closed.
+ *
+ * @param fn The function to be called. The function definition must
+ * be func(gpointer session, gpointer user_data);
+ *
+ * @param data The data to be passed to the function. */
+void qof_session_add_close_hook (GFunc fn, gpointer data);
+
+/** Call all registered session close hooks, informing them that the
+ * specified session is about to be closed.
+ *
+ * @param session A pointer to the session being closed. */
+void qof_session_call_close_hooks (QofSession *session);
+
+#endif /* QOF_SESSION_H */
+/** @} */
Added: gnucash/trunk/lib/libqof/qof/qofsql.c
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofsql.c 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofsql.c 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,928 @@
+/********************************************************************\
+ * qofsql.c -- QOF client-side SQL parser *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+/**
+ @file qofsql.c
+ @brief QOF client-side SQL parser.
+ @author Copyright (C) 2004 Linas Vepstas <linas at linas.org>
+
+*/
+#define _GNU_SOURCE
+
+#include <stdlib.h> /* for working atoll */
+#include <errno.h>
+#include "glib.h"
+#include "config.h"
+#ifdef HAVE_GDA
+#include <sql/sql_parser.h>
+#else
+#include "sql_parser.h"
+#endif
+#include <time.h>
+#include "kvp_frame.h"
+#include "gnc-date.h"
+#include "gnc-numeric.h"
+#include "gnc-trace.h"
+#include "guid.h"
+#include "qofbook.h"
+#include "qofquery.h"
+#include "qofquerycore.h"
+#include "qofsql.h"
+#include "gnc-engine-util.h"
+#include "qofinstance-p.h"
+#include "qofobject.h"
+
+static QofLogModule log_module = QOF_MOD_QUERY;
+
+/* =================================================================== */
+
+struct _QofSqlQuery
+{
+ sql_statement *parse_result;
+ QofQuery *qof_query;
+ QofBook *book;
+ char * single_global_tablename;
+ KvpFrame *kvp_join;
+ GList *param_list;
+ QofEntity *inserted_entity;
+};
+
+/* ========================================================== */
+
+QofSqlQuery *
+qof_sql_query_new(void)
+{
+ QofSqlQuery * sqn = (QofSqlQuery *) g_new0 (QofSqlQuery, 1);
+
+ sqn->qof_query = NULL;
+ sqn->parse_result = NULL;
+ sqn->book = NULL;
+ sqn->single_global_tablename = NULL;
+ sqn->kvp_join = NULL;
+
+ return sqn;
+}
+
+/* ========================================================== */
+
+void
+qof_sql_query_destroy (QofSqlQuery *q)
+{
+ if (!q) return;
+ qof_query_destroy (q->qof_query);
+ sql_destroy (q->parse_result);
+ g_free (q);
+}
+
+/* ========================================================== */
+
+QofQuery *
+qof_sql_query_get_query (QofSqlQuery *q)
+{
+ if (!q) return NULL;
+ return q->qof_query;
+}
+
+/* ========================================================== */
+
+void
+qof_sql_query_set_book (QofSqlQuery *q, QofBook *book)
+{
+ if (!q) return;
+ q->book = book;
+}
+
+/* ========================================================== */
+
+void
+qof_sql_query_set_kvp (QofSqlQuery *q, KvpFrame *kvp)
+{
+ if (!q) return;
+ q->kvp_join = kvp;
+}
+
+/* ========================================================== */
+
+static inline void
+get_table_and_param (char * str, char **tab, char **param)
+{
+ char * end = strchr (str, '.');
+ if (!end)
+ {
+ *tab = 0;
+ *param = str;
+ return;
+ }
+ *end = 0;
+ *tab = str;
+ *param = end+1;
+}
+
+static inline char *
+dequote_string (char *str)
+{
+ size_t len;
+ /* strip out quotation marks ... */
+ if (('\'' == str[0]) ||
+ ('\"' == str[0]))
+ {
+ str ++;
+ len = strlen(str);
+ str[len-1] = 0;
+ }
+ return str;
+}
+
+static QofQuery *
+handle_single_condition (QofSqlQuery *query, sql_condition * cond)
+{
+ char tmpbuff[128];
+ GSList *param_list;
+ GList *guid_list;
+ QofQueryPredData *pred_data;
+ sql_field_item *sparam, *svalue;
+ char * qparam_name, *qvalue_name, *table_name, *param_name;
+ char *sep, *path,*str,*p;
+ QofQuery *qq;
+ KvpValue *kv, *kval;
+ KvpValueType kvt;
+ QofQueryCompare qop;
+ time_t exact;
+ int rc, len;
+ Timespec ts;
+ QofType param_type;
+ QofGuidMatch gm;
+
+ pred_data = NULL;
+ if (NULL == cond)
+ {
+ PWARN("missing condition");
+ return NULL;
+ }
+ /* -------------------------------- */
+ /* field to match, assumed, for now to be on the left */
+ /* XXX fix this so it can be either left or right */
+ if (NULL == cond->d.pair.left)
+ {
+ PWARN("missing left parameter");
+ return NULL;
+ }
+ sparam = cond->d.pair.left->item;
+ if (SQL_name != sparam->type)
+ {
+ PWARN("we support only parameter names at this time (parsed %d)",
+ sparam->type);
+ return NULL;
+ }
+ qparam_name = sparam->d.name->data;
+ if (NULL == qparam_name)
+ {
+ PWARN ("missing parameter name");
+ return NULL;
+ }
+
+ /* -------------------------------- */
+ /* value to match, assumed, for now, to be on the right. */
+ /* XXX fix this so it can be either left or right */
+ if (NULL == cond->d.pair.right)
+ {
+ PWARN ("missing right parameter");
+ return NULL;
+ }
+ svalue = cond->d.pair.right->item;
+ if (SQL_name != svalue->type)
+ {
+ PWARN("we support only simple values (parsed as %d)", svalue->type);
+ return NULL;
+ }
+ qvalue_name = svalue->d.name->data;
+ if (NULL == qvalue_name)
+ {
+ PWARN("missing value");
+ return NULL;
+ }
+ qvalue_name = dequote_string (qvalue_name);
+ qvalue_name = (char *) qof_util_whitespace_filter (qvalue_name);
+
+ /* Look to see if its the special KVP value holder.
+ * If it is, look up the value. */
+ if (0 == strncasecmp (qvalue_name, "kvp://", 6))
+ {
+ if (NULL == query->kvp_join)
+ {
+ PWARN ("missing kvp frame");
+ return NULL;
+ }
+ kv = kvp_frame_get_value (query->kvp_join, qvalue_name+5);
+ /* If there's no value, its not an error;
+ * we just don't do this predicate */
+ if (!kv) return NULL;
+ kvt = kvp_value_get_type (kv);
+
+ tmpbuff[0] = 0x0;
+ qvalue_name = tmpbuff;
+ switch (kvt)
+ {
+ case KVP_TYPE_GINT64:
+ {
+ gint64 ival = kvp_value_get_gint64(kv);
+ sprintf (tmpbuff, "%" G_GINT64_FORMAT "\n", ival);
+ break;
+ }
+ case KVP_TYPE_DOUBLE:
+ {
+ double ival = kvp_value_get_double(kv);
+ sprintf (tmpbuff, "%26.18g\n", ival);
+ break;
+ }
+ case KVP_TYPE_STRING:
+ /* If there's no value, its not an error;
+ * we just don't do this predicate */
+ qvalue_name = kvp_value_get_string (kv);
+ if (!qvalue_name) return NULL;
+ break;
+ case KVP_TYPE_GUID:
+ case KVP_TYPE_TIMESPEC:
+ case KVP_TYPE_BINARY:
+ case KVP_TYPE_GLIST:
+ case KVP_TYPE_NUMERIC:
+ case KVP_TYPE_FRAME:
+ PWARN ("unhandled kvp type=%d", kvt);
+ return NULL;
+ }
+ }
+
+ /* -------------------------------- */
+ /* Now start building the QOF parameter */
+ param_list = qof_query_build_param_list (qparam_name, NULL);
+
+ /* Get the where-term comparison operator */
+ switch (cond->op)
+ {
+ case SQL_eq: qop = QOF_COMPARE_EQUAL; break;
+ case SQL_gt: qop = QOF_COMPARE_GT; break;
+ case SQL_lt: qop = QOF_COMPARE_LT; break;
+ case SQL_geq: qop = QOF_COMPARE_GTE; break;
+ case SQL_leq: qop = QOF_COMPARE_LTE; break;
+ case SQL_diff: qop = QOF_COMPARE_NEQ; break;
+ default:
+ /* XXX for string-type queries, we should be able to
+ * support 'IN' for substring search. Also regex. */
+ PWARN ("Unsupported compare op (parsed as %u)", cond->op);
+ return NULL;
+ }
+
+ /* OK, need to know the type of the thing being matched
+ * in order to build the correct predicate. Get the type
+ * from the object parameters. */
+ get_table_and_param (qparam_name, &table_name, ¶m_name);
+ if (NULL == table_name)
+ {
+ table_name = query->single_global_tablename;
+ }
+ if (NULL == table_name)
+ {
+ PWARN ("Need to specify an object class to query");
+ return NULL;
+ }
+
+ if (FALSE == qof_class_is_registered (table_name))
+ {
+ PWARN ("The query object \'%s\' is not known", table_name);
+ return NULL;
+ }
+
+ param_type = qof_class_get_parameter_type (table_name, param_name);
+ if (!param_type)
+ {
+ PWARN ("The parameter \'%s\' on object \'%s\' is not known",
+ param_name, table_name);
+ return NULL;
+ }
+
+ if (!strcmp (param_type, QOF_TYPE_STRING))
+ {
+ pred_data =
+ qof_query_string_predicate (qop, /* comparison to make */
+ qvalue_name, /* string to match */
+ QOF_STRING_MATCH_CASEINSENSITIVE, /* case matching */
+ FALSE); /* use_regexp */
+ }
+ else if (!strcmp (param_type, QOF_TYPE_CHAR))
+ {
+ QofCharMatch cm = QOF_CHAR_MATCH_ANY;
+ if (QOF_COMPARE_NEQ == qop) cm = QOF_CHAR_MATCH_NONE;
+ pred_data = qof_query_char_predicate (cm, qvalue_name);
+ }
+ else if (!strcmp (param_type, QOF_TYPE_INT32))
+ {
+ gint32 ival = atoi (qvalue_name);
+ pred_data = qof_query_int32_predicate (qop, ival);
+ }
+ else if (!strcmp (param_type, QOF_TYPE_INT64))
+ {
+ gint64 ival = atoll (qvalue_name);
+ pred_data = qof_query_int64_predicate (qop, ival);
+ }
+ else if (!strcmp (param_type, QOF_TYPE_DOUBLE))
+ {
+ double ival = atof (qvalue_name);
+ pred_data = qof_query_double_predicate (qop, ival);
+ }
+ else if (!strcmp (param_type, QOF_TYPE_BOOLEAN))
+ {
+ gboolean ival = qof_util_bool_to_int (qvalue_name);
+ pred_data = qof_query_boolean_predicate (qop, ival);
+ }
+ else if (!strcmp (param_type, QOF_TYPE_DATE))
+ {
+ /* Use a timezone independent setting */
+ qof_date_format_set(QOF_DATE_FORMAT_UTC);
+ rc = 0;
+ if(FALSE == qof_scan_date_secs (qvalue_name, &exact))
+ {
+ char *tail;
+ exact = strtoll(qvalue_name, &tail, 0);
+// PWARN ("unable to parse date: %s", qvalue_name);
+// return NULL;
+ }
+ ts.tv_sec = exact;
+ ts.tv_nsec = 0;
+ pred_data = qof_query_date_predicate (qop, QOF_DATE_MATCH_NORMAL, ts);
+ }
+ else if (!strcmp (param_type, QOF_TYPE_NUMERIC))
+ {
+ gnc_numeric ival;
+ string_to_gnc_numeric (qvalue_name, &ival);
+ pred_data = qof_query_numeric_predicate (qop, QOF_NUMERIC_MATCH_ANY, ival);
+ }
+ else if (!strcmp (param_type, QOF_TYPE_DEBCRED))
+ {
+ // XXX this probably needs some work ...
+ gnc_numeric ival;
+ string_to_gnc_numeric (qvalue_name, &ival);
+ pred_data = qof_query_numeric_predicate (qop, QOF_NUMERIC_MATCH_ANY, ival);
+ }
+ else if (!strcmp (param_type, QOF_TYPE_GUID))
+ {
+ GUID guid;
+ gboolean rc = string_to_guid (qvalue_name, &guid);
+ if (0 == rc)
+ {
+ PWARN ("unable to parse guid: %s", qvalue_name);
+ return NULL;
+ }
+
+ // XXX less, than greater than don't make sense,
+ // should check for those bad conditions
+
+ gm = QOF_GUID_MATCH_ANY;
+ if (QOF_COMPARE_NEQ == qop) gm = QOF_GUID_MATCH_NONE;
+ guid_list = g_list_append (NULL, &guid);
+ pred_data = qof_query_guid_predicate (gm, guid_list);
+
+ g_list_free (guid_list);
+ }
+ else if (!strcmp (param_type, QOF_TYPE_KVP))
+ {
+ /* We are expecting an encoded value that looks like
+ * /some/path/string:value
+ */
+ sep = strchr (qvalue_name, ':');
+ if (!sep) return NULL;
+ *sep = 0;
+ path = qvalue_name;
+ str = sep +1;
+ /* If str has only digits, we know its a plain number.
+ * If its numbers and a decimal point, assume a float
+ * If its numbers and a slash, assume numeric
+ * If its 32 bytes of hex, assume GUID
+ * If it looks like an iso date ...
+ * else assume its a string.
+ */
+ kval = NULL;
+ len = strlen (str);
+ if ((32 == len) && (32 == strspn (str, "0123456789abcdef")))
+ {
+ GUID guid;
+ string_to_guid (str, &guid);
+ kval = kvp_value_new_guid (&guid);
+ }
+ else
+ if (len == strspn (str, "0123456789"))
+ {
+ kval = kvp_value_new_gint64 (atoll(str));
+ }
+ else
+ if ((p=strchr (str, '.')) &&
+ ((len-1) == (strspn (str, "0123456789") +
+ strspn (p+1, "0123456789"))))
+ {
+ kval = kvp_value_new_double (atof(str));
+ }
+
+ else
+ if ((p=strchr (str, '/')) &&
+ ((len-1) == (strspn (str, "0123456789") +
+ strspn (p+1, "0123456789"))))
+ {
+ gnc_numeric num;
+ string_to_gnc_numeric (str, &num);
+ kval = kvp_value_new_gnc_numeric (num);
+ }
+ else
+ if ((p=strchr (str, '-')) &&
+ (p=strchr (p+1, '-')) &&
+ (p=strchr (p+1, ' ')) &&
+ (p=strchr (p+1, ':')) &&
+ (p=strchr (p+1, ':')))
+ {
+ kval = kvp_value_new_timespec (gnc_iso8601_to_timespec_gmt(str));
+ }
+
+ /* The default handler is a string */
+ if (NULL == kval)
+ {
+ kval = kvp_value_new_string (str);
+ }
+ pred_data = qof_query_kvp_predicate_path (qop, path, kval);
+ }
+ else
+ {
+ PWARN ("The predicate type \"%s\" is unsupported for now", param_type);
+ return NULL;
+ }
+
+ qq = qof_query_create();
+ qof_query_add_term (qq, param_list, pred_data, QOF_QUERY_FIRST_TERM);
+ return qq;
+}
+
+/* ========================================================== */
+
+static QofQuery *
+handle_where (QofSqlQuery *query, sql_where *swear)
+{
+ QofQueryOp qop;
+ QofQuery * qq;
+
+ switch (swear->type)
+ {
+ case SQL_pair:
+ {
+ QofQuery *qleft = handle_where (query, swear->d.pair.left);
+ QofQuery *qright = handle_where (query, swear->d.pair.right);
+ if (NULL == qleft) return qright;
+ if (NULL == qright) return qleft;
+ switch (swear->d.pair.op)
+ {
+ case SQL_and: qop = QOF_QUERY_AND; break;
+ case SQL_or: qop = QOF_QUERY_OR; break;
+ /* XXX should add support for nand, nor, xor */
+ default:
+ qof_query_destroy (qleft);
+ qof_query_destroy (qright);
+ return NULL;
+ }
+ qq = qof_query_merge (qleft, qright, qop);
+ qof_query_destroy (qleft);
+ qof_query_destroy (qright);
+ return qq;
+ }
+ case SQL_negated:
+ {
+ QofQuery *qq = handle_where (query, swear->d.negated);
+ QofQuery *qneg = qof_query_invert (qq);
+ qof_query_destroy (qq);
+ return qneg;
+ }
+
+ case SQL_single:
+ {
+ sql_condition * cond = swear->d.single;
+ return handle_single_condition (query, cond);
+ }
+ }
+ return NULL;
+}
+
+/* ========================================================== */
+
+static void
+handle_sort_order (QofSqlQuery *query, GList *sorder_list)
+{
+ GSList *qsp[3];
+ GList *n;
+ gboolean direction[3];
+ int i;
+ sql_order_field *sorder;
+ char * qparam_name;
+
+ if (!sorder_list) return;
+
+ for (i=0; i<3; i++)
+ {
+ qsp[i] = NULL;
+ direction[i] = 0;
+
+ if (sorder_list)
+ {
+ sorder = sorder_list->data;
+
+ /* Set the sort direction */
+ if (SQL_asc == sorder->order_type) direction[i] = TRUE;
+
+ /* Find the parameter name */
+ qparam_name = NULL;
+ n = sorder->name;
+ if (n)
+ {
+ qparam_name = n->data;
+ if (qparam_name)
+ {
+ qsp[i] = qof_query_build_param_list (qparam_name, NULL);
+ }
+ n = n->next; /* next parameter */
+ }
+ else
+ {
+ /* if no next parameter, then next order-by */
+ sorder_list = sorder_list->next;
+ }
+ }
+ }
+
+ qof_query_set_sort_order (query->qof_query, qsp[0], qsp[1], qsp[2]);
+ qof_query_set_sort_increasing (query->qof_query, direction[0],
+ direction[1], direction[2]);
+}
+
+/* ========================================================== */
+static void
+qof_queryForeachParam( QofParam* param, gpointer user_data)
+{
+ QofSqlQuery *q;
+
+ g_return_if_fail(user_data != NULL);
+ q = (QofSqlQuery*)user_data;
+ g_return_if_fail(param != NULL);
+ if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
+ q->param_list = g_list_append(q->param_list, param);
+ }
+}
+
+static const char*
+qof_sql_get_value(sql_insert_statement *insert)
+{
+ GList *walk, *cur;
+ const char *insert_string;
+ sql_field *field;
+ sql_field_item * item;
+
+ /* how to cope with multiple results? */
+ if (insert->values == NULL) { return NULL; }
+ insert_string = NULL;
+ for (walk = insert->values; walk != NULL; walk = walk->next)
+ {
+ field = walk->data;
+ item = field->item;
+ for (cur = item->d.name; cur != NULL; cur = cur->next)
+ {
+ insert_string = g_strdup_printf("%s", (char*)cur->data);
+ }
+ }
+ return insert_string;
+}
+
+static const QofParam*
+qof_sql_get_param(QofIdTypeConst type, sql_insert_statement *insert)
+{
+ GList *walk, *cur;
+ const char *param_name;
+ const QofParam *param;
+ sql_field *field;
+ sql_field_item *item;
+
+ param = NULL;
+ param_name = NULL;
+ if (insert->fields == NULL) { return NULL; }
+ for (walk = insert->fields; walk != NULL; walk = walk->next)
+ {
+ field = walk->data;
+ item = field->item;
+ for (cur = item->d.name; cur != NULL; cur = cur->next)
+ {
+ param_name = g_strdup_printf("%s", (char*)cur->data);
+ }
+ }
+ param = qof_class_get_parameter(type, param_name);
+ return param;
+}
+
+static void
+qof_sql_insertCB( gpointer value, gpointer data)
+{
+ GList *param_list;
+ QofSqlQuery *q;
+ QofIdTypeConst type;
+ sql_insert_statement *sis;
+ const char *insert_string;
+ gboolean registered_type;
+ QofEntity *ent;
+ QofParam *param;
+ struct tm query_time;
+ time_t query_time_t;
+ /* cm_ prefix used for variables that hold the data to commit */
+ gnc_numeric cm_numeric;
+ double cm_double;
+ gboolean cm_boolean;
+ gint32 cm_i32;
+ gint64 cm_i64;
+ Timespec cm_date;
+ char cm_char, *tail;
+ GUID *cm_guid;
+/* KvpFrame *cm_kvp;
+ KvpValue *cm_value;
+ KvpValueType cm_type;*/
+ const QofParam *cm_param;
+ void (*string_setter) (QofEntity*, const char*);
+ void (*date_setter) (QofEntity*, Timespec);
+ void (*numeric_setter) (QofEntity*, gnc_numeric);
+ void (*double_setter) (QofEntity*, double);
+ void (*boolean_setter) (QofEntity*, gboolean);
+ void (*i32_setter) (QofEntity*, gint32);
+ void (*i64_setter) (QofEntity*, gint64);
+ void (*char_setter) (QofEntity*, char);
+/* void (*kvp_frame_setter) (QofEntity*, KvpFrame*);*/
+
+ q = (QofSqlQuery*)data;
+ ent = q->inserted_entity;
+ param = (QofParam*)value;
+ sis = q->parse_result->statement;
+ type = g_strdup_printf("%s", sis->table->d.simple);
+ insert_string = g_strdup(qof_sql_get_value(sis));
+ cm_param = qof_sql_get_param(type, sis);
+ param_list = g_list_copy(q->param_list);
+ while(param_list != NULL) {
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_STRING) == 0) {
+ string_setter = (void(*)(QofEntity*, const char*))cm_param->param_setfcn;
+ if(string_setter != NULL) { string_setter(ent, insert_string); }
+ registered_type = TRUE;
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_DATE) == 0) {
+ date_setter = (void(*)(QofEntity*, Timespec))cm_param->param_setfcn;
+ strptime(insert_string, QOF_UTC_DATE_FORMAT, &query_time);
+ query_time_t = mktime(&query_time);
+ timespecFromTime_t(&cm_date, query_time_t);
+ if(date_setter != NULL) { date_setter(ent, cm_date); }
+ }
+ if((safe_strcmp(cm_param->param_type, QOF_TYPE_NUMERIC) == 0) ||
+ (safe_strcmp(cm_param->param_type, QOF_TYPE_DEBCRED) == 0)) {
+ numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_param->param_setfcn;
+ string_to_gnc_numeric(insert_string, &cm_numeric);
+ if(numeric_setter != NULL) { numeric_setter(ent, cm_numeric); }
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_GUID) == 0) {
+ cm_guid = g_new(GUID, 1);
+ if(TRUE != string_to_guid(insert_string, cm_guid))
+ {
+ LEAVE (" string to guid failed for %s", insert_string);
+ return;
+ }
+/* reference_type = xmlGetProp(node, QSF_OBJECT_TYPE);
+ if(0 == safe_strcmp(QOF_PARAM_GUID, reference_type))
+ {
+ qof_entity_set_guid(qsf_ent, cm_guid);
+ }
+ else {
+ reference = qof_entity_get_reference_from(qsf_ent, cm_param);
+ if(reference) {
+ params->referenceList = g_list_append(params->referenceList, reference);
+ }
+ }*/
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_INT32) == 0) {
+ errno = 0;
+ cm_i32 = (gint32)strtol (insert_string, &tail, 0);
+ if(errno == 0) {
+ i32_setter = (void(*)(QofEntity*, gint32))cm_param->param_setfcn;
+ if(i32_setter != NULL) { i32_setter(ent, cm_i32); }
+ }
+// else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_INT64) == 0) {
+ errno = 0;
+ cm_i64 = strtoll(insert_string, &tail, 0);
+ if(errno == 0) {
+ i64_setter = (void(*)(QofEntity*, gint64))cm_param->param_setfcn;
+ if(i64_setter != NULL) { i64_setter(ent, cm_i64); }
+ }
+// else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_DOUBLE) == 0) {
+ errno = 0;
+ cm_double = strtod(insert_string, &tail);
+ if(errno == 0) {
+ double_setter = (void(*)(QofEntity*, double))cm_param->param_setfcn;
+ if(double_setter != NULL) { double_setter(ent, cm_double); }
+ }
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_BOOLEAN) == 0){
+ if(0 == safe_strcmp(insert_string, "TRUE")) {
+ cm_boolean = TRUE;
+ }
+ else { cm_boolean = FALSE; }
+ boolean_setter = (void(*)(QofEntity*, gboolean))cm_param->param_setfcn;
+ if(boolean_setter != NULL) { boolean_setter(ent, cm_boolean); }
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_KVP) == 0) {
+
+ }
+ if(safe_strcmp(cm_param->param_type, QOF_TYPE_CHAR) == 0) {
+ cm_char = *insert_string;
+ char_setter = (void(*)(QofEntity*, char))cm_param->param_setfcn;
+ if(char_setter != NULL) { char_setter(ent, cm_char); }
+ }
+ param_list = param_list->next;
+ }
+}
+
+static QofEntity*
+qof_query_insert(QofSqlQuery *query)
+{
+ QofIdType type;
+ QofInstance *inst;
+ sql_insert_statement *sis;
+ sql_table *sis_t;
+
+ query->param_list = NULL;
+ type = NULL;
+ sis = query->parse_result->statement;
+ switch(sis->table->type) {
+ case SQL_simple: {
+ sis_t = sis->table;
+ query->single_global_tablename = g_strdup_printf("%s", sis_t->d.simple);
+ type = g_strdup(query->single_global_tablename);
+ break;
+ }
+ default: {
+ fprintf(stderr, "default");
+ }
+ }
+ inst = (QofInstance*)qof_object_new_instance(type, query->book);
+ if(inst == NULL) { return NULL; }
+ query->param_list = NULL;
+ query->inserted_entity = &inst->entity;
+ qof_class_param_foreach((QofIdTypeConst)type, qof_queryForeachParam, query);
+ g_list_foreach(query->param_list, qof_sql_insertCB, query);
+ return query->inserted_entity;
+}
+
+static const char*
+sql_type_as_string(sql_statement_type type)
+{
+ switch (type)
+ {
+ case SQL_select : { return "SELECT"; }
+ case SQL_insert : { return "INSERT"; }
+ case SQL_delete : { return "DELETE"; }
+ case SQL_update : { return "UPDATE"; }
+ default : { return "unknown"; }
+ }
+}
+
+void
+qof_sql_query_parse (QofSqlQuery *query, const char *str)
+{
+ GList *tables;
+ char *buf;
+ sql_select_statement *sss;
+ sql_where *swear;
+
+ if (!query) return;
+
+ /* Delete old query, if any */
+ if (query->qof_query)
+ {
+ qof_query_destroy (query->qof_query);
+ sql_destroy(query->parse_result);
+ query->qof_query = NULL;
+ }
+
+ /* Parse the SQL string */
+ buf = g_strdup(str);
+ query->parse_result = sql_parse (buf);
+ g_free(buf);
+
+ if (!query->parse_result)
+ {
+ PWARN ("parse error");
+ return;
+ }
+
+ if ((SQL_select != query->parse_result->type)&&(SQL_insert != query->parse_result->type))
+ {
+ PWARN("currently, only SELECT or INSERT statements are supported, "
+ "got type=%s", sql_type_as_string(query->parse_result->type));
+ return;
+ }
+
+ /* If the user wrote "SELECT * FROM tablename WHERE ..."
+ * then we have a single global tablename. But if the
+ * user wrote "SELECT * FROM tableA, tableB WHERE ..."
+ * then we don't have a single unique table-name.
+ */
+ tables = sql_statement_get_tables (query->parse_result);
+ if (1 == g_list_length (tables))
+ {
+ query->single_global_tablename = tables->data;
+ }
+ /* if this is an insert, we're done with the parse. */
+ if(SQL_insert == query->parse_result->type) {
+ query->qof_query = qof_query_create();
+ return;
+ }
+ sss = query->parse_result->statement;
+ swear = sss->where;
+ if (swear)
+ {
+ /* Walk over the where terms, turn them into QOF predicates */
+ query->qof_query = handle_where (query, swear);
+ if (NULL == query->qof_query) return;
+ }
+ else
+ {
+ query->qof_query = qof_query_create();
+ }
+ /* Provide support for different sort orders */
+ handle_sort_order (query, sss->order);
+
+ /* We also want to set the type of thing to search for.
+ * If the user said SELECT * FROM ... then we should return
+ * a list of QofEntity. Otherwise, we return ... ?
+ * XXX all this needs fixing.
+ */
+ qof_query_search_for (query->qof_query, query->single_global_tablename);
+}
+
+/* ========================================================== */
+
+GList *
+qof_sql_query_run (QofSqlQuery *query, const char *str)
+{
+ GList *results;
+
+ if (!query) return NULL;
+
+ qof_sql_query_parse (query, str);
+ if (NULL == query->qof_query) return NULL;
+
+ qof_query_set_book (query->qof_query, query->book);
+ if(SQL_insert == query->parse_result->type){
+ results = NULL;
+ results = g_list_append(results, qof_query_insert(query));
+ return results;
+ }
+ qof_query_print (query->qof_query);
+ results = qof_query_run (query->qof_query);
+
+ return results;
+}
+
+GList *
+qof_sql_query_rerun (QofSqlQuery *query)
+{
+ GList *results;
+
+ if (!query) return NULL;
+
+ if (NULL == query->qof_query) return NULL;
+
+ qof_query_set_book (query->qof_query, query->book);
+
+ // qof_query_print (query->qof_query);
+ results = qof_query_run (query->qof_query);
+
+ return results;
+}
+
+/* ========================== END OF FILE =================== */
Added: gnucash/trunk/lib/libqof/qof/qofsql.h
===================================================================
--- gnucash/trunk/lib/libqof/qof/qofsql.h 2005-11-07 15:45:00 UTC (rev 11881)
+++ gnucash/trunk/lib/libqof/qof/qofsql.h 2005-11-07 15:45:58 UTC (rev 11882)
@@ -0,0 +1,201 @@
+/********************************************************************\
+ * qofsql.h -- QOF client-side SQL parser *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
+ * Boston, MA 02111-1307, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+/** @addtogroup Query
+@{ */
+/**
+ @file qofsql.h
+ @brief QOF client-side SQL parser.
+ @author Copyright (C) 2004 Linas Vepstas <linas at linas.org>
+*/
+
+#ifndef QOF_SQL_QUERY_H
+#define QOF_SQL_QUERY_H
+
+#include <glib.h>
+#include <kvp_frame.h>
+#include <qofbook.h>
+#include <qofquery.h>
+
+/** @addtogroup SQL SQL Interface to Query
+
+The types of SQL queries that are allowed at this point are very
+limited. In general, only the following types of queries are
+supported:
+ SELECT * FROM SomeObj WHERE (param_a < 10.0) AND (param_b = "asdf")
+ SORT BY param_c DESC;
+ INSERT INTO SomeObj (param_a, param_b, param_c) VALUES
+ ("value_a", true, "0/1");
+
+For SELECT, the returned list is a list of all of the instances of 'SomeObj' that
+match the query. The 'SORT' term is optional. The 'WHERE' term is
+optional; but if you don't include 'WHERE', you will get a list of
+all of the object instances. The Boolean operations 'AND' and 'OR'
+together with parenthesis can be used to construct arbitrarily
+nested predicates.
+
+For INSERT, the returned list is a list containing the newly created instance
+of 'SomeObj'.
+
+Joins are not supported directly.
+ SELECT * FROM ObjA,ObjB WHERE (ObjA.param_id = ObjB.param_other_id);
+The problem with the above is that the search requires a nested
+search loop, aka a 'join', which is not currently supported in the
+underlying QofQuery code.
+
+However, by repeating queries and adding the entities to a new session using
+::qof_entity_copy_list, a series of queries can be added to a single
+book. e.g. You can insert multiple entities and save out as a QSF XML
+file or use multiple SELECT queries to build a precise list - this
+can be used to replicate most of the functionality of a SQL join.
+
+SELECT * from ObjA where param_id = value;
+SELECT * from ObjB where param_other_id = value;
+
+Equivalent to:
+SELECT * from ObjA,ObjB where param_id = param_other_id and param_id = value;
+
+When combined with a foreach callback on the value of param_id for each
+entity in the QofBook, you can produce the effect of a join from running
+the two SELECT queries for each value of param_id held in 'value'.
+
+See ::QofEntityForeachCB and ::qof_object_foreach.
+
+Date queries handle full date and time strings, using the format
+exported by the QSF backend. To query dates and times, convert
+user input into UTC time using the ::QOF_UTC_DATE_FORMAT string.
+e.g. set the UTC date format and call ::qof_print_time_buff
+with a time_t obtained via ::timespecToTime_t.
+
+If the param is a KVP frame, then we use a special markup to
+indicate frame values. The markup should look like
+/some/kvp/path:value. Thus, for example,
+ SELECT * FROM SomeObj WHERE (param_a < '/some/kvp:10.0')
+will search for the object where param_a is a KVP frame, and this
+KVP frame contains a path '/some/kvp' and the value stored at that
+path is floating-point and that float value is less than 10.0.
+
+The following are types of queries are NOT supported:
+ SELECT a,b,c FROM ...
+I am thinking of implementing the above as a list of KVP's
+whose keys would be a,b,c and values would be the results of the
+search. (Linas)
+
+XXX (Neil W). Alternatively, I need to use something like this
+when converting QOF objects between applications by using the
+returned parameter values to create a second object. One application
+using QOF could register objects from two applications and convert
+data from one to the other by using SELECT a,b,c FROM ObjA;
+SELECT d,f,k FROM ObjB; qof_object_new_instance(); ObjC_set_a(value_c);
+ObjC_set_b(value_k) etc. What's needed is for the SELECT to return
+a complete object that only contains the parameters selected.
+
+ Also unsupported: UPDATE.
+
+Certain SQL commands can have no QOF equivalent and will
+generate a runtime parser error:
+ - ALTER
+ - CREATE
+ - DROP
+ - FLUSH
+ - GRANT
+ - KILL
+ - LOCK
+ - OPTIMIZE
+ - REVOKE
+ - USE
+
+ @{ */
+typedef struct _QofSqlQuery QofSqlQuery;
+
+/** Create a new SQL-syntax query machine.
+ */
+QofSqlQuery * qof_sql_query_new (void);
+void qof_sql_query_destroy (QofSqlQuery *);
+
+/** Set the book to be searched (you can search multiple books)
+ * If no books are set, no results will be returned (since there
+ * is nothing to search over).
+ */
+void qof_sql_query_set_book (QofSqlQuery *q, QofBook *book);
+
+/** \brief Perform the query, return the results.
+
+ * The book must be set in order to be able to perform a query.
+ *
+ * The returned list will have been sorted using the indicated sort
+ * order, (by default ascending order) and trimmed to the
+ * max_results length.
+ * Do NOT free the resulting list. This list is managed internally
+ * by QofSqlQuery.
+ *
+ */
+
+GList * qof_sql_query_run (QofSqlQuery *query, const char * str);
+
+/** Same ::qof_sql_query_run, but just parse/pre-process the query; do
+ not actually run it over the dataset. The QofBook does not
+ need to be set before calling this function.
+*/
+
+void qof_sql_query_parse (QofSqlQuery *query, const char * str);
+
+/** Return the QofQuery form of the previously parsed query. */
+QofQuery * qof_sql_query_get_query (QofSqlQuery *);
+
+/** Run the previously parsed query. The QofBook must be set
+ * before this function can be called. Note, teh QofBook can
+ * be changed between each successive call to this routine.
+ * This routine can be called after either qof_sql_query_parse()
+ * or qof_sql_query_run() because both will set up the parse.
+ */
+GList * qof_sql_query_rerun (QofSqlQuery *query);
+
+/**
+ * Set the kvp frame to be used for formulating 'indirect' predicates.
+ *
+ * Although joins are not supported (see above), there is one special
+ * hack that one can use to pass data indirectly into the predicates.
+ * This is by using a KVP key name to reference the value to be used
+ * for a predicate. Thus, for example,
+ * SELECT * FROM SomeObj WHERE (param_a = KVP:/some/key/path);
+ * will look up the value stored at '/some/key/path', and use that
+ * value to form the actual predicate. So, for example, if
+ * the value stored at '/some/key/path' was 2, then the actual
+ * query run will be
+ * SELECT * FROM SomeObj WHERE (param_a = 2);
+ * The lookup occurs at the time that the query is formulated.
+ *
+ * The query does *not* take over ownership of the kvp frame,
+ * nor does it copy it. Thus, the kvp frame must exist when the
+ * query is formulated, and it is the responsibility of the
+ * caller to free it when no longer needed.
+ *
+ * Note that because this feature is a kind of a hack put in place
+ * due to the lack of support for joins, it will probably go away
+ * (be deprecated) if/when joins are implemented.
+ */
+void qof_sql_query_set_kvp (QofSqlQuery *, KvpFrame *);
+
+/** @} */
+#endif /* QOF_SQL_QUERY_H */
+/** @} */
More information about the gnucash-changes
mailing list