Files
gnucash/src/backend/file/io-gncxml-v2.c
T
Joshua Sled d372dbb81a Restore enter/leave indentation. Cleanup some deprecated and (nearly-)unused qoflog code. Audit fraction enter/leave pairs. :/
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@15539 57a11ea4-9604-0410-9ed3-97b8803252fd
2007-02-09 21:52:09 +00:00

1839 lines
50 KiB
C

/********************************************************************\
* Copyright (C) 2000,2001 Gnumatic Inc. *
* *
* 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 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
#include "config.h"
#include <glib.h>
#include <glib/gstdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <zlib.h>
#include <errno.h>
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#include "gnc-engine.h"
#include "gnc-pricedb-p.h"
#include "Group.h"
#include "GroupP.h"
#include "Scrub.h"
#include "SX-book.h"
#include "SX-book-p.h"
#include "Transaction.h"
#include "TransactionP.h"
#include "TransLog.h"
#include "sixtp-dom-parsers.h"
#include "io-gncxml-v2.h"
#include "io-gncxml-gen.h"
#include "sixtp.h"
#include "sixtp-parsers.h"
#include "sixtp-utils.h"
#include "gnc-xml.h"
#include "io-utils.h"
static QofLogModule log_module = GNC_MOD_IO;
static pid_t gzip_child_pid = 0;
/* Callback structure */
struct file_backend {
gboolean ok;
gpointer data;
sixtp_gdv2 * gd;
const char * tag;
sixtp * parser;
FILE * out;
QofBook * book;
};
#define GNC_V2_STRING "gnc-v2"
extern const gchar *gnc_v2_book_version_string; /* see gnc-book-xml-v2 */
void
run_callback(sixtp_gdv2 *data, const char *type)
{
if(data->countCallback)
{
data->countCallback(data, type);
}
}
static void
clear_up_account_commodity(
gnc_commodity_table *tbl, Account *act,
gnc_commodity * (*getter) (const Account *account),
void (*setter) (Account *account, gnc_commodity *comm),
int (*scu_getter) (const Account *account),
void (*scu_setter) (Account *account, int scu))
{
gnc_commodity *gcom;
gnc_commodity *com = getter(act);
int old_scu;
if (scu_getter)
old_scu = scu_getter(act);
else
old_scu = 0;
if(!com)
{
return;
}
gcom = gnc_commodity_table_lookup(tbl, gnc_commodity_get_namespace(com),
gnc_commodity_get_mnemonic(com));
if (gcom == com)
{
return;
}
else if(!gcom)
{
PWARN("unable to find global commodity for %s adding new",
gnc_commodity_get_unique_name(com));
gnc_commodity_table_insert(tbl, com);
}
else
{
setter(act, gcom);
if (old_scu != 0 && scu_setter)
scu_setter(act, old_scu);
gnc_commodity_destroy(com);
}
}
static void
clear_up_transaction_commodity(
gnc_commodity_table *tbl, Transaction *trans,
gnc_commodity * (*getter) (const Transaction *trans),
void (*setter) (Transaction *trans, gnc_commodity *comm))
{
gnc_commodity *gcom;
gnc_commodity *com = getter(trans);
if(!com)
{
return;
}
gcom = gnc_commodity_table_lookup(tbl, gnc_commodity_get_namespace(com),
gnc_commodity_get_mnemonic(com));
if(gcom == com)
{
return;
}
else if(!gcom)
{
PWARN("unable to find global commodity for %s adding new",
gnc_commodity_get_unique_name(com));
gnc_commodity_table_insert(tbl, com);
}
else
{
xaccTransBeginEdit(trans);
setter(trans, gcom);
xaccTransCommitEdit(trans);
gnc_commodity_destroy(com);
}
}
static gboolean
add_account_local(sixtp_gdv2 *data, Account *act)
{
gnc_commodity_table *table;
table = gnc_book_get_commodity_table (data->book);
clear_up_account_commodity(table, act,
DxaccAccountGetCurrency,
DxaccAccountSetCurrency,
NULL, NULL);
clear_up_account_commodity(table, act,
xaccAccountGetCommodity,
xaccAccountSetCommodity,
xaccAccountGetCommoditySCUi,
xaccAccountSetCommoditySCU);
xaccAccountScrubCommodity (act);
xaccAccountScrubKvp (act);
if(!xaccAccountGetParent(act))
{
xaccGroupInsertAccount(gnc_book_get_group(data->book), act);
}
data->counter.accounts_loaded++;
run_callback(data, "account");
return FALSE;
}
static gboolean
add_book_local(sixtp_gdv2 *data, QofBook *book)
{
data->counter.books_loaded++;
run_callback(data, "book");
return FALSE;
}
static gboolean
add_commodity_local(sixtp_gdv2 *data, gnc_commodity *com)
{
gnc_commodity_table *table;
table = gnc_book_get_commodity_table (data->book);
gnc_commodity_table_insert(table, com);
data->counter.commodities_loaded++;
run_callback(data, "commodities");
return TRUE;
}
static gboolean
add_transaction_local(sixtp_gdv2 *data, Transaction *trn)
{
gnc_commodity_table *table;
table = gnc_book_get_commodity_table (data->book);
xaccTransBeginEdit (trn);
clear_up_transaction_commodity(table, trn,
xaccTransGetCurrency,
xaccTransSetCurrency);
xaccTransScrubCurrency (trn);
xaccTransCommitEdit (trn);
data->counter.transactions_loaded++;
run_callback(data, "transaction");
return TRUE;
}
static gboolean
add_schedXaction_local(sixtp_gdv2 *data, SchedXaction *sx)
{
SchedXactions *sxes;
sxes = gnc_book_get_schedxactions(data->book);
gnc_sxes_add_sx(sxes, sx);
data->counter.schedXactions_loaded++;
run_callback(data, "schedXactions");
return TRUE;
}
static gboolean
add_template_transaction_local( sixtp_gdv2 *data,
gnc_template_xaction_data *txd )
{
GList *n;
Account *tmpAcct;
AccountGroup *acctGroup = NULL;
QofBook *book;
book = data->book;
/* expect a struct of: */
/* . template accounts. */
/* . transactions in those accounts. */
for ( n = txd->accts; n; n = n->next ) {
if ( xaccAccountGetParent( (Account*)n->data ) == NULL ) {
/* remove the gnc_book_init-created account of the same name */
acctGroup =
gnc_book_get_template_group(book);
tmpAcct =
xaccGetAccountFromName( acctGroup,
xaccAccountGetName( (Account*)n->data ) );
if ( tmpAcct != NULL ) {
/* XXX hack alert FIXME .... Should this be 'Remove', or 'Destroy'?
* If we just remove, then this seems to be a memory leak to me, since
* it is never reparented. Shouldn't it be a Destroy ???
*/
xaccGroupRemoveAccount( acctGroup, tmpAcct );
}
xaccGroupInsertAccount( acctGroup, (Account*)n->data );
}
}
for ( n = txd->transactions; n; n = n->next ) {
/* insert transactions into accounts */
add_transaction_local( data, (Transaction*)n->data );
}
xaccAccountGroupCommitEdit (acctGroup);
return TRUE;
}
static gboolean
add_pricedb_local(sixtp_gdv2 *data, GNCPriceDB *db)
{
/* gnc_pricedb_print_contents(db, stdout); */
return TRUE;
}
static void
do_counter_cb (const char *type, gpointer data_p, gpointer be_data_p)
{
GncXmlDataType_t *data = data_p;
struct file_backend *be_data = be_data_p;
g_return_if_fail (type && data && be_data);
g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
if (be_data->ok == TRUE)
return;
if (!safe_strcmp (be_data->tag, data->type_name))
be_data->ok = TRUE;
/* XXX: should we do anything with this counter? */
}
static gboolean
gnc_counter_end_handler(gpointer data_for_children,
GSList* data_from_children, GSList* sibling_data,
gpointer parent_data, gpointer global_data,
gpointer *result, const gchar *tag)
{
char *strval;
gint64 val;
char *type;
xmlNodePtr tree = (xmlNodePtr)data_for_children;
gxpf_data *gdata = (gxpf_data*)global_data;
sixtp_gdv2 *sixdata = (sixtp_gdv2*)gdata->parsedata;
gboolean ret = TRUE;
if (parent_data)
return TRUE;
/* OK. For some messed up reason this is getting called again with a
NULL tag. So we ignore those cases */
if (!tag)
return TRUE;
g_return_val_if_fail(tree, FALSE);
/* Note: BADXML.
*
* This is invalid xml because the namespace isn't declared in the
* tag itself. This should be changed to 'type' at some point. */
type = (char*)xmlGetProp(tree, BAD_CAST "cd:type");
strval = dom_tree_to_text(tree);
if (!string_to_gint64(strval, &val))
{
PERR ("string_to_gint64 failed with input: %s",
strval ? strval : "(null)");
ret = FALSE;
}
else if (safe_strcmp(type, "transaction") == 0)
{
sixdata->counter.transactions_total = val;
}
else if (safe_strcmp(type, "account") == 0)
{
sixdata->counter.accounts_total = val;
}
else if (safe_strcmp(type, "book") == 0)
{
sixdata->counter.books_total = val;
}
else if (safe_strcmp(type, "commodity") == 0)
{
sixdata->counter.commodities_total = val;
}
else if (safe_strcmp(type, "schedxaction") == 0)
{
sixdata->counter.schedXactions_total = val;
}
else if (safe_strcmp(type, "budget") == 0)
{
sixdata->counter.budgets_total = val;
}
else
{
struct file_backend be_data;
be_data.ok = FALSE;
be_data.tag = type;
qof_object_foreach_backend (GNC_FILE_BACKEND, do_counter_cb, &be_data);
if (be_data.ok == FALSE)
{
PERR("Unknown type: %s", type ? type : "(null)");
/* Do *NOT* flag this as an error. Gnucash 1.8 writes invalid
* xml by writing the 'cd:type' attribute without providing
* the namespace in the gnc:count-data tag. The parser is
* entirely within its rights to refuse to read this bad
* attribute. Gnucash will function correctly without the data
* in this tag, so just let the error pass. */
ret = TRUE;
}
}
g_free (strval);
xmlFree (type);
xmlFreeNode(tree);
return ret;
}
static sixtp*
gnc_counter_sixtp_parser_create(void)
{
return sixtp_dom_parser_new(gnc_counter_end_handler, NULL, NULL);
}
static void
debug_print_counter_data(load_counter *data)
{
DEBUG("Transactions: Total: %d, Loaded: %d",
data->transactions_total, data->transactions_loaded);
DEBUG("Accounts: Total: %d, Loaded: %d",
data->accounts_total, data->accounts_loaded);
DEBUG("Books: Total: %d, Loaded: %d",
data->books_total, data->books_loaded);
DEBUG("Commodities: Total: %d, Loaded: %d",
data->commodities_total, data->commodities_loaded);
DEBUG("Scheduled Tansactions: Total: %d, Loaded: %d",
data->schedXactions_total, data->schedXactions_loaded);
DEBUG("Budgets: Total: %d, Loaded: %d",
data->budgets_total, data->budgets_loaded);
}
static void
file_rw_feedback (sixtp_gdv2 *gd, const char *type)
{
load_counter *counter;
int loaded, total, percentage;
g_assert(gd != NULL);
if (!gd->gui_display_fn)
return;
counter = &gd->counter;
loaded = counter->transactions_loaded + counter->accounts_loaded +
counter->books_loaded + counter->commodities_loaded +
counter->schedXactions_loaded + counter->budgets_loaded;
total = counter->transactions_total + counter->accounts_total +
counter->books_total + counter->commodities_total +
counter->schedXactions_total + counter->budgets_total;
if (total == 0)
total = 1;
percentage = (loaded * 100)/total;
if (percentage > 100) {
/* FIXME: Perhaps the below should be replaced by:
print_counter_data(counter); */
// printf("Transactions: Total: %d, Loaded: %d\n",
// counter->transactions_total, counter->transactions_loaded);
// printf("Accounts: Total: %d, Loaded: %d\n",
// counter->accounts_total, counter->accounts_loaded);
// printf("Books: Total: %d, Loaded: %d\n",
// counter->books_total, counter->books_loaded);
// printf("Commodities: Total: %d, Loaded: %d\n",
// counter->commodities_total, counter->commodities_loaded);
// printf("Scheduled Tansactions: Total: %d, Loaded: %d\n",
// counter->schedXactions_total, counter->schedXactions_loaded);
// printf("Budgets: Total: %d, Loaded: %d\n",
// counter->budgets_total, counter->budgets_loaded);
}
gd->gui_display_fn(NULL, percentage);
}
static const char *BOOK_TAG = "gnc:book";
static const char *BOOK_ID_TAG = "book:id";
static const char *BOOK_SLOTS_TAG = "book:slots";
static const char *ACCOUNT_TAG = "gnc:account";
static const char *PRICEDB_TAG = "gnc:pricedb";
static const char *COMMODITY_TAG = "gnc:commodity";
static const char *COUNT_DATA_TAG = "gnc:count-data";
static const char *TRANSACTION_TAG = "gnc:transaction";
static const char *SCHEDXACTION_TAG = "gnc:schedxaction";
static const char *TEMPLATE_TRANSACTION_TAG = "gnc:template-transactions";
static const char *BUDGET_TAG = "gnc:budget";
static void
add_item_cb (const char *type, gpointer data_p, gpointer be_data_p)
{
GncXmlDataType_t *data = data_p;
struct file_backend *be_data = be_data_p;
g_return_if_fail (type && data && be_data);
g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
if (be_data->ok)
return;
if (!safe_strcmp (be_data->tag, data->type_name)) {
if (data->add_item)
(data->add_item)(be_data->gd, be_data->data);
be_data->ok = TRUE;
}
}
static gboolean
book_callback(const char *tag, gpointer globaldata, gpointer data)
{
sixtp_gdv2 *gd = (sixtp_gdv2*)globaldata;
if(safe_strcmp(tag, ACCOUNT_TAG) == 0)
{
add_account_local(gd, (Account*)data);
}
else if(safe_strcmp(tag, PRICEDB_TAG) == 0)
{
add_pricedb_local(gd, (GNCPriceDB*)data);
}
else if(safe_strcmp(tag, COMMODITY_TAG) == 0)
{
add_commodity_local(gd, (gnc_commodity*)data);
}
else if(safe_strcmp(tag, TRANSACTION_TAG) == 0)
{
add_transaction_local(gd, (Transaction*)data);
}
else if(safe_strcmp(tag, SCHEDXACTION_TAG) == 0)
{
add_schedXaction_local(gd, (SchedXaction*)data);
}
else if(safe_strcmp(tag, TEMPLATE_TRANSACTION_TAG) == 0)
{
add_template_transaction_local( gd, (gnc_template_xaction_data*)data );
}
else if(safe_strcmp(tag, BUDGET_TAG) == 0)
{
// Nothing needed here.
}
else
{
struct file_backend be_data;
be_data.ok = FALSE;
be_data.tag = tag;
be_data.gd = gd;
be_data.data = data;
qof_object_foreach_backend (GNC_FILE_BACKEND, add_item_cb, &be_data);
if (be_data.ok == FALSE)
{
PWARN ("unexpected tag %s", tag);
}
}
return TRUE;
}
static gboolean
generic_callback(const char *tag, gpointer globaldata, gpointer data)
{
sixtp_gdv2 *gd = (sixtp_gdv2*)globaldata;
if(safe_strcmp(tag, BOOK_TAG) == 0)
{
add_book_local(gd, (QofBook*)data);
book_callback(tag, globaldata, data);
}
else
{
// PWARN ("importing pre-book-style XML data file");
book_callback(tag, globaldata, data);
}
return TRUE;
}
static void
add_parser_cb (const char *type, gpointer data_p, gpointer be_data_p)
{
GncXmlDataType_t *data = data_p;
struct file_backend *be_data = be_data_p;
g_return_if_fail (type && data && be_data);
g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
if (be_data->ok == FALSE)
return;
if (data->create_parser)
if(!sixtp_add_some_sub_parsers(
be_data->parser, TRUE,
data->type_name, (data->create_parser)(),
NULL, NULL))
be_data->ok = FALSE;
}
static void
scrub_cb (const char *type, gpointer data_p, gpointer be_data_p)
{
GncXmlDataType_t *data = data_p;
struct file_backend *be_data = be_data_p;
g_return_if_fail (type && data && be_data);
g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
if (data->scrub)
(data->scrub)(be_data->book);
}
static sixtp_gdv2 *
gnc_sixtp_gdv2_new (
QofBook *book,
gboolean exporting,
countCallbackFn countcallback,
QofBePercentageFunc gui_display_fn)
{
sixtp_gdv2 *gd = g_new0(sixtp_gdv2, 1);
if (gd == NULL) return NULL;
gd->book = book;
gd->counter.accounts_loaded = 0;
gd->counter.accounts_total = 0;
gd->counter.books_loaded = 0;
gd->counter.books_total = 0;
gd->counter.commodities_loaded = 0;
gd->counter.commodities_total = 0;
gd->counter.transactions_loaded = 0;
gd->counter.transactions_total = 0;
gd->counter.prices_loaded = 0;
gd->counter.prices_total = 0;
gd->counter.schedXactions_loaded = 0;
gd->counter.schedXactions_total = 0;
gd->counter.budgets_loaded = 0;
gd->counter.budgets_total = 0;
gd->exporting = exporting;
gd->countCallback = countcallback;
gd->gui_display_fn = gui_display_fn;
return gd;
}
static gboolean
qof_session_load_from_xml_file_v2_full(
FileBackend *fbe, QofBook *book,
sixtp_push_handler push_handler, gpointer push_user_data)
{
AccountGroup *grp;
QofBackend *be = &fbe->be;
sixtp_gdv2 *gd;
sixtp *top_parser;
sixtp *main_parser;
sixtp *book_parser;
struct file_backend be_data;
gboolean retval;
gd = gnc_sixtp_gdv2_new(book, FALSE, file_rw_feedback, be->percentage);
top_parser = sixtp_new();
main_parser = sixtp_new();
book_parser = sixtp_new();
if(!sixtp_add_some_sub_parsers(
top_parser, TRUE,
GNC_V2_STRING, main_parser,
NULL, NULL))
{
goto bail;
}
if(!sixtp_add_some_sub_parsers(
main_parser, TRUE,
COUNT_DATA_TAG, gnc_counter_sixtp_parser_create(),
BOOK_TAG, book_parser,
/* the following are present here only to support
* the older, pre-book format. Basically, the top-level
* book is implicit. */
PRICEDB_TAG, gnc_pricedb_sixtp_parser_create(),
COMMODITY_TAG, gnc_commodity_sixtp_parser_create(),
ACCOUNT_TAG, gnc_account_sixtp_parser_create(),
TRANSACTION_TAG, gnc_transaction_sixtp_parser_create(),
SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create(),
TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create(),
NULL, NULL))
{
goto bail;
}
if(!sixtp_add_some_sub_parsers(
book_parser, TRUE,
BOOK_ID_TAG, gnc_book_id_sixtp_parser_create(),
BOOK_SLOTS_TAG, gnc_book_slots_sixtp_parser_create(),
COUNT_DATA_TAG, gnc_counter_sixtp_parser_create(),
PRICEDB_TAG, gnc_pricedb_sixtp_parser_create(),
COMMODITY_TAG, gnc_commodity_sixtp_parser_create(),
ACCOUNT_TAG, gnc_account_sixtp_parser_create(),
BUDGET_TAG, gnc_budget_sixtp_parser_create(),
TRANSACTION_TAG, gnc_transaction_sixtp_parser_create(),
SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create(),
TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create(),
NULL, NULL))
{
goto bail;
}
be_data.ok = TRUE;
be_data.parser = book_parser;
qof_object_foreach_backend (GNC_FILE_BACKEND, add_parser_cb, &be_data);
if (be_data.ok == FALSE)
goto bail;
/* stop logging while we load */
xaccLogDisable ();
xaccDisableDataScrubbing();
if (push_handler) {
gpointer parse_result = NULL;
gxpf_data gpdata;
gpdata.cb = generic_callback;
gpdata.parsedata = gd;
gpdata.bookdata = book;
retval = sixtp_parse_push(top_parser, push_handler, push_user_data,
NULL, &gpdata, &parse_result);
} else {
retval = gnc_xml_parse_file(top_parser, fbe->fullpath,
generic_callback, gd, book);
}
if (!retval) {
sixtp_destroy(top_parser);
xaccLogEnable ();
xaccEnableDataScrubbing();
goto bail;
}
debug_print_counter_data(&gd->counter);
/* destroy the parser */
sixtp_destroy (top_parser);
g_free(gd);
xaccEnableDataScrubbing();
/* Mark the book as saved */
qof_book_mark_saved (book);
/* Call individual scrub functions */
memset(&be_data, 0, sizeof(be_data));
be_data.book = book;
qof_object_foreach_backend (GNC_FILE_BACKEND, scrub_cb, &be_data);
/* fix price quote sources */
grp = gnc_book_get_group(book);
xaccGroupScrubQuoteSources (grp, gnc_book_get_commodity_table(book));
/* Fix account and transaction commodities */
xaccGroupScrubCommodities (grp);
/* Fix split amount/value */
xaccGroupScrubSplits (grp);
/* commit all groups, this completes the BeginEdit started when the
* account_end_handler finished reading the account.
*/
xaccAccountGroupCommitEdit (grp);
/* start logging again */
xaccLogEnable ();
return TRUE;
bail:
g_free(gd);
return FALSE;
}
gboolean
qof_session_load_from_xml_file_v2(FileBackend *fbe, QofBook *book)
{
return qof_session_load_from_xml_file_v2_full(fbe, book, NULL, NULL);
}
/***********************************************************************/
static void
write_counts(FILE* out, ...)
{
va_list ap;
char *type;
va_start(ap, out);
type = va_arg(ap, char *);
while(type)
{
int amount = va_arg(ap, int);
if(amount != 0)
{
#if GNUCASH_REALLY_BUILD_AN_XML_TREE_ON_OUTPUT
char *val;
xmlNodePtr node;
val = g_strdup_printf("%d", amount);
node = xmlNewNode(NULL, BAD_CAST COUNT_DATA_TAG);
/* Note: BADXML.
*
* This is invalid xml because the namespace isn't
* declared in the tag itself. This should be changed to
* 'type' at some point. */
xmlSetProp(node, BAD_CAST "cd:type", BAD_CAST type);
xmlNodeAddContent(node, BAD_CAST val);
xmlElemDump(out, NULL, node);
fprintf(out, "\n");
g_free(val);
xmlFreeNode(node);
#else
fprintf(out, "<%s %s=\"%s\">%d</%s>\n",
COUNT_DATA_TAG, "cd:type", type, amount, COUNT_DATA_TAG);
#endif
}
type = va_arg(ap, char *);
}
va_end(ap);
}
static gint
compare_namespaces(gconstpointer a, gconstpointer b)
{
const gchar *sa = (const gchar *) a;
const gchar *sb = (const gchar *) b;
return(safe_strcmp(sa, sb));
}
static gint
compare_commodity_ids(gconstpointer a, gconstpointer b)
{
const gnc_commodity *ca = (const gnc_commodity *) a;
const gnc_commodity *cb = (const gnc_commodity *) b;
return(safe_strcmp(gnc_commodity_get_mnemonic(ca),
gnc_commodity_get_mnemonic(cb)));
}
static void write_pricedb (FILE *out, QofBook *book, sixtp_gdv2 *gd);
static void write_transactions (FILE *out, QofBook *book, sixtp_gdv2 *gd);
static void write_template_transaction_data (FILE *out, QofBook *book, sixtp_gdv2 *gd);
static void write_schedXactions(FILE *out, QofBook *book, sixtp_gdv2 *gd);
static void write_budget (QofEntity *ent, gpointer data);
static void
write_counts_cb (const char *type, gpointer data_p, gpointer be_data_p)
{
GncXmlDataType_t *data = data_p;
struct file_backend *be_data = be_data_p;
g_return_if_fail (type && data && be_data);
g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
if (data->get_count)
write_counts (be_data->out, data->type_name,
(data->get_count) (be_data->book),
NULL);
}
static void
write_data_cb (const char *type, gpointer data_p, gpointer be_data_p)
{
GncXmlDataType_t *data = data_p;
struct file_backend *be_data = be_data_p;
g_return_if_fail (type && data && be_data);
g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
if (data->write)
(data->write)(be_data->out, be_data->book);
}
static void
write_book(FILE *out, QofBook *book, sixtp_gdv2 *gd)
{
struct file_backend be_data;
#ifdef IMPLEMENT_BOOK_DOM_TREES_LATER
/* We can't just blast out the dom tree, because the dom tree
* doesn't have the books, transactions, etc underneath it.
* But that is just as well, since I think the performance
* will be much better if we write out as we go along
*/
xmlNodePtr node;
node = gnc_book_dom_tree_create(book);
if(!node)
{
return;
}
xmlElemDump(out, NULL, node);
if(fprintf(out, "\n") < 0)
{
qof_backend_set_error(qof_book_get_backend(book), ERR_FILEIO_WRITE_ERROR);
return;
}
xmlFreeNode(node);
#endif
be_data.out = out;
be_data.book = book;
be_data.gd = gd;
if(fprintf( out, "<%s version=\"%s\">\n", BOOK_TAG, gnc_v2_book_version_string) < 0)
{
qof_backend_set_error(qof_book_get_backend(book), ERR_FILEIO_WRITE_ERROR);
return;
}
write_book_parts (out, book);
/* gd->counter.{foo}_total fields should have all these totals
already collected. I don't know why we're re-calling all these
functions. */
write_counts(out,
"commodity",
gnc_commodity_table_get_size(
gnc_book_get_commodity_table(book)),
"account",
1 + xaccGroupGetNumSubAccounts(gnc_book_get_group(book)),
"transaction",
gnc_book_count_transactions(book),
"schedxaction",
g_list_length(gnc_book_get_schedxactions(book)->sx_list),
"budget", qof_collection_count(
qof_book_get_collection(book, GNC_ID_BUDGET)),
NULL);
qof_object_foreach_backend (GNC_FILE_BACKEND, write_counts_cb, &be_data);
write_commodities(out, book, gd);
write_pricedb(out, book, gd);
write_accounts(out, book, gd);
write_transactions(out, book, gd);
write_template_transaction_data(out, book, gd);
write_schedXactions(out, book, gd);
qof_collection_foreach(qof_book_get_collection(book, GNC_ID_BUDGET),
write_budget, &be_data);
qof_object_foreach_backend (GNC_FILE_BACKEND, write_data_cb, &be_data);
if(fprintf( out, "</%s>\n", BOOK_TAG ) < 0) {
qof_backend_set_error(qof_book_get_backend(book), ERR_FILEIO_WRITE_ERROR);
}
}
void
write_commodities(FILE *out, QofBook *book, sixtp_gdv2 *gd)
{
gnc_commodity_table *tbl;
GList *namespaces;
GList *lp;
tbl = gnc_book_get_commodity_table(book);
namespaces = gnc_commodity_table_get_namespaces(tbl);
if(namespaces)
{
namespaces = g_list_sort(namespaces, compare_namespaces);
}
for(lp = namespaces; lp; lp = lp->next)
{
GList *comms, *lp2;
xmlNodePtr comnode;
comms = gnc_commodity_table_get_commodities(tbl, lp->data);
comms = g_list_sort(comms, compare_commodity_ids);
for(lp2 = comms; lp2; lp2 = lp2->next) {
comnode = gnc_commodity_dom_tree_create(lp2->data);
if (comnode == NULL)
continue;
xmlElemDump(out, NULL, comnode);
fprintf(out, "\n");
xmlFreeNode(comnode);
gd->counter.commodities_loaded++;
run_callback(gd, "commodities");
}
g_list_free (comms);
}
if (namespaces) g_list_free (namespaces);
}
static void
write_pricedb(FILE *out, QofBook *book, sixtp_gdv2 *gd)
{
xmlNodePtr node;
node = gnc_pricedb_dom_tree_create(gnc_book_get_pricedb(book));
if(!node)
{
return;
}
xmlElemDump(out, NULL, node);
fprintf(out, "\n");
xmlFreeNode(node);
}
static int
xml_add_trn_data(Transaction *t, gpointer data)
{
struct file_backend *be_data = data;
xmlNodePtr node;
node = gnc_transaction_dom_tree_create(t);
xmlElemDump(be_data->out, NULL, node);
fprintf(be_data->out, "\n");
xmlFreeNode(node);
be_data->gd->counter.transactions_loaded++;
run_callback(be_data->gd, "transaction");
return 0;
}
static void
write_transactions(FILE *out, QofBook *book, sixtp_gdv2 *gd)
{
struct file_backend be_data;
be_data.out = out;
be_data.gd = gd;
xaccGroupForEachTransaction(gnc_book_get_group(book),
xml_add_trn_data,
(gpointer) &be_data);
}
static void
write_template_transaction_data( FILE *out, QofBook *book, sixtp_gdv2 *gd )
{
AccountGroup *ag;
struct file_backend be_data;
be_data.out = out;
be_data.gd = gd;
ag = gnc_book_get_template_group(book);
if ( xaccGroupGetNumSubAccounts(ag) > 0 )
{
fprintf( out, "<%s>\n", TEMPLATE_TRANSACTION_TAG );
write_account_group( out, ag, gd );
xaccGroupForEachTransaction( ag, xml_add_trn_data, (gpointer)&be_data );
fprintf( out, "</%s>\n", TEMPLATE_TRANSACTION_TAG );
}
}
static void
write_schedXactions( FILE *out, QofBook *book, sixtp_gdv2 *gd)
{
GList *schedXactions;
SchedXaction *tmpSX;
xmlNodePtr node;
schedXactions = gnc_book_get_schedxactions(book)->sx_list;
if ( schedXactions == NULL )
return;
do {
tmpSX = schedXactions->data;
node = gnc_schedXaction_dom_tree_create( tmpSX );
xmlElemDump( out, NULL, node );
fprintf( out, "\n" );
xmlFreeNode( node );
gd->counter.schedXactions_loaded++;
run_callback(gd, "schedXactions");
} while ( (schedXactions = schedXactions->next) );
}
static void
write_budget (QofEntity *ent, gpointer data)
{
xmlNodePtr node;
struct file_backend* be = data;
GncBudget *bgt = GNC_BUDGET(ent);
node = gnc_budget_dom_tree_create(bgt);
xmlElemDump( be->out, NULL, node );
fprintf( be->out, "\n" );
xmlFreeNode( node );
be->gd->counter.budgets_loaded++;
run_callback(be->gd, "budgets");
}
void
gnc_xml2_write_namespace_decl (FILE *out, const char *namespace)
{
g_return_if_fail (namespace);
fprintf(out, "\n xmlns:%s=\"http://www.gnucash.org/XML/%s\"",
namespace, namespace);
}
static void
do_write_namespace_cb (const char *type, gpointer data_p, gpointer file_p)
{
GncXmlDataType_t *data = data_p;
FILE *out = file_p;
g_return_if_fail (type && data && out);
g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
if (data->ns)
(data->ns)(out);
}
static void
write_v2_header (FILE *out)
{
fprintf(out, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
fprintf(out, "<" GNC_V2_STRING);
gnc_xml2_write_namespace_decl (out, "gnc");
gnc_xml2_write_namespace_decl (out, "act");
gnc_xml2_write_namespace_decl (out, "book");
gnc_xml2_write_namespace_decl (out, "cd");
gnc_xml2_write_namespace_decl (out, "cmdty");
gnc_xml2_write_namespace_decl (out, "price");
gnc_xml2_write_namespace_decl (out, "slot");
gnc_xml2_write_namespace_decl (out, "split");
gnc_xml2_write_namespace_decl (out, "sx");
gnc_xml2_write_namespace_decl (out, "trn");
gnc_xml2_write_namespace_decl (out, "ts");
gnc_xml2_write_namespace_decl (out, "fs");
gnc_xml2_write_namespace_decl (out, "bgt");
gnc_xml2_write_namespace_decl (out, "recurrence");
gnc_xml2_write_namespace_decl (out, "lot");
/* now cope with the plugins */
qof_object_foreach_backend (GNC_FILE_BACKEND, do_write_namespace_cb, out);
fprintf(out, ">\n");
}
gboolean
gnc_book_write_to_xml_filehandle_v2(QofBook *book, FILE *out)
{
QofBackend *be;
sixtp_gdv2 *gd;
if (!out) return FALSE;
write_v2_header (out);
write_counts(out,
"book", 1,
NULL);
be = qof_book_get_backend(book);
gd = gnc_sixtp_gdv2_new(book, FALSE, file_rw_feedback, be->percentage);
gd->counter.commodities_total =
gnc_commodity_table_get_size(gnc_book_get_commodity_table(book));
gd->counter.accounts_total = 1 +
xaccGroupGetNumSubAccounts(gnc_book_get_group(book));
gd->counter.transactions_total = gnc_book_count_transactions(book);
gd->counter.schedXactions_total =
g_list_length(gnc_book_get_schedxactions(book)->sx_list);
gd->counter.budgets_total = qof_collection_count(
qof_book_get_collection(book, GNC_ID_BUDGET));
write_book(out, book, gd);
fprintf(out, "</" GNC_V2_STRING ">\n\n");
g_free(gd);
return TRUE;
}
/*
* This function is called by the "export" code.
*/
gboolean
gnc_book_write_accounts_to_xml_filehandle_v2(QofBackend *be, QofBook *book, FILE *out)
{
gnc_commodity_table *table;
AccountGroup *grp;
int ncom, nacc;
sixtp_gdv2 *gd;
if (!out) return FALSE;
grp = gnc_book_get_group(book);
nacc = 1 + xaccGroupGetNumSubAccounts(grp);
table = gnc_book_get_commodity_table(book);
ncom = gnc_commodity_table_get_size(table);
write_v2_header (out);
write_counts(out,
"commodity", ncom,
"account", nacc,
NULL);
gd = gnc_sixtp_gdv2_new(book, TRUE, file_rw_feedback, be->percentage);
gd->counter.commodities_total = ncom;
gd->counter.accounts_total = nacc;
write_commodities(out, book, gd);
write_accounts(out, book, gd);
fprintf(out, "</" GNC_V2_STRING ">\n\n");
g_free(gd);
return TRUE;
}
#define BUFLEN 4096
static FILE *
try_gz_open (const char *filename, const char *perms, gboolean use_gzip,
gboolean compress)
{
if (strstr(filename, ".gz.") != NULL) /* its got a temp extension */
use_gzip = TRUE;
if (!use_gzip)
return g_fopen(filename, perms);
#ifdef G_OS_WIN32
PWARN("Compression not implemented on Windows. Opening uncompressed file.");
return g_fopen(filename, perms);
/* Potential implementation: Windows doesn't have pipe(); use
the g_spawn glib wrappers. */
{
/* Start gzip from a command line, not by fork(). */
gchar *argv[] = {compress ? "gzip" : "gunzip",
NULL};
GPid child_pid;
GError *error;
int child_stdin;
g_assert_not_reached(); /* Not yet correctly implemented. */
if ( !g_spawn_async_with_pipes(NULL, argv,
NULL, G_SPAWN_SEARCH_PATH,
NULL, NULL,
&child_pid,
&child_stdin, NULL, NULL,
&error) ) {
PWARN("G_spawn call failed. Opening uncompressed file.");
return g_fopen(filename, perms);
}
/* FIXME: Now need to set up the child process to write to the
file. */
return fdopen(child_stdin, compress ? "w" : "r");
/* Eventually the GPid must be cleanup up, but not here? */
/* g_spawn_close_pid(child_pid); */
}
#else
{
/* Normal Posix platform (non-windows) */
int filedes[2];
pid_t pid;
/* avoid reading from file that is still being written to
by a child process */
g_assert(gzip_child_pid == 0);
if (pipe(filedes) < 0) {
PWARN("Pipe call failed. Opening uncompressed file.");
return g_fopen(filename, perms);
}
pid = fork();
switch (pid) {
case -1:
PWARN("Fork call failed. Opening uncompressed file.");
return g_fopen(filename, perms);
case 0: /* child */ {
char buffer[BUFLEN];
unsigned bytes;
ssize_t written;
gzFile *file;
file = gzopen(filename, perms);
if (file == NULL) {
PWARN("child gzopen failed\n");
exit(0);
}
if (compress) {
close(filedes[1]);
while ((bytes = read(filedes[0], buffer, BUFLEN)) > 0)
gzwrite(file, buffer, bytes);
}
else
{
close(filedes[0]);
while ((bytes = gzread(file, buffer, BUFLEN)) > 0)
written = write(filedes[1], buffer, bytes);
}
gzclose(file);
_exit(0);
}
default: /* parent */
if (compress) {
/* the calling code must wait_for_gzip() */
gzip_child_pid = pid;
}
sleep(2);
if (compress) {
close(filedes[0]);
return fdopen(filedes[1], "w");
}
else
{
close(filedes[1]);
return fdopen(filedes[0], "r");
}
}
}
#endif
}
static gboolean
wait_for_gzip()
{
pid_t retval;
if (gzip_child_pid == 0)
return TRUE;
#ifdef HAVE_SYS_WAIT_H
retval = waitpid(gzip_child_pid, NULL, WUNTRACED);
#else
/* FIXME: Windows doesn't have waitpid. According to glib's
g_spawn_async_with_pipes(), we should use one of the
g_spawn functions and some Win32-API WaitFor*() function
here. For now, we ignore that race condition. */
retval = 1;
#endif
gzip_child_pid = 0;
return retval != -1;
}
gboolean
gnc_book_write_to_xml_file_v2(
QofBook *book,
const char *filename,
gboolean compress)
{
FILE *out;
out = try_gz_open(filename, "w", compress, TRUE);
if (out == NULL)
{
return FALSE;
}
gnc_book_write_to_xml_filehandle_v2 (book, out);
write_emacs_trailer(out);
if (fclose(out) != 0)
{
return FALSE;
}
if (compress)
return wait_for_gzip();
return TRUE;
}
/*
* Have to pass in the backend as this routine needs the temporary
* backend for file export, not the real backend which could be
* postgress or anything else.
*/
gboolean
gnc_book_write_accounts_to_xml_file_v2(
QofBackend *be,
QofBook *book,
const char *filename)
{
FILE *out;
out = g_fopen(filename, "w");
if (out == NULL)
{
return FALSE;
}
gnc_book_write_accounts_to_xml_filehandle_v2 (be, book, out);
write_emacs_trailer(out);
if (fclose(out) != 0)
{
return FALSE;
}
return TRUE;
}
/***********************************************************************/
static gboolean
is_gzipped_file(const gchar *name)
{
unsigned char buf[2];
int fd = g_open(name, O_RDONLY, 0);
if (fd == -1) {
return FALSE;
}
if (read(fd, buf, 2) != 2) {
close(fd);
return FALSE;
}
close(fd);
if (buf[0] == 037 && buf[1] == 0213) {
return TRUE;
}
return FALSE;
}
gboolean
gnc_is_xml_data_file_v2(const gchar *name, gboolean *with_encoding)
{
if (gnc_is_our_xml_file(name, GNC_V2_STRING, with_encoding))
return TRUE;
if (is_gzipped_file(name)) {
gzFile *file;
char first_chunk[256];
int num_read;
file = gzopen(name, "r");
if (file == NULL)
return FALSE;
num_read = gzread(file, first_chunk, sizeof(first_chunk) - 1);
gzclose(file);
if (num_read < 1)
return FALSE;
return gnc_is_our_first_xml_chunk(first_chunk, GNC_V2_STRING,
with_encoding);
}
return FALSE;
}
static void
replace_character_references(gchar *string)
{
gchar *cursor, *semicolon, *tail;
glong number;
for (cursor = strstr(string, "&#");
cursor && *cursor;
cursor = strstr(cursor, "&#")) {
semicolon = strchr(cursor, ';');
if (semicolon && *semicolon) {
/* parse number */
errno = 0;
if (*(cursor+2) == 'x') {
number = strtol(cursor+3, &tail, 16);
} else {
number = strtol(cursor+2, &tail, 10);
}
if (errno || tail != semicolon || number < 0 || number > 255) {
PWARN("Illegal character reference");
return;
}
/* overwrite '&' with the specified character */
*cursor = (gchar) number;
cursor++;
if (*(semicolon+1)) {
/* move text after semicolon the the left */
tail = g_strdup(semicolon+1);
strcpy(cursor, tail);
g_free(tail);
} else {
/* cut here */
*cursor = '\0';
}
} else {
PWARN("Unclosed character reference");
return;
}
}
}
static void
conv_free(conv_type *conv) {
if (conv) {
g_free(conv->utf8_string);
g_free(conv);
}
}
static void
conv_list_free(GList *conv_list) {
g_list_foreach(conv_list, (GFunc) conv_free, NULL);
g_list_free(conv_list);
}
typedef struct {
GQuark encoding;
GIConv iconv;
} iconv_item_type;
gint
gnc_xml2_find_ambiguous(const gchar *filename, GList *encodings,
GHashTable **unique, GHashTable **ambiguous,
GList **impossible)
{
FILE *file=NULL;
GList *iconv_list=NULL, *conv_list=NULL, *iter;
iconv_item_type *iconv_item=NULL, *ascii=NULL;
const gchar *enc;
GHashTable *processed=NULL;
gint n_impossible = 0;
GError *error=NULL;
gboolean is_compressed;
gboolean clean_return=FALSE;
is_compressed = is_gzipped_file(filename);
file = try_gz_open(filename, "r", is_compressed, FALSE);
if (file == NULL) {
PWARN("Unable to open file %s", filename);
goto cleanup_find_ambs;
}
/* we need ascii */
ascii = g_new(iconv_item_type, 1);
ascii->encoding = g_quark_from_string("ASCII");
ascii->iconv = g_iconv_open("UTF-8", "ASCII");
if (ascii->iconv == (GIConv) -1) {
PWARN("Unable to open ASCII ICONV conversion descriptor");
goto cleanup_find_ambs;
}
/* call iconv_open on encodings */
for (iter = encodings; iter; iter = iter->next) {
iconv_item = g_new(iconv_item_type, 1);
iconv_item->encoding = GPOINTER_TO_UINT (iter->data);
if (iconv_item->encoding == ascii->encoding) {
continue;
}
enc = g_quark_to_string(iconv_item->encoding);
iconv_item->iconv = g_iconv_open("UTF-8", enc);
if (iconv_item->iconv == (GIConv) -1) {
PWARN("Unable to open IConv conversion descriptor for '%s'", enc);
goto cleanup_find_ambs;
} else {
iconv_list = g_list_prepend(iconv_list, iconv_item);
}
}
/* prepare data containers */
if (unique)
*unique = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
(GDestroyNotify) conv_free);
if (ambiguous)
*ambiguous = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
(GDestroyNotify) conv_list_free);
if (impossible)
*impossible = NULL;
processed = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
/* loop through lines */
while (1) {
gchar line[256], *word, *utf8;
gchar **word_array, **word_cursor;
conv_type *conv = NULL;
if (!fgets(line, sizeof(line)-1, file)) {
if (feof(file)) {
break;
} else {
goto cleanup_find_ambs;
}
}
g_strchomp(line);
replace_character_references(line);
word_array = g_strsplit_set(line, "> <", 0);
/* loop through words */
for (word_cursor = word_array; *word_cursor; word_cursor++) {
word = *word_cursor;
if (!word)
continue;
utf8 = g_convert_with_iconv(word, -1, ascii->iconv,
NULL, NULL, &error);
if (utf8) {
/* pure ascii */
g_free(utf8);
continue;
}
g_error_free(error);
error = NULL;
if (g_hash_table_lookup_extended(processed, word, NULL, NULL)) {
/* already processed */
continue;
}
/* loop through encodings */
conv_list = NULL;
for (iter = iconv_list; iter; iter = iter->next) {
iconv_item = iter->data;
utf8 = g_convert_with_iconv(word, -1, iconv_item->iconv,
NULL, NULL, &error);
if (utf8) {
conv = g_new(conv_type, 1);
conv->encoding = iconv_item->encoding;
conv->utf8_string = utf8;
conv_list = g_list_prepend(conv_list, conv);
} else {
g_error_free(error);
error = NULL;
}
}
/* no successful conversion */
if (!conv_list) {
if (impossible)
*impossible = g_list_append(*impossible, g_strdup(word));
n_impossible++;
}
/* more than one successful conversion */
else if (conv_list->next) {
if (ambiguous) {
g_hash_table_insert(*ambiguous, g_strdup(word), conv_list);
} else {
conv_list_free(conv_list);
}
}
/* only one successful conversion */
else {
if (unique) {
g_hash_table_insert(*unique, g_strdup(word), conv);
} else {
conv_free(conv);
}
g_list_free(conv_list);
}
g_hash_table_insert(processed, g_strdup(word), NULL);
}
g_strfreev(word_array);
}
clean_return = TRUE;
cleanup_find_ambs:
if (iconv_list) {
for (iter = iconv_list; iter; iter = iter->next) {
if (iter->data) {
g_iconv_close(((iconv_item_type*) iter->data)->iconv);
g_free(iter->data);
}
}
g_list_free(iconv_list);
}
if (processed)
g_hash_table_destroy(processed);
if (ascii)
g_free(ascii);
if (file)
fclose(file);
return (clean_return) ? n_impossible : -1;
}
typedef struct {
gchar *filename;
GHashTable *subst;
} push_data_type;
static void
parse_with_subst_push_handler (xmlParserCtxtPtr xml_context,
push_data_type *push_data)
{
const gchar *filename;
FILE *file = NULL;
GIConv ascii=(GIConv)-1;
GString *output=NULL;
GError *error=NULL;
gboolean is_compressed;
filename = push_data->filename;
is_compressed = is_gzipped_file(filename);
file = try_gz_open(filename, "r", is_compressed, FALSE);
if (file == NULL) {
PWARN("Unable to open file %s", filename);
goto cleanup_push_handler;
}
ascii = g_iconv_open("UTF-8", "ASCII");
if (ascii == (GIConv) -1) {
PWARN("Unable to open ASCII ICONV conversion descriptor");
goto cleanup_push_handler;
}
/* loop through lines */
while (1) {
gchar line[256], *word, *repl, *utf8;
gint pos, len;
gchar *start, *cursor;
if (!fgets(line, sizeof(line)-1, file)) {
if (feof(file)) {
break;
} else {
goto cleanup_push_handler;
}
}
replace_character_references(line);
output = g_string_new(line);
/* loop through words */
cursor = output->str;
pos = 0;
while (1) {
/* ignore delimiters */
while (*cursor=='>' || *cursor==' ' || *cursor=='<' ||
*cursor=='\n') {
cursor++;
pos += 1;
}
if (!*cursor)
/* welcome to EOL */
break;
/* search for a delimiter */
start = cursor;
len = 0;
while (*cursor && *cursor!='>' && *cursor!=' ' && *cursor!='<' &&
*cursor!='\n') {
cursor++;
len++;
}
utf8 = g_convert_with_iconv(start, len, ascii, NULL, NULL, &error);
if (utf8) {
/* pure ascii */
g_free(utf8);
pos += len;
} else {
g_error_free(error);
error = NULL;
word = g_strndup(start, len);
repl = g_hash_table_lookup(push_data->subst, word);
g_free(word);
if (repl) {
/* there is a replacement */
output = g_string_insert(g_string_erase(output, pos, len),
pos, repl);
pos += strlen(repl);
cursor = output->str + pos;
} else {
/* there is no replacement, return immediately */
goto cleanup_push_handler;
}
}
}
if (xmlParseChunk(xml_context, output->str, output->len, 0) != 0) {
goto cleanup_push_handler;
}
}
/* last chunk */
xmlParseChunk(xml_context, "", 0, 1);
cleanup_push_handler:
if (output)
g_string_free(output, TRUE);
if (ascii != (GIConv) -1)
g_iconv_close(ascii);
if (file)
fclose(file);
}
gboolean
gnc_xml2_parse_with_subst (FileBackend *fbe, QofBook *book, GHashTable *subst)
{
push_data_type *push_data;
gboolean success;
push_data = g_new(push_data_type, 1);
push_data->filename = fbe->fullpath;
push_data->subst = subst;
success = qof_session_load_from_xml_file_v2_full(
fbe, book, (sixtp_push_handler) parse_with_subst_push_handler,
push_data);
if (success)
qof_book_kvp_changed(book);
return success;
}