mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
This will avoid a ninja-build from picking up a config.h generated by the autotools build (in the root build directory). Picking up the wrong config.h may lead to all kinds of subtle issues if the autotools run was done with different options than the cmake run.
2185 lines
60 KiB
C++
2185 lines
60 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 *
|
|
\********************************************************************/
|
|
extern "C"
|
|
{
|
|
#include <config.h>
|
|
|
|
#include <platform.h>
|
|
#if PLATFORM(WINDOWS)
|
|
#ifdef __STRICT_ANSI__
|
|
#undef __STRICT_ANSI__
|
|
#define __STRICT_ANSI_UNSET__ 1
|
|
#endif
|
|
#ifdef _NO_OLDNAMES
|
|
#undef _NO_OLDNAMES
|
|
#endif
|
|
#ifdef _UWIN
|
|
#undef _UWIN
|
|
#endif
|
|
#include <windows.h>
|
|
#endif
|
|
#include <glib.h>
|
|
#include <glib/gstdio.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
#include <zlib.h>
|
|
#include <errno.h>
|
|
|
|
#include "gnc-engine.h"
|
|
#include "gnc-pricedb-p.h"
|
|
#include "Scrub.h"
|
|
#include "SX-book.h"
|
|
#include "SX-book-p.h"
|
|
#include "Transaction.h"
|
|
#include "TransactionP.h"
|
|
#include "TransLog.h"
|
|
#if PLATFORM(WINDOWS)
|
|
#ifdef __STRICT_ANSI_UNSET__
|
|
#undef __STRICT_ANSI_UNSET__
|
|
#define __STRICT_ANSI__ 1
|
|
#endif
|
|
#endif
|
|
#if COMPILER(MSVC)
|
|
# define g_fopen fopen
|
|
# define g_open _open
|
|
#endif
|
|
}
|
|
|
|
#include "gnc-xml-backend.hpp"
|
|
#include "sixtp-parsers.h"
|
|
#include "sixtp-utils.h"
|
|
#include "gnc-xml.h"
|
|
#include "io-utils.h"
|
|
#include "sixtp-dom-parsers.h"
|
|
#include "io-gncxml-v2.h"
|
|
#include "io-gncxml-gen.h"
|
|
|
|
/* Do not treat -Wstrict-aliasing warnings as errors because of problems of the
|
|
* G_LOCK* macros as declared by glib. See
|
|
* http://bugzilla.gnome.org/show_bug.cgi?id=316221 for additional information.
|
|
*/
|
|
#if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 2)
|
|
# pragma GCC diagnostic warning "-Wstrict-aliasing"
|
|
#endif
|
|
|
|
static QofLogModule log_module = GNC_MOD_IO;
|
|
|
|
/* map pointers, e.g. of type FILE*, to GThreads */
|
|
static GHashTable* threads = NULL;
|
|
G_LOCK_DEFINE_STATIC (threads);
|
|
|
|
typedef struct
|
|
{
|
|
gint fd;
|
|
gchar* filename;
|
|
gchar* perms;
|
|
gboolean compress;
|
|
} gz_thread_params_t;
|
|
|
|
/* Callback structure */
|
|
struct file_backend
|
|
{
|
|
gboolean ok;
|
|
gpointer data;
|
|
sixtp_gdv2* gd;
|
|
const char* tag;
|
|
sixtp* parser;
|
|
FILE* out;
|
|
QofBook* book;
|
|
};
|
|
|
|
static std::vector<GncXmlDataType_t> backend_registry;
|
|
void
|
|
gnc_xml_register_backend(GncXmlDataType_t& xmlbe)
|
|
{
|
|
backend_registry.push_back(xmlbe);
|
|
}
|
|
|
|
#define GNC_V2_STRING "gnc-v2"
|
|
/* non-static because they are used in sixtp.c */
|
|
const gchar* gnc_v2_xml_version_string = GNC_V2_STRING;
|
|
extern const gchar*
|
|
gnc_v2_book_version_string; /* see gnc-book-xml-v2 */
|
|
|
|
/* Forward declarations */
|
|
static FILE* try_gz_open (const char* filename, const char* perms,
|
|
gboolean use_gzip,
|
|
gboolean compress);
|
|
static gboolean is_gzipped_file (const gchar* name);
|
|
static gboolean wait_for_gzip (FILE* file);
|
|
|
|
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;
|
|
Account* parent, *root;
|
|
int type;
|
|
|
|
table = gnc_commodity_table_get_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);
|
|
|
|
/* Backwards compatibility. If there's no parent, see if this
|
|
* account is of type ROOT. If not, find or create a ROOT
|
|
* account and make that the parent. */
|
|
type = xaccAccountGetType (act);
|
|
if (type == ACCT_TYPE_ROOT)
|
|
{
|
|
gnc_book_set_root_account (data->book, act);
|
|
}
|
|
else
|
|
{
|
|
parent = gnc_account_get_parent (act);
|
|
if (parent == NULL)
|
|
{
|
|
root = gnc_book_get_root_account (data->book);
|
|
gnc_account_append_child (root, act);
|
|
}
|
|
}
|
|
|
|
data->counter.accounts_loaded++;
|
|
sixtp_run_callback (data, "account");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
add_book_local (sixtp_gdv2* data, QofBook* book)
|
|
{
|
|
data->counter.books_loaded++;
|
|
sixtp_run_callback (data, "book");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
add_commodity_local (sixtp_gdv2* data, gnc_commodity* com)
|
|
{
|
|
gnc_commodity_table* table;
|
|
|
|
table = gnc_commodity_table_get_table (data->book);
|
|
|
|
gnc_commodity_table_insert (table, com);
|
|
|
|
data->counter.commodities_loaded++;
|
|
sixtp_run_callback (data, "commodities");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
add_transaction_local (sixtp_gdv2* data, Transaction* trn)
|
|
{
|
|
gnc_commodity_table* table;
|
|
|
|
table = gnc_commodity_table_get_table (data->book);
|
|
|
|
xaccTransBeginEdit (trn);
|
|
clear_up_transaction_commodity (table, trn,
|
|
xaccTransGetCurrency,
|
|
xaccTransSetCurrency);
|
|
|
|
xaccTransScrubCurrency (trn);
|
|
xaccTransScrubPostedDate (trn);
|
|
xaccTransCommitEdit (trn);
|
|
|
|
data->counter.transactions_loaded++;
|
|
sixtp_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++;
|
|
sixtp_run_callback (data, "schedXactions");
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
add_template_transaction_local (sixtp_gdv2* data,
|
|
gnc_template_xaction_data* txd)
|
|
{
|
|
GList* n;
|
|
Account* acctRoot = 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 (gnc_account_get_parent ((Account*)n->data) == NULL)
|
|
{
|
|
if (xaccAccountGetType ((Account*)n->data) == ACCT_TYPE_ROOT)
|
|
{
|
|
/* replace the gnc_book_init-created root account */
|
|
gnc_book_set_template_root (book, (Account*)n->data);
|
|
}
|
|
else
|
|
{
|
|
/* This is an old data file that doesn't have a template root
|
|
account and this is a top level account. Make it a child
|
|
of the template root account. */
|
|
acctRoot = gnc_book_get_template_root (book);
|
|
gnc_account_append_child (acctRoot, (Account*)n->data);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
for (n = txd->transactions; n; n = n->next)
|
|
{
|
|
/* insert transactions into accounts */
|
|
add_transaction_local (data, (Transaction*)n->data);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
add_pricedb_local (sixtp_gdv2* data, GNCPriceDB* db)
|
|
{
|
|
/* gnc_pricedb_print_contents(db, stdout); */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
counter (const GncXmlDataType_t& data, file_backend* be_data)
|
|
{
|
|
g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
|
|
|
|
if (be_data->ok == TRUE)
|
|
return;
|
|
|
|
if (!g_strcmp0 (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 (g_strcmp0 (type, "transaction") == 0)
|
|
{
|
|
sixdata->counter.transactions_total = val;
|
|
}
|
|
else if (g_strcmp0 (type, "account") == 0)
|
|
{
|
|
sixdata->counter.accounts_total = val;
|
|
}
|
|
else if (g_strcmp0 (type, "book") == 0)
|
|
{
|
|
sixdata->counter.books_total = val;
|
|
}
|
|
else if (g_strcmp0 (type, "commodity") == 0)
|
|
{
|
|
sixdata->counter.commodities_total = val;
|
|
}
|
|
else if (g_strcmp0 (type, "schedxaction") == 0)
|
|
{
|
|
sixdata->counter.schedXactions_total = val;
|
|
}
|
|
else if (g_strcmp0 (type, "budget") == 0)
|
|
{
|
|
sixdata->counter.budgets_total = val;
|
|
}
|
|
else if (g_strcmp0 (type, "price") == 0)
|
|
{
|
|
sixdata->counter.prices_total = val;
|
|
}
|
|
else
|
|
{
|
|
struct file_backend be_data;
|
|
|
|
be_data.ok = FALSE;
|
|
be_data.tag = type;
|
|
for(auto data : backend_registry)
|
|
counter(data, &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 Transactions: 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 +
|
|
counter->prices_loaded;
|
|
total = counter->transactions_total + counter->accounts_total +
|
|
counter->books_total + counter->commodities_total +
|
|
counter->schedXactions_total + counter->budgets_total +
|
|
counter->prices_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 Transactions: 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 (const GncXmlDataType_t& data, struct file_backend* be_data)
|
|
{
|
|
g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
|
|
|
|
if (be_data->ok)
|
|
return;
|
|
|
|
if (!g_strcmp0 (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 (g_strcmp0 (tag, ACCOUNT_TAG) == 0)
|
|
{
|
|
add_account_local (gd, (Account*)data);
|
|
}
|
|
else if (g_strcmp0 (tag, PRICEDB_TAG) == 0)
|
|
{
|
|
add_pricedb_local (gd, (GNCPriceDB*)data);
|
|
}
|
|
else if (g_strcmp0 (tag, COMMODITY_TAG) == 0)
|
|
{
|
|
add_commodity_local (gd, (gnc_commodity*)data);
|
|
}
|
|
else if (g_strcmp0 (tag, TRANSACTION_TAG) == 0)
|
|
{
|
|
add_transaction_local (gd, (Transaction*)data);
|
|
}
|
|
else if (g_strcmp0 (tag, SCHEDXACTION_TAG) == 0)
|
|
{
|
|
add_schedXaction_local (gd, (SchedXaction*)data);
|
|
}
|
|
else if (g_strcmp0 (tag, TEMPLATE_TRANSACTION_TAG) == 0)
|
|
{
|
|
add_template_transaction_local (gd, (gnc_template_xaction_data*)data);
|
|
}
|
|
else if (g_strcmp0 (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;
|
|
|
|
for (auto data : backend_registry)
|
|
add_item(data, &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 (g_strcmp0 (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(const GncXmlDataType_t& data, struct file_backend* 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 (const GncXmlDataType_t& data, struct file_backend* 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 (
|
|
GncXmlBackend* xml_be, QofBook* book,
|
|
sixtp_push_handler push_handler, gpointer push_user_data,
|
|
QofBookFileType type)
|
|
{
|
|
Account* root;
|
|
sixtp_gdv2* gd;
|
|
sixtp* top_parser;
|
|
sixtp* main_parser;
|
|
sixtp* book_parser;
|
|
struct file_backend be_data;
|
|
gboolean retval;
|
|
char* v2type = NULL;
|
|
|
|
gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
|
|
xml_be->get_percentage());
|
|
|
|
top_parser = sixtp_new ();
|
|
main_parser = sixtp_new ();
|
|
book_parser = sixtp_new ();
|
|
|
|
if (type == GNC_BOOK_XML2_FILE)
|
|
v2type = g_strdup (GNC_V2_STRING);
|
|
|
|
if (!sixtp_add_some_sub_parsers (
|
|
top_parser, TRUE,
|
|
v2type, main_parser,
|
|
NULL, NULL))
|
|
{
|
|
g_free (v2type);
|
|
goto bail;
|
|
}
|
|
|
|
g_free (v2type);
|
|
|
|
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;
|
|
for (auto data : backend_registry)
|
|
add_parser(data, &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
|
|
{
|
|
/* Even though libxml2 knows how to decompress zipped files, we
|
|
* do it ourself since as of version 2.9.1 it has a bug that
|
|
* causes it to fail to decompress certain files. See
|
|
* https://bugzilla.gnome.org/show_bug.cgi?id=712528 for more
|
|
* info.
|
|
*/
|
|
const char* filename = xml_be->get_filename();
|
|
FILE* file;
|
|
gboolean 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);
|
|
retval = FALSE;
|
|
}
|
|
else
|
|
{
|
|
retval = gnc_xml_parse_fd (top_parser, file,
|
|
generic_callback, gd, book);
|
|
fclose (file);
|
|
if (is_compressed)
|
|
wait_for_gzip (file);
|
|
}
|
|
}
|
|
|
|
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 session as saved */
|
|
qof_book_mark_session_saved (book);
|
|
|
|
/* Call individual scrub functions */
|
|
memset (&be_data, 0, sizeof (be_data));
|
|
be_data.book = book;
|
|
for (auto data : backend_registry)
|
|
scrub(data, &be_data);
|
|
|
|
/* fix price quote sources */
|
|
root = gnc_book_get_root_account (book);
|
|
xaccAccountTreeScrubQuoteSources (root, gnc_commodity_table_get_table (book));
|
|
|
|
/* Fix account and transaction commodities */
|
|
xaccAccountTreeScrubCommodities (root);
|
|
|
|
/* Fix split amount/value */
|
|
xaccAccountTreeScrubSplits (root);
|
|
|
|
/* commit all groups, this completes the BeginEdit started when the
|
|
* account_end_handler finished reading the account.
|
|
*/
|
|
gnc_account_foreach_descendant (root,
|
|
(AccountCb) xaccAccountCommitEdit,
|
|
NULL);
|
|
|
|
/* start logging again */
|
|
xaccLogEnable ();
|
|
|
|
return TRUE;
|
|
|
|
bail:
|
|
g_free (gd);
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
qof_session_load_from_xml_file_v2 (GncXmlBackend* xml_be, QofBook* book,
|
|
QofBookFileType type)
|
|
{
|
|
return qof_session_load_from_xml_file_v2_full (xml_be, book, NULL, NULL, type);
|
|
}
|
|
|
|
/***********************************************************************/
|
|
|
|
static gboolean
|
|
write_counts (FILE* out, ...)
|
|
{
|
|
va_list ap;
|
|
char* type;
|
|
gboolean success = TRUE;
|
|
|
|
va_start (ap, out);
|
|
type = g_strdup (va_arg (ap, char*));
|
|
|
|
while (success && 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", checked_char_cast (type));
|
|
xmlNodeAddContent (node, checked_char_cast (val));
|
|
g_free (val);
|
|
g_free (type);
|
|
|
|
xmlElemDump (out, NULL, node);
|
|
xmlFreeNode (node);
|
|
|
|
if (ferror (out) || fprintf (out, "\n") < 0)
|
|
{
|
|
success = FALSE;
|
|
break;
|
|
}
|
|
#else
|
|
if (fprintf (out, "<%s %s=\"%s\">%d</%s>\n",
|
|
COUNT_DATA_TAG, "cd:type", type, amount, COUNT_DATA_TAG) < 0)
|
|
{
|
|
success = FALSE;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
type = va_arg (ap, char*);
|
|
}
|
|
|
|
va_end (ap);
|
|
return success;
|
|
}
|
|
|
|
static gint
|
|
compare_namespaces (gconstpointer a, gconstpointer b)
|
|
{
|
|
const gchar* sa = (const gchar*) a;
|
|
const gchar* sb = (const gchar*) b;
|
|
return (g_strcmp0 (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 (g_strcmp0 (gnc_commodity_get_mnemonic (ca),
|
|
gnc_commodity_get_mnemonic (cb)));
|
|
}
|
|
|
|
static gboolean write_pricedb (FILE* out, QofBook* book, sixtp_gdv2* gd);
|
|
static gboolean write_transactions (FILE* out, QofBook* book, sixtp_gdv2* gd);
|
|
static gboolean write_template_transaction_data (FILE* out, QofBook* book,
|
|
sixtp_gdv2* gd);
|
|
static gboolean write_schedXactions (FILE* out, QofBook* book, sixtp_gdv2* gd);
|
|
static void write_budget (QofInstance* ent, gpointer data);
|
|
|
|
static void
|
|
write_counts(const GncXmlDataType_t& data, struct file_backend* 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(const GncXmlDataType_t& data, struct file_backend* be_data)
|
|
{
|
|
g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
|
|
|
|
if (data.write && !ferror(be_data->out))
|
|
(data.write)(be_data->out, be_data->book);
|
|
}
|
|
|
|
static gboolean
|
|
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 FALSE;
|
|
}
|
|
|
|
xmlElemDump (out, NULL, node);
|
|
xmlFreeNode (node);
|
|
|
|
if (ferror (out) || fprintf (out, "\n") < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
#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)
|
|
return FALSE;
|
|
if (!write_book_parts (out, book))
|
|
return FALSE;
|
|
|
|
/* gd->counter.{foo}_total fields should have all these totals
|
|
already collected. I don't know why we're re-calling all these
|
|
functions. */
|
|
if (!write_counts (out,
|
|
"commodity",
|
|
gnc_commodity_table_get_size (
|
|
gnc_commodity_table_get_table (book)),
|
|
"account",
|
|
1 + gnc_account_n_descendants (gnc_book_get_root_account (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)),
|
|
"price", gnc_pricedb_get_num_prices (gnc_pricedb_get_db (book)),
|
|
NULL))
|
|
return FALSE;
|
|
|
|
for (auto data : backend_registry)
|
|
write_counts(data, &be_data);
|
|
|
|
if (ferror (out)
|
|
|| !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))
|
|
|
|
return FALSE;
|
|
|
|
qof_collection_foreach (qof_book_get_collection (book, GNC_ID_BUDGET),
|
|
write_budget, &be_data);
|
|
if (ferror (out))
|
|
return FALSE;
|
|
|
|
for (auto data : backend_registry)
|
|
write_data(data, &be_data);
|
|
if (ferror(out))
|
|
return FALSE;
|
|
|
|
if (fprintf (out, "</%s>\n", BOOK_TAG) < 0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
write_commodities (FILE* out, QofBook* book, sixtp_gdv2* gd)
|
|
{
|
|
gnc_commodity_table* tbl;
|
|
GList* namespaces;
|
|
GList* lp;
|
|
gboolean success = TRUE;
|
|
|
|
tbl = gnc_commodity_table_get_table (book);
|
|
|
|
namespaces = gnc_commodity_table_get_namespaces (tbl);
|
|
if (namespaces)
|
|
{
|
|
namespaces = g_list_sort (namespaces, compare_namespaces);
|
|
}
|
|
|
|
for (lp = namespaces; success && lp; lp = lp->next)
|
|
{
|
|
GList* comms, *lp2;
|
|
xmlNodePtr comnode;
|
|
|
|
comms = gnc_commodity_table_get_commodities (tbl,
|
|
static_cast<const char*> (lp->data));
|
|
comms = g_list_sort (comms, compare_commodity_ids);
|
|
|
|
for (lp2 = comms; lp2; lp2 = lp2->next)
|
|
{
|
|
comnode = gnc_commodity_dom_tree_create (static_cast<const gnc_commodity*>
|
|
(lp2->data));
|
|
if (comnode == NULL)
|
|
continue;
|
|
|
|
xmlElemDump (out, NULL, comnode);
|
|
if (ferror (out) || fprintf (out, "\n") < 0)
|
|
{
|
|
success = FALSE;
|
|
break;
|
|
}
|
|
|
|
xmlFreeNode (comnode);
|
|
gd->counter.commodities_loaded++;
|
|
sixtp_run_callback (gd, "commodities");
|
|
}
|
|
|
|
g_list_free (comms);
|
|
}
|
|
|
|
if (namespaces) g_list_free (namespaces);
|
|
|
|
return success;
|
|
}
|
|
|
|
static gboolean
|
|
write_pricedb (FILE* out, QofBook* book, sixtp_gdv2* gd)
|
|
{
|
|
xmlNodePtr node;
|
|
xmlNodePtr parent;
|
|
xmlOutputBufferPtr outbuf;
|
|
|
|
parent = gnc_pricedb_dom_tree_create (gnc_pricedb_get_db (book));
|
|
|
|
if (!parent)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
/* Write out the parent pricedb tag then loop to write out each price.
|
|
We do it this way instead of just calling xmlElemDump so that we can
|
|
increment the progress bar as we go. */
|
|
|
|
if (fprintf (out, "<%s version=\"%s\">\n", parent->name,
|
|
xmlGetProp (parent, BAD_CAST "version")) < 0)
|
|
return FALSE;
|
|
|
|
/* We create our own output buffer so we can call xmlNodeDumpOutput to get
|
|
the indentation correct. */
|
|
outbuf = xmlOutputBufferCreateFile (out, NULL);
|
|
if (outbuf == NULL)
|
|
{
|
|
xmlFreeNode (parent);
|
|
return FALSE;
|
|
}
|
|
|
|
for (node = parent->children; node; node = node->next)
|
|
{
|
|
/* Write two spaces since xmlNodeDumpOutput doesn't indent the first line */
|
|
xmlOutputBufferWrite (outbuf, 2, " ");
|
|
xmlNodeDumpOutput (outbuf, NULL, node, 1, 1, NULL);
|
|
/* It also doesn't terminate the last line */
|
|
xmlOutputBufferWrite (outbuf, 1, "\n");
|
|
if (ferror (out))
|
|
break;
|
|
gd->counter.prices_loaded += 1;
|
|
sixtp_run_callback (gd, "prices");
|
|
}
|
|
|
|
xmlOutputBufferClose (outbuf);
|
|
|
|
if (ferror (out) || fprintf (out, "</%s>\n", parent->name) < 0)
|
|
{
|
|
xmlFreeNode (parent);
|
|
return FALSE;
|
|
}
|
|
|
|
xmlFreeNode (parent);
|
|
return TRUE;
|
|
}
|
|
|
|
static int
|
|
xml_add_trn_data (Transaction* t, gpointer data)
|
|
{
|
|
struct file_backend* be_data = static_cast<decltype (be_data)> (data);
|
|
xmlNodePtr node;
|
|
|
|
node = gnc_transaction_dom_tree_create (t);
|
|
|
|
xmlElemDump (be_data->out, NULL, node);
|
|
xmlFreeNode (node);
|
|
|
|
if (ferror (be_data->out) || fprintf (be_data->out, "\n") < 0)
|
|
return -1;
|
|
|
|
be_data->gd->counter.transactions_loaded++;
|
|
sixtp_run_callback (be_data->gd, "transaction");
|
|
return 0;
|
|
}
|
|
|
|
static gboolean
|
|
write_transactions (FILE* out, QofBook* book, sixtp_gdv2* gd)
|
|
{
|
|
struct file_backend be_data;
|
|
|
|
be_data.out = out;
|
|
be_data.gd = gd;
|
|
return 0 ==
|
|
xaccAccountTreeForEachTransaction (gnc_book_get_root_account (book),
|
|
xml_add_trn_data,
|
|
(gpointer) &be_data);
|
|
}
|
|
|
|
static gboolean
|
|
write_template_transaction_data (FILE* out, QofBook* book, sixtp_gdv2* gd)
|
|
{
|
|
Account* ra;
|
|
struct file_backend be_data;
|
|
|
|
be_data.out = out;
|
|
be_data.gd = gd;
|
|
|
|
ra = gnc_book_get_template_root (book);
|
|
if (gnc_account_n_descendants (ra) > 0)
|
|
{
|
|
if (fprintf (out, "<%s>\n", TEMPLATE_TRANSACTION_TAG) < 0
|
|
|| !write_account_tree (out, ra, gd)
|
|
|| xaccAccountTreeForEachTransaction (ra, xml_add_trn_data, (gpointer)&be_data)
|
|
|| fprintf (out, "</%s>\n", TEMPLATE_TRANSACTION_TAG) < 0)
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
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 TRUE;
|
|
|
|
do
|
|
{
|
|
tmpSX = static_cast<decltype (tmpSX)> (schedXactions->data);
|
|
node = gnc_schedXaction_dom_tree_create (tmpSX);
|
|
xmlElemDump (out, NULL, node);
|
|
xmlFreeNode (node);
|
|
if (ferror (out) || fprintf (out, "\n") < 0)
|
|
return FALSE;
|
|
gd->counter.schedXactions_loaded++;
|
|
sixtp_run_callback (gd, "schedXactions");
|
|
}
|
|
while ((schedXactions = schedXactions->next));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
write_budget (QofInstance* ent, gpointer data)
|
|
{
|
|
xmlNodePtr node;
|
|
struct file_backend* file_be = static_cast<decltype (file_be)> (data);
|
|
|
|
GncBudget* bgt = GNC_BUDGET (ent);
|
|
|
|
if (ferror (file_be->out))
|
|
return;
|
|
|
|
node = gnc_budget_dom_tree_create (bgt);
|
|
xmlElemDump (file_be->out, NULL, node);
|
|
xmlFreeNode (node);
|
|
if (ferror (file_be->out) || fprintf (file_be->out, "\n") < 0)
|
|
return;
|
|
|
|
file_be->gd->counter.budgets_loaded++;
|
|
sixtp_run_callback (file_be->gd, "budgets");
|
|
}
|
|
|
|
gboolean
|
|
gnc_xml2_write_namespace_decl (FILE* out, const char* name_space)
|
|
{
|
|
g_return_val_if_fail (name_space, FALSE);
|
|
return fprintf (out, "\n xmlns:%s=\"http://www.gnucash.org/XML/%s\"",
|
|
name_space, name_space) >= 0;
|
|
}
|
|
|
|
static void
|
|
write_namespace (const GncXmlDataType_t& data, FILE* out)
|
|
{
|
|
g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
|
|
|
|
if (data.ns && !ferror(out))
|
|
(data.ns)(out);
|
|
}
|
|
|
|
static gboolean
|
|
write_v2_header (FILE* out)
|
|
{
|
|
if (fprintf (out, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n") < 0
|
|
|| fprintf (out, "<" GNC_V2_STRING) < 0
|
|
|
|
|| !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"))
|
|
|
|
return FALSE;
|
|
|
|
/* now cope with the plugins */
|
|
for (auto data : backend_registry)
|
|
write_namespace(data, out);
|
|
|
|
if (ferror (out) || fprintf (out, ">\n") < 0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
|
|
{
|
|
QofBackend* qof_be;
|
|
sixtp_gdv2* gd;
|
|
gboolean success = TRUE;
|
|
|
|
if (!out) return FALSE;
|
|
|
|
if (!write_v2_header (out)
|
|
|| !write_counts (out, "book", 1, NULL))
|
|
return FALSE;
|
|
|
|
qof_be = qof_book_get_backend (book);
|
|
gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
|
|
qof_be->get_percentage());
|
|
gd->counter.commodities_total =
|
|
gnc_commodity_table_get_size (gnc_commodity_table_get_table (book));
|
|
gd->counter.accounts_total = 1 +
|
|
gnc_account_n_descendants (gnc_book_get_root_account (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));
|
|
gd->counter.prices_total = gnc_pricedb_get_num_prices (gnc_pricedb_get_db (
|
|
book));
|
|
|
|
if (!write_book (out, book, gd)
|
|
|| fprintf (out, "</" GNC_V2_STRING ">\n\n") < 0)
|
|
success = FALSE;
|
|
|
|
g_free (gd);
|
|
return success;
|
|
}
|
|
|
|
/*
|
|
* This function is called by the "export" code.
|
|
*/
|
|
gboolean
|
|
gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* qof_be, QofBook* book,
|
|
FILE* out)
|
|
{
|
|
gnc_commodity_table* table;
|
|
Account* root;
|
|
int ncom, nacc;
|
|
sixtp_gdv2* gd;
|
|
gboolean success = TRUE;
|
|
|
|
if (!out) return FALSE;
|
|
|
|
root = gnc_book_get_root_account (book);
|
|
nacc = 1 + gnc_account_n_descendants (root);
|
|
|
|
table = gnc_commodity_table_get_table (book);
|
|
ncom = gnc_commodity_table_get_size (table);
|
|
|
|
if (!write_v2_header (out)
|
|
|| !write_counts (out, "commodity", ncom, "account", nacc, NULL))
|
|
return FALSE;
|
|
|
|
gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback,
|
|
qof_be->get_percentage());
|
|
gd->counter.commodities_total = ncom;
|
|
gd->counter.accounts_total = nacc;
|
|
|
|
if (!write_commodities (out, book, gd)
|
|
|| !write_accounts (out, book, gd)
|
|
|| fprintf (out, "</" GNC_V2_STRING ">\n\n") < 0)
|
|
success = FALSE;
|
|
|
|
g_free (gd);
|
|
return success;
|
|
}
|
|
|
|
#define BUFLEN 4096
|
|
|
|
/* Compress or decompress function that is to be run in a separate thread.
|
|
* Returns 1 on success or 0 otherwise, stuffed into a pointer type. */
|
|
static gpointer
|
|
gz_thread_func (gz_thread_params_t* params)
|
|
{
|
|
gchar buffer[BUFLEN];
|
|
gssize bytes;
|
|
gint gzval;
|
|
gzFile file;
|
|
gint success = 1;
|
|
|
|
#ifdef G_OS_WIN32
|
|
{
|
|
gchar* conv_name = g_win32_locale_filename_from_utf8 (params->filename);
|
|
gchar* perms;
|
|
|
|
if (!conv_name)
|
|
{
|
|
g_warning ("Could not convert '%s' to system codepage",
|
|
params->filename);
|
|
success = 0;
|
|
goto cleanup_gz_thread_func;
|
|
}
|
|
|
|
if (strchr (params->perms, 'b'))
|
|
perms = g_strdup (params->perms);
|
|
else
|
|
perms = g_strdup_printf ("%cb%s", *params->perms, params->perms + 1);
|
|
|
|
file = gzopen (conv_name, perms);
|
|
g_free (perms);
|
|
g_free (conv_name);
|
|
}
|
|
#else /* !G_OS_WIN32 */
|
|
file = gzopen (params->filename, params->perms);
|
|
#endif /* G_OS_WIN32 */
|
|
|
|
if (file == NULL)
|
|
{
|
|
g_warning ("Child threads gzopen failed");
|
|
success = 0;
|
|
goto cleanup_gz_thread_func;
|
|
}
|
|
|
|
if (params->compress)
|
|
{
|
|
while (success)
|
|
{
|
|
bytes = read (params->fd, buffer, BUFLEN);
|
|
if (bytes > 0)
|
|
{
|
|
if (gzwrite (file, buffer, bytes) <= 0)
|
|
{
|
|
gint errnum;
|
|
const gchar* error = gzerror (file, &errnum);
|
|
g_warning ("Could not write the compressed file '%s'. The error is: '%s' (%d)",
|
|
params->filename, error, errnum);
|
|
success = 0;
|
|
}
|
|
}
|
|
else if (bytes == 0)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
g_warning ("Could not read from pipe. The error is '%s' (errno %d)",
|
|
g_strerror (errno) ? g_strerror (errno) : "", errno);
|
|
success = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (success)
|
|
{
|
|
gzval = gzread (file, buffer, BUFLEN);
|
|
if (gzval > 0)
|
|
{
|
|
if (
|
|
#if COMPILER(MSVC)
|
|
_write
|
|
#else
|
|
write
|
|
#endif
|
|
(params->fd, buffer, gzval) < 0)
|
|
{
|
|
g_warning ("Could not write to pipe. The error is '%s' (%d)",
|
|
g_strerror (errno) ? g_strerror (errno) : "", errno);
|
|
success = 0;
|
|
}
|
|
}
|
|
else if (gzval == 0)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
gint errnum;
|
|
const gchar* error = gzerror (file, &errnum);
|
|
g_warning ("Could not read from compressed file '%s'. The error is: '%s' (%d)",
|
|
params->filename, error, errnum);
|
|
success = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((gzval = gzclose (file)) != Z_OK)
|
|
{
|
|
g_warning ("Could not close the compressed file '%s' (errnum %d)",
|
|
params->filename, gzval);
|
|
success = 0;
|
|
}
|
|
|
|
cleanup_gz_thread_func:
|
|
close (params->fd);
|
|
g_free (params->filename);
|
|
g_free (params->perms);
|
|
g_free (params);
|
|
|
|
return GINT_TO_POINTER (success);
|
|
}
|
|
|
|
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);
|
|
|
|
{
|
|
int filedes[2];
|
|
GThread* thread;
|
|
GError* error = NULL;
|
|
gz_thread_params_t* params;
|
|
FILE* file;
|
|
|
|
#ifdef G_OS_WIN32
|
|
if (_pipe (filedes, 4096, _O_BINARY) < 0)
|
|
{
|
|
#else
|
|
if (pipe (filedes) < 0)
|
|
{
|
|
#endif
|
|
g_warning ("Pipe call failed. Opening uncompressed file.");
|
|
return g_fopen (filename, perms);
|
|
}
|
|
|
|
params = g_new (gz_thread_params_t, 1);
|
|
params->fd = filedes[compress ? 0 : 1];
|
|
params->filename = g_strdup (filename);
|
|
params->perms = g_strdup (perms);
|
|
params->compress = compress;
|
|
|
|
thread = g_thread_new ("xml_thread", (GThreadFunc) gz_thread_func,
|
|
params);
|
|
if (!thread)
|
|
{
|
|
g_warning ("Could not create thread for (de)compression: %s",
|
|
error->message);
|
|
g_error_free (error);
|
|
g_free (params->filename);
|
|
g_free (params->perms);
|
|
g_free (params);
|
|
close (filedes[0]);
|
|
close (filedes[1]);
|
|
|
|
return g_fopen (filename, perms);
|
|
}
|
|
|
|
if (compress)
|
|
file = fdopen (filedes[1], "w");
|
|
else
|
|
file = fdopen (filedes[0], "r");
|
|
|
|
G_LOCK (threads);
|
|
if (!threads)
|
|
threads = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
|
|
g_hash_table_insert (threads, file, thread);
|
|
G_UNLOCK (threads);
|
|
|
|
return file;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
wait_for_gzip (FILE* file)
|
|
{
|
|
gboolean retval = TRUE;
|
|
|
|
G_LOCK (threads);
|
|
if (threads)
|
|
{
|
|
GThread* thread = static_cast<decltype (thread)> (g_hash_table_lookup (threads,
|
|
file));
|
|
if (thread)
|
|
{
|
|
g_hash_table_remove (threads, file);
|
|
retval = GPOINTER_TO_INT (g_thread_join (thread));
|
|
}
|
|
}
|
|
G_UNLOCK (threads);
|
|
|
|
return retval;
|
|
}
|
|
|
|
gboolean
|
|
gnc_book_write_to_xml_file_v2 (
|
|
QofBook* book,
|
|
const char* filename,
|
|
gboolean compress)
|
|
{
|
|
FILE* out;
|
|
gboolean success = TRUE;
|
|
|
|
out = try_gz_open (filename, "w", compress, TRUE);
|
|
|
|
/* Try to write as much as possible */
|
|
if (!out
|
|
|| !gnc_book_write_to_xml_filehandle_v2 (book, out)
|
|
|| !write_emacs_trailer (out))
|
|
success = FALSE;
|
|
|
|
/* Close the output stream */
|
|
if (out && fclose (out))
|
|
success = FALSE;
|
|
|
|
/* Optionally wait for parallel compression threads */
|
|
if (out && compress)
|
|
if (!wait_for_gzip (out))
|
|
success = FALSE;
|
|
|
|
return success;
|
|
}
|
|
|
|
/*
|
|
* 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* qof_be, QofBook* book,
|
|
const char* filename)
|
|
{
|
|
FILE* out;
|
|
gboolean success = TRUE;
|
|
|
|
out = g_fopen (filename, "w");
|
|
|
|
/* Try to write as much as possible */
|
|
if (!out
|
|
|| !gnc_book_write_accounts_to_xml_filehandle_v2 (qof_be, book, out)
|
|
|| !write_emacs_trailer (out))
|
|
success = FALSE;
|
|
|
|
/* Close the output stream */
|
|
if (out && fclose (out))
|
|
success = FALSE;
|
|
|
|
if (!success && !qof_be->check_error())
|
|
{
|
|
|
|
/* Use a generic write error code */
|
|
qof_backend_set_error (qof_be, ERR_FILEIO_WRITE_ERROR);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
/***********************************************************************/
|
|
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;
|
|
}
|
|
|
|
QofBookFileType
|
|
gnc_is_xml_data_file_v2 (const gchar* name, gboolean* with_encoding)
|
|
{
|
|
if (is_gzipped_file (name))
|
|
{
|
|
gzFile file = NULL;
|
|
char first_chunk[256];
|
|
int num_read;
|
|
|
|
#ifdef G_OS_WIN32
|
|
{
|
|
gchar* conv_name = g_win32_locale_filename_from_utf8 (name);
|
|
if (!conv_name)
|
|
g_warning ("Could not convert '%s' to system codepage", name);
|
|
else
|
|
{
|
|
file = gzopen (conv_name, "rb");
|
|
g_free (conv_name);
|
|
}
|
|
}
|
|
#else
|
|
file = gzopen (name, "r");
|
|
#endif
|
|
if (file == NULL)
|
|
return GNC_BOOK_NOT_OURS;
|
|
|
|
num_read = gzread (file, first_chunk, sizeof (first_chunk) - 1);
|
|
gzclose (file);
|
|
|
|
if (num_read < 1)
|
|
return GNC_BOOK_NOT_OURS;
|
|
|
|
return gnc_is_our_first_xml_chunk (first_chunk, with_encoding);
|
|
}
|
|
|
|
return (gnc_is_our_xml_file (name, with_encoding));
|
|
}
|
|
|
|
|
|
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 = static_cast<decltype (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);
|
|
if (is_compressed)
|
|
wait_for_gzip (file);
|
|
}
|
|
|
|
return (clean_return) ? n_impossible : -1;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
const char* 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 = static_cast<decltype (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);
|
|
if (is_compressed)
|
|
wait_for_gzip (file);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gnc_xml2_parse_with_subst (GncXmlBackend* xml_be, QofBook* book, GHashTable* subst)
|
|
{
|
|
push_data_type* push_data;
|
|
gboolean success;
|
|
|
|
push_data = g_new (push_data_type, 1);
|
|
push_data->filename = xml_be->get_filename();
|
|
push_data->subst = subst;
|
|
|
|
success = qof_session_load_from_xml_file_v2_full (
|
|
xml_be, book, (sixtp_push_handler) parse_with_subst_push_handler,
|
|
push_data, GNC_BOOK_XML2_FILE);
|
|
|
|
if (success)
|
|
qof_instance_set_dirty (QOF_INSTANCE (book));
|
|
|
|
return success;
|
|
}
|