GnuCash 2.4.99
io-gncxml-v2.c
00001 /********************************************************************\
00002  * Copyright (C) 2000,2001 Gnumatic Inc.                            *
00003  *                                                                  *
00004  * This program is free software; you can redistribute it and/or    *
00005  * modify it under the terms of the GNU General Public License as   *
00006  * published by the Free Software Foundation; either version 2 of   *
00007  * the License, or (at your option) any later version.              *
00008  *                                                                  *
00009  * This program is distributed in the hope that it will be useful,  *
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00012  * GNU General Public License for more details.                     *
00013  *                                                                  *
00014  * You should have received a copy of the GNU General Public License*
00015  * along with this program; if not, contact:                        *
00016  *                                                                  *
00017  * Free Software Foundation           Voice:  +1-617-542-5942       *
00018  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00019  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00020 \********************************************************************/
00021 
00022 #include "config.h"
00023 
00024 #include <glib.h>
00025 #include <glib/gstdio.h>
00026 #include <fcntl.h>
00027 #include <string.h>
00028 #ifdef HAVE_UNISTD_H
00029 # include <unistd.h>
00030 #endif
00031 #include <zlib.h>
00032 #include <errno.h>
00033 
00034 #include "gnc-engine.h"
00035 #include "gnc-pricedb-p.h"
00036 #include "Scrub.h"
00037 #include "SX-book.h"
00038 #include "SX-book-p.h"
00039 #include "Transaction.h"
00040 #include "TransactionP.h"
00041 #include "TransLog.h"
00042 #include "sixtp-dom-parsers.h"
00043 #include "io-gncxml-v2.h"
00044 #include "io-gncxml-gen.h"
00045 
00046 #include "sixtp.h"
00047 #include "sixtp-parsers.h"
00048 #include "sixtp-utils.h"
00049 #include "gnc-xml.h"
00050 #include "io-utils.h"
00051 #ifdef G_OS_WIN32
00052 # include <io.h>
00053 # define close _close
00054 # define fdopen _fdopen
00055 # define read _read
00056 #endif
00057 #include "platform.h"
00058 #if COMPILER(MSVC)
00059 # define g_fopen fopen
00060 # define g_open _open
00061 #endif
00062 
00063 /* Do not treat -Wstrict-aliasing warnings as errors because of problems of the
00064  * G_LOCK* macros as declared by glib.  See
00065  * http://bugzilla.gnome.org/show_bug.cgi?id=316221 for additional information.
00066  */
00067 #if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 2)
00068 #    pragma GCC diagnostic warning "-Wstrict-aliasing"
00069 #endif
00070 
00071 static QofLogModule log_module = GNC_MOD_IO;
00072 
00073 /* map pointers, e.g. of type FILE*, to GThreads */
00074 static GHashTable *threads = NULL;
00075 G_LOCK_DEFINE_STATIC(threads);
00076 
00077 typedef struct
00078 {
00079     gint fd;
00080     gchar *filename;
00081     gchar *perms;
00082     gboolean compress;
00083 } gz_thread_params_t;
00084 
00085 /* Callback structure */
00086 struct file_backend
00087 {
00088     gboolean        ok;
00089     gpointer        data;
00090     sixtp_gdv2    * gd;
00091     const char    * tag;
00092     sixtp         * parser;
00093     FILE          * out;
00094     QofBook       * book;
00095 };
00096 
00097 #define GNC_V2_STRING "gnc-v2"
00098 /* non-static because they are used in sixtp.c */
00099 const gchar *gnc_v2_xml_version_string = GNC_V2_STRING;
00100 extern const gchar *gnc_v2_book_version_string;        /* see gnc-book-xml-v2 */
00101 
00102 void
00103 run_callback(sixtp_gdv2 *data, const char *type)
00104 {
00105     if (data->countCallback)
00106     {
00107         data->countCallback(data, type);
00108     }
00109 }
00110 
00111 static void
00112 clear_up_account_commodity(
00113     gnc_commodity_table *tbl, Account *act,
00114     gnc_commodity * (*getter) (const Account *account),
00115     void (*setter) (Account *account, gnc_commodity *comm),
00116     int (*scu_getter) (const Account *account),
00117     void (*scu_setter) (Account *account, int scu))
00118 {
00119     gnc_commodity *gcom;
00120     gnc_commodity *com = getter(act);
00121     int old_scu;
00122 
00123     if (scu_getter)
00124         old_scu = scu_getter(act);
00125     else
00126         old_scu = 0;
00127 
00128     if (!com)
00129     {
00130         return;
00131     }
00132 
00133     gcom = gnc_commodity_table_lookup(tbl, gnc_commodity_get_namespace(com),
00134                                       gnc_commodity_get_mnemonic(com));
00135 
00136     if (gcom == com)
00137     {
00138         return;
00139     }
00140     else if (!gcom)
00141     {
00142         PWARN("unable to find global commodity for %s adding new",
00143               gnc_commodity_get_unique_name(com));
00144         gnc_commodity_table_insert(tbl, com);
00145     }
00146     else
00147     {
00148         setter(act, gcom);
00149         if (old_scu != 0 && scu_setter)
00150             scu_setter(act, old_scu);
00151         gnc_commodity_destroy(com);
00152     }
00153 }
00154 
00155 static void
00156 clear_up_transaction_commodity(
00157     gnc_commodity_table *tbl, Transaction *trans,
00158     gnc_commodity * (*getter) (const Transaction *trans),
00159     void (*setter) (Transaction *trans, gnc_commodity *comm))
00160 {
00161     gnc_commodity *gcom;
00162     gnc_commodity *com = getter(trans);
00163 
00164     if (!com)
00165     {
00166         return;
00167     }
00168 
00169     gcom = gnc_commodity_table_lookup(tbl, gnc_commodity_get_namespace(com),
00170                                       gnc_commodity_get_mnemonic(com));
00171 
00172     if (gcom == com)
00173     {
00174         return;
00175     }
00176     else if (!gcom)
00177     {
00178         PWARN("unable to find global commodity for %s adding new",
00179               gnc_commodity_get_unique_name(com));
00180         gnc_commodity_table_insert(tbl, com);
00181     }
00182     else
00183     {
00184         xaccTransBeginEdit(trans);
00185         setter(trans, gcom);
00186         xaccTransCommitEdit(trans);
00187         gnc_commodity_destroy(com);
00188     }
00189 }
00190 
00191 static gboolean
00192 add_account_local(sixtp_gdv2 *data, Account *act)
00193 {
00194     gnc_commodity_table *table;
00195     Account *parent, *root;
00196     int type;
00197 
00198     table = gnc_commodity_table_get_table (data->book);
00199 
00200     clear_up_account_commodity(table, act,
00201                                DxaccAccountGetCurrency,
00202                                DxaccAccountSetCurrency,
00203                                NULL, NULL);
00204 
00205     clear_up_account_commodity(table, act,
00206                                xaccAccountGetCommodity,
00207                                xaccAccountSetCommodity,
00208                                xaccAccountGetCommoditySCUi,
00209                                xaccAccountSetCommoditySCU);
00210 
00211     xaccAccountScrubCommodity (act);
00212     xaccAccountScrubKvp (act);
00213 
00214     /* Backwards compatability.  If there's no parent, see if this
00215      * account is of type ROOT.  If not, find or create a ROOT
00216      * account and make that the parent. */
00217     type = xaccAccountGetType(act);
00218     if (type == ACCT_TYPE_ROOT)
00219     {
00220         gnc_book_set_root_account(data->book, act);
00221     }
00222     else
00223     {
00224         parent = gnc_account_get_parent(act);
00225         if (parent == NULL)
00226         {
00227             root = gnc_book_get_root_account(data->book);
00228             gnc_account_append_child(root, act);
00229         }
00230     }
00231 
00232     data->counter.accounts_loaded++;
00233     run_callback(data, "account");
00234 
00235     return FALSE;
00236 }
00237 
00238 static gboolean
00239 add_book_local(sixtp_gdv2 *data, QofBook *book)
00240 {
00241     data->counter.books_loaded++;
00242     run_callback(data, "book");
00243 
00244     return FALSE;
00245 }
00246 
00247 static gboolean
00248 add_commodity_local(sixtp_gdv2 *data, gnc_commodity *com)
00249 {
00250     gnc_commodity_table *table;
00251 
00252     table = gnc_commodity_table_get_table (data->book);
00253 
00254     gnc_commodity_table_insert(table, com);
00255 
00256     data->counter.commodities_loaded++;
00257     run_callback(data, "commodities");
00258 
00259     return TRUE;
00260 }
00261 
00262 static gboolean
00263 add_transaction_local(sixtp_gdv2 *data, Transaction *trn)
00264 {
00265     gnc_commodity_table *table;
00266 
00267     table = gnc_commodity_table_get_table (data->book);
00268 
00269     xaccTransBeginEdit (trn);
00270     clear_up_transaction_commodity(table, trn,
00271                                    xaccTransGetCurrency,
00272                                    xaccTransSetCurrency);
00273 
00274     xaccTransScrubCurrency (trn);
00275     xaccTransCommitEdit (trn);
00276 
00277     data->counter.transactions_loaded++;
00278     run_callback(data, "transaction");
00279     return TRUE;
00280 }
00281 
00282 static gboolean
00283 add_schedXaction_local(sixtp_gdv2 *data, SchedXaction *sx)
00284 {
00285     SchedXactions *sxes;
00286     sxes = gnc_book_get_schedxactions(data->book);
00287     gnc_sxes_add_sx(sxes, sx);
00288     data->counter.schedXactions_loaded++;
00289     run_callback(data, "schedXactions");
00290     return TRUE;
00291 }
00292 
00293 static gboolean
00294 add_template_transaction_local( sixtp_gdv2 *data,
00295                                 gnc_template_xaction_data *txd )
00296 {
00297     GList *n;
00298     Account *acctRoot = NULL;
00299     QofBook *book;
00300 
00301     book = data->book;
00302 
00303     /* expect a struct of: */
00304     /* . template accounts. */
00305     /* . transactions in those accounts. */
00306     for ( n = txd->accts; n; n = n->next )
00307     {
00308         if ( gnc_account_get_parent( (Account*)n->data ) == NULL )
00309         {
00310             if ( xaccAccountGetType( (Account*)n->data ) == ACCT_TYPE_ROOT )
00311             {
00312                 /* replace the gnc_book_init-created root account */
00313                 gnc_book_set_template_root(book, (Account *)n->data);
00314             }
00315             else
00316             {
00317                 /* This is an old data file that doesn't have a template root
00318                    account and this is a top level account.  Make it a child
00319                    of the template root account. */
00320                 acctRoot = gnc_book_get_template_root(book);
00321                 gnc_account_append_child( acctRoot, (Account*)n->data );
00322             }
00323         }
00324 
00325     }
00326 
00327     for ( n = txd->transactions; n; n = n->next )
00328     {
00329         /* insert transactions into accounts */
00330         add_transaction_local( data, (Transaction*)n->data );
00331     }
00332 
00333     return TRUE;
00334 }
00335 
00336 static gboolean
00337 add_pricedb_local(sixtp_gdv2 *data, GNCPriceDB *db)
00338 {
00339     /* gnc_pricedb_print_contents(db, stdout); */
00340     return TRUE;
00341 }
00342 
00343 static void
00344 do_counter_cb (const char *type, gpointer data_p, gpointer be_data_p)
00345 {
00346     GncXmlDataType_t *data = data_p;
00347     struct file_backend *be_data = be_data_p;
00348 
00349     g_return_if_fail (type && data && be_data);
00350     g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
00351 
00352     if (be_data->ok == TRUE)
00353         return;
00354 
00355     if (!safe_strcmp (be_data->tag, data->type_name))
00356         be_data->ok = TRUE;
00357 
00358     /* XXX: should we do anything with this counter? */
00359 }
00360 
00361 static gboolean
00362 gnc_counter_end_handler(gpointer data_for_children,
00363                         GSList* data_from_children, GSList* sibling_data,
00364                         gpointer parent_data, gpointer global_data,
00365                         gpointer *result, const gchar *tag)
00366 {
00367     char *strval;
00368     gint64 val;
00369     char *type;
00370     xmlNodePtr tree = (xmlNodePtr)data_for_children;
00371     gxpf_data *gdata = (gxpf_data*)global_data;
00372     sixtp_gdv2 *sixdata = (sixtp_gdv2*)gdata->parsedata;
00373     gboolean ret = TRUE;
00374 
00375     if (parent_data)
00376         return TRUE;
00377 
00378     /* OK.  For some messed up reason this is getting called again with a
00379        NULL tag.  So we ignore those cases */
00380     if (!tag)
00381         return TRUE;
00382 
00383     g_return_val_if_fail(tree, FALSE);
00384 
00385     /* Note: BADXML.
00386      *
00387      * This is invalid xml because the namespace isn't declared in the
00388      * tag itself. This should be changed to 'type' at some point. */
00389     type = (char*)xmlGetProp(tree, BAD_CAST "cd:type");
00390     strval = dom_tree_to_text(tree);
00391     if (!string_to_gint64(strval, &val))
00392     {
00393         PERR ("string_to_gint64 failed with input: %s",
00394               strval ? strval : "(null)");
00395         ret = FALSE;
00396     }
00397     else if (safe_strcmp(type, "transaction") == 0)
00398     {
00399         sixdata->counter.transactions_total = val;
00400     }
00401     else if (safe_strcmp(type, "account") == 0)
00402     {
00403         sixdata->counter.accounts_total = val;
00404     }
00405     else if (safe_strcmp(type, "book") == 0)
00406     {
00407         sixdata->counter.books_total = val;
00408     }
00409     else if (safe_strcmp(type, "commodity") == 0)
00410     {
00411         sixdata->counter.commodities_total = val;
00412     }
00413     else if (safe_strcmp(type, "schedxaction") == 0)
00414     {
00415         sixdata->counter.schedXactions_total = val;
00416     }
00417     else if (safe_strcmp(type, "budget") == 0)
00418     {
00419         sixdata->counter.budgets_total = val;
00420     }
00421     else
00422     {
00423         struct file_backend be_data;
00424 
00425         be_data.ok = FALSE;
00426         be_data.tag = type;
00427 
00428         qof_object_foreach_backend (GNC_FILE_BACKEND, do_counter_cb, &be_data);
00429 
00430         if (be_data.ok == FALSE)
00431         {
00432             PERR("Unknown type: %s", type ? type : "(null)");
00433             /* Do *NOT* flag this as an error. Gnucash 1.8 writes invalid
00434              * xml by writing the 'cd:type' attribute without providing
00435              * the namespace in the gnc:count-data tag.  The parser is
00436              * entirely within its rights to refuse to read this bad
00437              * attribute. Gnucash will function correctly without the data
00438              * in this tag, so just let the error pass. */
00439             ret = TRUE;
00440         }
00441     }
00442 
00443     g_free (strval);
00444     xmlFree (type);
00445     xmlFreeNode(tree);
00446     return ret;
00447 }
00448 
00449 static sixtp*
00450 gnc_counter_sixtp_parser_create(void)
00451 {
00452     return sixtp_dom_parser_new(gnc_counter_end_handler, NULL, NULL);
00453 }
00454 
00455 static void
00456 debug_print_counter_data(load_counter *data)
00457 {
00458     DEBUG("Transactions: Total: %d, Loaded: %d",
00459           data->transactions_total, data->transactions_loaded);
00460     DEBUG("Accounts: Total: %d, Loaded: %d",
00461           data->accounts_total, data->accounts_loaded);
00462     DEBUG("Books: Total: %d, Loaded: %d",
00463           data->books_total, data->books_loaded);
00464     DEBUG("Commodities: Total: %d, Loaded: %d",
00465           data->commodities_total, data->commodities_loaded);
00466     DEBUG("Scheduled Transactions: Total: %d, Loaded: %d",
00467           data->schedXactions_total, data->schedXactions_loaded);
00468     DEBUG("Budgets: Total: %d, Loaded: %d",
00469           data->budgets_total, data->budgets_loaded);
00470 }
00471 
00472 static void
00473 file_rw_feedback (sixtp_gdv2 *gd, const char *type)
00474 {
00475     load_counter *counter;
00476     int loaded, total, percentage;
00477 
00478     g_assert(gd != NULL);
00479     if (!gd->gui_display_fn)
00480         return;
00481 
00482     counter = &gd->counter;
00483     loaded = counter->transactions_loaded + counter->accounts_loaded +
00484              counter->books_loaded + counter->commodities_loaded +
00485              counter->schedXactions_loaded + counter->budgets_loaded;
00486     total = counter->transactions_total + counter->accounts_total +
00487             counter->books_total + counter->commodities_total +
00488             counter->schedXactions_total + counter->budgets_total;
00489     if (total == 0)
00490         total = 1;
00491 
00492     percentage = (loaded * 100) / total;
00493     if (percentage > 100)
00494     {
00495         /* FIXME: Perhaps the below should be replaced by:
00496         print_counter_data(counter); */
00497 //      printf("Transactions: Total: %d, Loaded: %d\n",
00498 //             counter->transactions_total, counter->transactions_loaded);
00499 //      printf("Accounts: Total: %d, Loaded: %d\n",
00500 //             counter->accounts_total, counter->accounts_loaded);
00501 //      printf("Books: Total: %d, Loaded: %d\n",
00502 //             counter->books_total, counter->books_loaded);
00503 //      printf("Commodities: Total: %d, Loaded: %d\n",
00504 //             counter->commodities_total, counter->commodities_loaded);
00505 //      printf("Scheduled Transactions: Total: %d, Loaded: %d\n",
00506 //             counter->schedXactions_total, counter->schedXactions_loaded);
00507 //      printf("Budgets: Total: %d, Loaded: %d\n",
00508 //           counter->budgets_total, counter->budgets_loaded);
00509     }
00510     gd->gui_display_fn(NULL, percentage);
00511 }
00512 
00513 static const char *BOOK_TAG = "gnc:book";
00514 static const char *BOOK_ID_TAG = "book:id";
00515 static const char *BOOK_SLOTS_TAG = "book:slots";
00516 static const char *ACCOUNT_TAG = "gnc:account";
00517 static const char *PRICEDB_TAG = "gnc:pricedb";
00518 static const char *COMMODITY_TAG = "gnc:commodity";
00519 static const char *COUNT_DATA_TAG = "gnc:count-data";
00520 static const char *TRANSACTION_TAG = "gnc:transaction";
00521 static const char *SCHEDXACTION_TAG = "gnc:schedxaction";
00522 static const char *TEMPLATE_TRANSACTION_TAG = "gnc:template-transactions";
00523 static const char *BUDGET_TAG = "gnc:budget";
00524 
00525 static void
00526 add_item_cb (const char *type, gpointer data_p, gpointer be_data_p)
00527 {
00528     GncXmlDataType_t *data = data_p;
00529     struct file_backend *be_data = be_data_p;
00530 
00531     g_return_if_fail (type && data && be_data);
00532     g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
00533 
00534     if (be_data->ok)
00535         return;
00536 
00537     if (!safe_strcmp (be_data->tag, data->type_name))
00538     {
00539         if (data->add_item)
00540             (data->add_item)(be_data->gd, be_data->data);
00541 
00542         be_data->ok = TRUE;
00543     }
00544 }
00545 
00546 static gboolean
00547 book_callback(const char *tag, gpointer globaldata, gpointer data)
00548 {
00549     sixtp_gdv2 *gd = (sixtp_gdv2*)globaldata;
00550 
00551     if (safe_strcmp(tag, ACCOUNT_TAG) == 0)
00552     {
00553         add_account_local(gd, (Account*)data);
00554     }
00555     else if (safe_strcmp(tag, PRICEDB_TAG) == 0)
00556     {
00557         add_pricedb_local(gd, (GNCPriceDB*)data);
00558     }
00559     else if (safe_strcmp(tag, COMMODITY_TAG) == 0)
00560     {
00561         add_commodity_local(gd, (gnc_commodity*)data);
00562     }
00563     else if (safe_strcmp(tag, TRANSACTION_TAG) == 0)
00564     {
00565         add_transaction_local(gd, (Transaction*)data);
00566     }
00567     else if (safe_strcmp(tag, SCHEDXACTION_TAG) == 0)
00568     {
00569         add_schedXaction_local(gd, (SchedXaction*)data);
00570     }
00571     else if (safe_strcmp(tag, TEMPLATE_TRANSACTION_TAG) == 0)
00572     {
00573         add_template_transaction_local( gd, (gnc_template_xaction_data*)data );
00574     }
00575     else if (safe_strcmp(tag, BUDGET_TAG) == 0)
00576     {
00577         // Nothing needed here.
00578     }
00579     else
00580     {
00581         struct file_backend be_data;
00582 
00583         be_data.ok = FALSE;
00584         be_data.tag = tag;
00585         be_data.gd = gd;
00586         be_data.data = data;
00587 
00588         qof_object_foreach_backend (GNC_FILE_BACKEND, add_item_cb, &be_data);
00589 
00590         if (be_data.ok == FALSE)
00591         {
00592             PWARN ("unexpected tag %s", tag);
00593         }
00594     }
00595     return TRUE;
00596 }
00597 
00598 static gboolean
00599 generic_callback(const char *tag, gpointer globaldata, gpointer data)
00600 {
00601     sixtp_gdv2 *gd = (sixtp_gdv2*)globaldata;
00602 
00603     if (safe_strcmp(tag, BOOK_TAG) == 0)
00604     {
00605         add_book_local(gd, (QofBook*)data);
00606         book_callback(tag, globaldata, data);
00607     }
00608     else
00609     {
00610         // PWARN ("importing pre-book-style XML data file");
00611         book_callback(tag, globaldata, data);
00612     }
00613     return TRUE;
00614 }
00615 
00616 static void
00617 add_parser_cb (const char *type, gpointer data_p, gpointer be_data_p)
00618 {
00619     GncXmlDataType_t *data = data_p;
00620     struct file_backend *be_data = be_data_p;
00621 
00622     g_return_if_fail (type && data && be_data);
00623     g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
00624 
00625     if (be_data->ok == FALSE)
00626         return;
00627 
00628     if (data->create_parser)
00629         if (!sixtp_add_some_sub_parsers(
00630                     be_data->parser, TRUE,
00631                     data->type_name, (data->create_parser)(),
00632                     NULL, NULL))
00633             be_data->ok = FALSE;
00634 }
00635 
00636 static void
00637 scrub_cb (const char *type, gpointer data_p, gpointer be_data_p)
00638 {
00639     GncXmlDataType_t *data = data_p;
00640     struct file_backend *be_data = be_data_p;
00641 
00642     g_return_if_fail (type && data && be_data);
00643     g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
00644 
00645     if (data->scrub)
00646         (data->scrub)(be_data->book);
00647 }
00648 
00649 static sixtp_gdv2 *
00650 gnc_sixtp_gdv2_new (
00651     QofBook *book,
00652     gboolean exporting,
00653     countCallbackFn countcallback,
00654     QofBePercentageFunc gui_display_fn)
00655 {
00656     sixtp_gdv2 *gd = g_new0(sixtp_gdv2, 1);
00657 
00658     if (gd == NULL) return NULL;
00659 
00660     gd->book = book;
00661     gd->counter.accounts_loaded = 0;
00662     gd->counter.accounts_total = 0;
00663     gd->counter.books_loaded = 0;
00664     gd->counter.books_total = 0;
00665     gd->counter.commodities_loaded = 0;
00666     gd->counter.commodities_total = 0;
00667     gd->counter.transactions_loaded = 0;
00668     gd->counter.transactions_total = 0;
00669     gd->counter.prices_loaded = 0;
00670     gd->counter.prices_total = 0;
00671     gd->counter.schedXactions_loaded = 0;
00672     gd->counter.schedXactions_total = 0;
00673     gd->counter.budgets_loaded = 0;
00674     gd->counter.budgets_total = 0;
00675     gd->exporting = exporting;
00676     gd->countCallback = countcallback;
00677     gd->gui_display_fn = gui_display_fn;
00678     return gd;
00679 }
00680 
00681 static gboolean
00682 qof_session_load_from_xml_file_v2_full(
00683     FileBackend *fbe, QofBook *book,
00684     sixtp_push_handler push_handler, gpointer push_user_data,
00685     QofBookFileType type)
00686 {
00687     Account *root;
00688     QofBackend *be = &fbe->be;
00689     sixtp_gdv2 *gd;
00690     sixtp *top_parser;
00691     sixtp *main_parser;
00692     sixtp *book_parser;
00693     struct file_backend be_data;
00694     gboolean retval;
00695     char *v2type = NULL;
00696 
00697     gd = gnc_sixtp_gdv2_new(book, FALSE, file_rw_feedback, be->percentage);
00698 
00699     top_parser = sixtp_new();
00700     main_parser = sixtp_new();
00701     book_parser = sixtp_new();
00702 
00703     if (type == GNC_BOOK_XML2_FILE)
00704         v2type = g_strdup(GNC_V2_STRING);
00705 
00706     if (!sixtp_add_some_sub_parsers(
00707                 top_parser, TRUE,
00708                 v2type, main_parser,
00709                 NULL, NULL))
00710     {
00711         g_free(v2type);
00712         goto bail;
00713     }
00714 
00715     g_free(v2type);
00716 
00717     if (!sixtp_add_some_sub_parsers(
00718                 main_parser, TRUE,
00719                 COUNT_DATA_TAG, gnc_counter_sixtp_parser_create(),
00720                 BOOK_TAG, book_parser,
00721 
00722                 /* the following are present here only to support
00723                  * the older, pre-book format.  Basically, the top-level
00724                  * book is implicit. */
00725                 PRICEDB_TAG, gnc_pricedb_sixtp_parser_create(),
00726                 COMMODITY_TAG, gnc_commodity_sixtp_parser_create(),
00727                 ACCOUNT_TAG, gnc_account_sixtp_parser_create(),
00728                 TRANSACTION_TAG, gnc_transaction_sixtp_parser_create(),
00729                 SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create(),
00730                 TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create(),
00731                 NULL, NULL))
00732     {
00733         goto bail;
00734     }
00735 
00736     if (!sixtp_add_some_sub_parsers(
00737                 book_parser, TRUE,
00738                 BOOK_ID_TAG, gnc_book_id_sixtp_parser_create(),
00739                 BOOK_SLOTS_TAG, gnc_book_slots_sixtp_parser_create(),
00740                 COUNT_DATA_TAG, gnc_counter_sixtp_parser_create(),
00741                 PRICEDB_TAG, gnc_pricedb_sixtp_parser_create(),
00742                 COMMODITY_TAG, gnc_commodity_sixtp_parser_create(),
00743                 ACCOUNT_TAG, gnc_account_sixtp_parser_create(),
00744                 BUDGET_TAG, gnc_budget_sixtp_parser_create(),
00745                 TRANSACTION_TAG, gnc_transaction_sixtp_parser_create(),
00746                 SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create(),
00747                 TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create(),
00748                 NULL, NULL))
00749     {
00750         goto bail;
00751     }
00752 
00753     be_data.ok = TRUE;
00754     be_data.parser = book_parser;
00755     qof_object_foreach_backend (GNC_FILE_BACKEND, add_parser_cb, &be_data);
00756     if (be_data.ok == FALSE)
00757         goto bail;
00758 
00759     /* stop logging while we load */
00760     xaccLogDisable ();
00761     xaccDisableDataScrubbing();
00762 
00763     if (push_handler)
00764     {
00765         gpointer parse_result = NULL;
00766         gxpf_data gpdata;
00767 
00768         gpdata.cb = generic_callback;
00769         gpdata.parsedata = gd;
00770         gpdata.bookdata = book;
00771 
00772         retval = sixtp_parse_push(top_parser, push_handler, push_user_data,
00773                                   NULL, &gpdata, &parse_result);
00774     }
00775     else
00776     {
00777         retval = gnc_xml_parse_file(top_parser, fbe->fullpath,
00778                                     generic_callback, gd, book);
00779     }
00780 
00781     if (!retval)
00782     {
00783         sixtp_destroy(top_parser);
00784         xaccLogEnable ();
00785         xaccEnableDataScrubbing();
00786         goto bail;
00787     }
00788     debug_print_counter_data(&gd->counter);
00789 
00790     /* destroy the parser */
00791     sixtp_destroy (top_parser);
00792     g_free(gd);
00793 
00794     xaccEnableDataScrubbing();
00795 
00796     /* Mark the session as saved */
00797     qof_book_mark_session_saved (book);
00798 
00799     /* Call individual scrub functions */
00800     memset(&be_data, 0, sizeof(be_data));
00801     be_data.book = book;
00802     qof_object_foreach_backend (GNC_FILE_BACKEND, scrub_cb, &be_data);
00803 
00804     /* fix price quote sources */
00805     root = gnc_book_get_root_account(book);
00806     xaccAccountTreeScrubQuoteSources (root, gnc_commodity_table_get_table(book));
00807 
00808     /* Fix account and transaction commodities */
00809     xaccAccountTreeScrubCommodities (root);
00810 
00811     /* Fix split amount/value */
00812     xaccAccountTreeScrubSplits (root);
00813 
00814     /* commit all groups, this completes the BeginEdit started when the
00815      * account_end_handler finished reading the account.
00816      */
00817     gnc_account_foreach_descendant(root,
00818                                    (AccountCb) xaccAccountCommitEdit,
00819                                    NULL);
00820 
00821     /* start logging again */
00822     xaccLogEnable ();
00823 
00824     return TRUE;
00825 
00826 bail:
00827     g_free(gd);
00828     return FALSE;
00829 }
00830 
00831 gboolean
00832 qof_session_load_from_xml_file_v2(FileBackend *fbe, QofBook *book,
00833                                   QofBookFileType type)
00834 {
00835     return qof_session_load_from_xml_file_v2_full(fbe, book, NULL, NULL, type);
00836 }
00837 
00838 /***********************************************************************/
00839 
00840 static gboolean
00841 write_counts(FILE* out, ...)
00842 {
00843     va_list ap;
00844     char *type;
00845     gboolean success = TRUE;
00846 
00847     va_start(ap, out);
00848     type = va_arg(ap, char *);
00849 
00850     while (success && type)
00851     {
00852         int amount = va_arg(ap, int);
00853 
00854         if (amount != 0)
00855         {
00856 #if GNUCASH_REALLY_BUILD_AN_XML_TREE_ON_OUTPUT
00857             char *val;
00858             xmlNodePtr node;
00859 
00860             val = g_strdup_printf("%d", amount);
00861 
00862             node = xmlNewNode(NULL, BAD_CAST COUNT_DATA_TAG);
00863             /* Note: BADXML.
00864              *
00865              * This is invalid xml because the namespace isn't
00866              * declared in the tag itself. This should be changed to
00867              * 'type' at some point. */
00868             xmlSetProp(node, BAD_CAST "cd:type", BAD_CAST type);
00869             xmlNodeAddContent(node, BAD_CAST val);
00870             g_free(val);
00871 
00872             xmlElemDump(out, NULL, node);
00873             xmlFreeNode(node);
00874 
00875             if (ferror(out) || fprintf(out, "\n") < 0)
00876             {
00877                 success = FALSE;
00878                 break;
00879             }
00880 #else
00881             if (fprintf(out, "<%s %s=\"%s\">%d</%s>\n",
00882                         COUNT_DATA_TAG, "cd:type", type, amount, COUNT_DATA_TAG) < 0)
00883             {
00884                 success = FALSE;
00885                 break;
00886             }
00887 #endif
00888 
00889         }
00890 
00891         type = va_arg(ap, char *);
00892     }
00893 
00894     va_end(ap);
00895     return success;
00896 }
00897 
00898 static gint
00899 compare_namespaces(gconstpointer a, gconstpointer b)
00900 {
00901     const gchar *sa = (const gchar *) a;
00902     const gchar *sb = (const gchar *) b;
00903     return(safe_strcmp(sa, sb));
00904 }
00905 
00906 static gint
00907 compare_commodity_ids(gconstpointer a, gconstpointer b)
00908 {
00909     const gnc_commodity *ca = (const gnc_commodity *) a;
00910     const gnc_commodity *cb = (const gnc_commodity *) b;
00911     return(safe_strcmp(gnc_commodity_get_mnemonic(ca),
00912                        gnc_commodity_get_mnemonic(cb)));
00913 }
00914 
00915 static gboolean write_pricedb (FILE *out, QofBook *book, sixtp_gdv2 *gd);
00916 static gboolean write_transactions (FILE *out, QofBook *book, sixtp_gdv2 *gd);
00917 static gboolean write_template_transaction_data (FILE *out, QofBook *book, sixtp_gdv2 *gd);
00918 static gboolean write_schedXactions(FILE *out, QofBook *book, sixtp_gdv2 *gd);
00919 static void write_budget (QofInstance *ent, gpointer data);
00920 
00921 static void
00922 write_counts_cb (const char *type, gpointer data_p, gpointer be_data_p)
00923 {
00924     GncXmlDataType_t *data = data_p;
00925     struct file_backend *be_data = be_data_p;
00926 
00927     g_return_if_fail (type && data && be_data);
00928     g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
00929 
00930     if (data->get_count)
00931         write_counts (be_data->out, data->type_name,
00932                       (data->get_count) (be_data->book),
00933                       NULL);
00934 }
00935 
00936 static void
00937 write_data_cb (const char *type, gpointer data_p, gpointer be_data_p)
00938 {
00939     GncXmlDataType_t *data = data_p;
00940     struct file_backend *be_data = be_data_p;
00941 
00942     g_return_if_fail (type && data && be_data);
00943     g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
00944 
00945     if (data->write && !ferror(be_data->out))
00946         (data->write)(be_data->out, be_data->book);
00947 }
00948 
00949 static gboolean
00950 write_book(FILE *out, QofBook *book, sixtp_gdv2 *gd)
00951 {
00952     struct file_backend be_data;
00953 
00954 #ifdef IMPLEMENT_BOOK_DOM_TREES_LATER
00955     /* We can't just blast out the dom tree, because the dom tree
00956      * doesn't have the books, transactions, etc underneath it.
00957      * But that is just as well, since I think the performance
00958      * will be much better if we write out as we go along
00959      */
00960     xmlNodePtr node;
00961 
00962     node = gnc_book_dom_tree_create(book);
00963 
00964     if (!node)
00965     {
00966         return FALSE;
00967     }
00968 
00969     xmlElemDump(out, NULL, node);
00970     xmlFreeNode(node);
00971 
00972     if (ferror(out) || fprintf(out, "\n") < 0)
00973     {
00974         return FALSE;
00975     }
00976 
00977 #endif
00978 
00979     be_data.out = out;
00980     be_data.book = book;
00981     be_data.gd = gd;
00982     if (fprintf( out, "<%s version=\"%s\">\n", BOOK_TAG, gnc_v2_book_version_string) < 0)
00983         return FALSE;
00984     if (!write_book_parts (out, book))
00985         return FALSE;
00986 
00987     /* gd->counter.{foo}_total fields should have all these totals
00988        already collected.  I don't know why we're re-calling all these
00989        functions.  */
00990     if (!write_counts(out,
00991                       "commodity",
00992                       gnc_commodity_table_get_size(
00993                           gnc_commodity_table_get_table(book)),
00994                       "account",
00995                       1 + gnc_account_n_descendants(gnc_book_get_root_account(book)),
00996                       "transaction",
00997                       gnc_book_count_transactions(book),
00998                       "schedxaction",
00999                       g_list_length(gnc_book_get_schedxactions(book)->sx_list),
01000                       "budget", qof_collection_count(
01001                           qof_book_get_collection(book, GNC_ID_BUDGET)),
01002                       NULL))
01003         return FALSE;
01004 
01005     qof_object_foreach_backend (GNC_FILE_BACKEND, write_counts_cb, &be_data);
01006 
01007     if (ferror(out)
01008             || !write_commodities(out, book, gd)
01009             || !write_pricedb(out, book, gd)
01010             || !write_accounts(out, book, gd)
01011             || !write_transactions(out, book, gd)
01012             || !write_template_transaction_data(out, book, gd)
01013             || !write_schedXactions(out, book, gd))
01014 
01015         return FALSE;
01016 
01017     qof_collection_foreach(qof_book_get_collection(book, GNC_ID_BUDGET),
01018                            write_budget, &be_data);
01019     if (ferror(out))
01020         return FALSE;
01021 
01022     qof_object_foreach_backend (GNC_FILE_BACKEND, write_data_cb, &be_data);
01023     if (ferror(out))
01024         return FALSE;
01025 
01026     if (fprintf( out, "</%s>\n", BOOK_TAG ) < 0)
01027         return FALSE;
01028 
01029     return TRUE;
01030 }
01031 
01032 gboolean
01033 write_commodities(FILE *out, QofBook *book, sixtp_gdv2 *gd)
01034 {
01035     gnc_commodity_table *tbl;
01036     GList *namespaces;
01037     GList *lp;
01038     gboolean success = TRUE;
01039 
01040     tbl = gnc_commodity_table_get_table(book);
01041 
01042     namespaces = gnc_commodity_table_get_namespaces(tbl);
01043     if (namespaces)
01044     {
01045         namespaces = g_list_sort(namespaces, compare_namespaces);
01046     }
01047 
01048     for (lp = namespaces; success && lp; lp = lp->next)
01049     {
01050         GList *comms, *lp2;
01051         xmlNodePtr comnode;
01052 
01053         comms = gnc_commodity_table_get_commodities(tbl, lp->data);
01054         comms = g_list_sort(comms, compare_commodity_ids);
01055 
01056         for (lp2 = comms; lp2; lp2 = lp2->next)
01057         {
01058             comnode = gnc_commodity_dom_tree_create(lp2->data);
01059             if (comnode == NULL)
01060                 continue;
01061 
01062             xmlElemDump(out, NULL, comnode);
01063             if (ferror(out) || fprintf(out, "\n") < 0)
01064             {
01065                 success = FALSE;
01066                 break;
01067             }
01068 
01069             xmlFreeNode(comnode);
01070             gd->counter.commodities_loaded++;
01071             run_callback(gd, "commodities");
01072         }
01073 
01074         g_list_free (comms);
01075     }
01076 
01077     if (namespaces) g_list_free (namespaces);
01078 
01079     return success;
01080 }
01081 
01082 static gboolean
01083 write_pricedb(FILE *out, QofBook *book, sixtp_gdv2 *gd)
01084 {
01085     xmlNodePtr node;
01086 
01087     node = gnc_pricedb_dom_tree_create(gnc_pricedb_get_db(book));
01088 
01089     if (!node)
01090     {
01091         return TRUE;
01092     }
01093 
01094     xmlElemDump(out, NULL, node);
01095     xmlFreeNode(node);
01096 
01097     if (ferror(out) || fprintf(out, "\n") < 0)
01098         return FALSE;
01099 
01100     return TRUE;
01101 }
01102 
01103 static int
01104 xml_add_trn_data(Transaction *t, gpointer data)
01105 {
01106     struct file_backend *be_data = data;
01107     xmlNodePtr node;
01108 
01109     node = gnc_transaction_dom_tree_create(t);
01110 
01111     xmlElemDump(be_data->out, NULL, node);
01112     xmlFreeNode(node);
01113 
01114     if (ferror(be_data->out) || fprintf(be_data->out, "\n") < 0)
01115         return -1;
01116 
01117     be_data->gd->counter.transactions_loaded++;
01118     run_callback(be_data->gd, "transaction");
01119     return 0;
01120 }
01121 
01122 static gboolean
01123 write_transactions(FILE *out, QofBook *book, sixtp_gdv2 *gd)
01124 {
01125     struct file_backend be_data;
01126 
01127     be_data.out = out;
01128     be_data.gd = gd;
01129     return 0 ==
01130            xaccAccountTreeForEachTransaction(gnc_book_get_root_account(book),
01131                    xml_add_trn_data,
01132                    (gpointer) &be_data);
01133 }
01134 
01135 static gboolean
01136 write_template_transaction_data( FILE *out, QofBook *book, sixtp_gdv2 *gd )
01137 {
01138     Account *ra;
01139     struct file_backend be_data;
01140 
01141     be_data.out = out;
01142     be_data.gd = gd;
01143 
01144     ra = gnc_book_get_template_root(book);
01145     if ( gnc_account_n_descendants(ra) > 0 )
01146     {
01147         if (fprintf(out, "<%s>\n", TEMPLATE_TRANSACTION_TAG) < 0
01148                 || !write_account_tree(out, ra, gd)
01149                 || xaccAccountTreeForEachTransaction(ra, xml_add_trn_data, (gpointer)&be_data)
01150                 || fprintf(out, "</%s>\n", TEMPLATE_TRANSACTION_TAG) < 0)
01151 
01152             return FALSE;
01153     }
01154 
01155     return TRUE;
01156 }
01157 
01158 static gboolean
01159 write_schedXactions( FILE *out, QofBook *book, sixtp_gdv2 *gd)
01160 {
01161     GList *schedXactions;
01162     SchedXaction *tmpSX;
01163     xmlNodePtr node;
01164 
01165     schedXactions = gnc_book_get_schedxactions(book)->sx_list;
01166 
01167     if (schedXactions == NULL)
01168         return TRUE;
01169 
01170     do
01171     {
01172         tmpSX = schedXactions->data;
01173         node = gnc_schedXaction_dom_tree_create( tmpSX );
01174         xmlElemDump( out, NULL, node );
01175         xmlFreeNode(node);
01176         if (ferror(out) || fprintf(out, "\n") < 0)
01177             return FALSE;
01178         gd->counter.schedXactions_loaded++;
01179         run_callback(gd, "schedXactions");
01180     }
01181     while ( (schedXactions = schedXactions->next) );
01182 
01183     return TRUE;
01184 }
01185 
01186 static void
01187 write_budget (QofInstance *ent, gpointer data)
01188 {
01189     xmlNodePtr node;
01190     struct file_backend* be = data;
01191 
01192     GncBudget *bgt = GNC_BUDGET(ent);
01193 
01194     if (ferror(be->out))
01195         return;
01196 
01197     node = gnc_budget_dom_tree_create(bgt);
01198     xmlElemDump( be->out, NULL, node );
01199     xmlFreeNode(node);
01200     if (ferror(be->out) || fprintf(be->out, "\n") < 0)
01201         return;
01202 
01203     be->gd->counter.budgets_loaded++;
01204     run_callback(be->gd, "budgets");
01205 }
01206 
01207 gboolean
01208 gnc_xml2_write_namespace_decl (FILE *out, const char *namespace)
01209 {
01210     g_return_val_if_fail(namespace, FALSE);
01211     return fprintf(out, "\n     xmlns:%s=\"http://www.gnucash.org/XML/%s\"",
01212                    namespace, namespace) >= 0;
01213 }
01214 
01215 static void
01216 do_write_namespace_cb (const char *type, gpointer data_p, gpointer file_p)
01217 {
01218     GncXmlDataType_t *data = data_p;
01219     FILE *out = file_p;
01220 
01221     g_return_if_fail (type && data && out);
01222     g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
01223 
01224     if (data->ns && !ferror(out))
01225         (data->ns)(out);
01226 }
01227 
01228 static gboolean
01229 write_v2_header (FILE *out)
01230 {
01231     if (fprintf(out, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n") < 0
01232             || fprintf(out, "<" GNC_V2_STRING) < 0
01233 
01234             || !gnc_xml2_write_namespace_decl (out, "gnc")
01235             || !gnc_xml2_write_namespace_decl (out, "act")
01236             || !gnc_xml2_write_namespace_decl (out, "book")
01237             || !gnc_xml2_write_namespace_decl (out, "cd")
01238             || !gnc_xml2_write_namespace_decl (out, "cmdty")
01239             || !gnc_xml2_write_namespace_decl (out, "price")
01240             || !gnc_xml2_write_namespace_decl (out, "slot")
01241             || !gnc_xml2_write_namespace_decl (out, "split")
01242             || !gnc_xml2_write_namespace_decl (out, "sx")
01243             || !gnc_xml2_write_namespace_decl (out, "trn")
01244             || !gnc_xml2_write_namespace_decl (out, "ts")
01245             || !gnc_xml2_write_namespace_decl (out, "fs")
01246             || !gnc_xml2_write_namespace_decl (out, "bgt")
01247             || !gnc_xml2_write_namespace_decl (out, "recurrence")
01248             || !gnc_xml2_write_namespace_decl (out, "lot"))
01249 
01250         return FALSE;
01251 
01252     /* now cope with the plugins */
01253     qof_object_foreach_backend (GNC_FILE_BACKEND, do_write_namespace_cb, out);
01254 
01255     if (ferror(out) || fprintf(out, ">\n") < 0)
01256         return FALSE;
01257 
01258     return TRUE;
01259 }
01260 
01261 gboolean
01262 gnc_book_write_to_xml_filehandle_v2(QofBook *book, FILE *out)
01263 {
01264     QofBackend *be;
01265     sixtp_gdv2 *gd;
01266     gboolean success = TRUE;
01267 
01268     if (!out) return FALSE;
01269 
01270     if (!write_v2_header(out)
01271             || !write_counts(out, "book", 1, NULL))
01272         return FALSE;
01273 
01274     be = qof_book_get_backend(book);
01275     gd = gnc_sixtp_gdv2_new(book, FALSE, file_rw_feedback, be->percentage);
01276     gd->counter.commodities_total =
01277         gnc_commodity_table_get_size(gnc_commodity_table_get_table(book));
01278     gd->counter.accounts_total = 1 +
01279                                  gnc_account_n_descendants(gnc_book_get_root_account(book));
01280     gd->counter.transactions_total = gnc_book_count_transactions(book);
01281     gd->counter.schedXactions_total =
01282         g_list_length(gnc_book_get_schedxactions(book)->sx_list);
01283     gd->counter.budgets_total = qof_collection_count(
01284                                     qof_book_get_collection(book, GNC_ID_BUDGET));
01285 
01286     if (!write_book(out, book, gd)
01287             || fprintf(out, "</" GNC_V2_STRING ">\n\n") < 0)
01288         success = FALSE;
01289 
01290     g_free(gd);
01291     return success;
01292 }
01293 
01294 /*
01295  * This function is called by the "export" code.
01296  */
01297 gboolean
01298 gnc_book_write_accounts_to_xml_filehandle_v2(QofBackend *be, QofBook *book, FILE *out)
01299 {
01300     gnc_commodity_table *table;
01301     Account *root;
01302     int ncom, nacc;
01303     sixtp_gdv2 *gd;
01304     gboolean success = TRUE;
01305 
01306     if (!out) return FALSE;
01307 
01308     root = gnc_book_get_root_account(book);
01309     nacc = 1 + gnc_account_n_descendants(root);
01310 
01311     table = gnc_commodity_table_get_table(book);
01312     ncom = gnc_commodity_table_get_size(table);
01313 
01314     if (!write_v2_header(out)
01315             || !write_counts(out, "commodity", ncom, "account", nacc, NULL))
01316         return FALSE;
01317 
01318     gd = gnc_sixtp_gdv2_new(book, TRUE, file_rw_feedback, be->percentage);
01319     gd->counter.commodities_total = ncom;
01320     gd->counter.accounts_total = nacc;
01321 
01322     if (!write_commodities(out, book, gd)
01323             || !write_accounts(out, book, gd)
01324             || fprintf(out, "</" GNC_V2_STRING ">\n\n") < 0)
01325         success = FALSE;
01326 
01327     g_free(gd);
01328     return success;
01329 }
01330 
01331 #define BUFLEN 4096
01332 
01333 /* Compress or decompress function that is to be run in a separate thread.
01334  * Returns 1 on success or 0 otherwise, stuffed into a pointer type. */
01335 static gpointer
01336 gz_thread_func(gz_thread_params_t *params)
01337 {
01338     gchar buffer[BUFLEN];
01339     gssize bytes;
01340     gint gzval;
01341     gzFile file;
01342     gint success = 1;
01343 
01344 #ifdef G_OS_WIN32
01345     {
01346         gchar *conv_name = g_win32_locale_filename_from_utf8(params->filename);
01347         gchar *perms;
01348 
01349         if (!conv_name)
01350         {
01351             g_warning("Could not convert '%s' to system codepage",
01352                       params->filename);
01353             success = 0;
01354             goto cleanup_gz_thread_func;
01355         }
01356 
01357         if (strchr(params->perms, 'b'))
01358             perms = g_strdup(params->perms);
01359         else
01360             perms = g_strdup_printf("%cb%s", *params->perms, params->perms + 1);
01361 
01362         file = gzopen(conv_name, perms);
01363         g_free(perms);
01364         g_free(conv_name);
01365     }
01366 #else /* !G_OS_WIN32 */
01367     file = gzopen(params->filename, params->perms);
01368 #endif /* G_OS_WIN32 */
01369 
01370     if (file == NULL)
01371     {
01372         g_warning("Child threads gzopen failed");
01373         success = 0;
01374         goto cleanup_gz_thread_func;
01375     }
01376 
01377     if (params->compress)
01378     {
01379         while (success)
01380         {
01381             bytes = read(params->fd, buffer, BUFLEN);
01382             if (bytes > 0)
01383             {
01384                 if (gzwrite(file, buffer, bytes) <= 0)
01385                 {
01386                     gint errnum;
01387                     const gchar *error = gzerror(file, &errnum);
01388                     g_warning("Could not write the compressed file '%s'. The error is: '%s' (%d)",
01389                               params->filename, error, errnum);
01390                     success = 0;
01391                 }
01392             }
01393             else if (bytes == 0)
01394             {
01395                 break;
01396             }
01397             else
01398             {
01399                 g_warning("Could not read from pipe. The error is '%s' (errno %d)",
01400                           g_strerror(errno) ? g_strerror(errno) : "", errno);
01401                 success = 0;
01402             }
01403         }
01404     }
01405     else
01406     {
01407         while (success)
01408         {
01409             gzval = gzread(file, buffer, BUFLEN);
01410             if (gzval > 0)
01411             {
01412                 if (
01413 #if COMPILER(MSVC)
01414                     _write
01415 #else
01416                     write
01417 #endif
01418                     (params->fd, buffer, gzval) < 0)
01419                 {
01420                     g_warning("Could not write to pipe. The error is '%s' (%d)",
01421                               g_strerror(errno) ? g_strerror(errno) : "", errno);
01422                     success = 0;
01423                 }
01424             }
01425             else if (gzval == 0)
01426             {
01427                 break;
01428             }
01429             else
01430             {
01431                 gint errnum;
01432                 const gchar *error = gzerror(file, &errnum);
01433                 g_warning("Could not read from compressed file '%s'. The error is: '%s' (%d)",
01434                           params->filename, error, errnum);
01435                 success = 0;
01436             }
01437         }
01438     }
01439 
01440     if ((gzval = gzclose(file)) != Z_OK)
01441     {
01442         g_warning("Could not close the compressed file '%s' (errnum %d)",
01443                   params->filename, gzval);
01444         success = 0;
01445     }
01446 
01447 cleanup_gz_thread_func:
01448     close(params->fd);
01449     g_free(params->filename);
01450     g_free(params->perms);
01451     g_free(params);
01452 
01453     return GINT_TO_POINTER(success);
01454 }
01455 
01456 static FILE *
01457 try_gz_open (const char *filename, const char *perms, gboolean use_gzip,
01458              gboolean compress)
01459 {
01460     if (strstr(filename, ".gz.") != NULL) /* its got a temp extension */
01461         use_gzip = TRUE;
01462 
01463     if (!use_gzip)
01464         return g_fopen(filename, perms);
01465 
01466     {
01467         int filedes[2];
01468         GThread *thread;
01469         GError *error = NULL;
01470         gz_thread_params_t *params;
01471         FILE *file;
01472 
01473 #ifdef G_OS_WIN32
01474         if (_pipe(filedes, 4096, _O_BINARY) < 0)
01475         {
01476 #else
01477         if (pipe(filedes) < 0)
01478         {
01479 #endif
01480             g_warning("Pipe call failed. Opening uncompressed file.");
01481             return g_fopen(filename, perms);
01482         }
01483 
01484         params = g_new(gz_thread_params_t, 1);
01485         params->fd = filedes[compress ? 0 : 1];
01486         params->filename = g_strdup(filename);
01487         params->perms = g_strdup(perms);
01488         params->compress = compress;
01489 
01490         thread = g_thread_create((GThreadFunc) gz_thread_func, params, TRUE, &error);
01491         if (!thread)
01492         {
01493             g_warning("Could not create thread for (de)compression: %s",
01494                       error->message);
01495             g_error_free(error);
01496             g_free(params->filename);
01497             g_free(params->perms);
01498             g_free(params);
01499             close(filedes[0]);
01500             close(filedes[1]);
01501 
01502             return g_fopen(filename, perms);
01503         }
01504 
01505         if (compress)
01506             file = fdopen(filedes[1], "w");
01507         else
01508             file = fdopen(filedes[0], "r");
01509 
01510         G_LOCK(threads);
01511         if (!threads)
01512             threads = g_hash_table_new(g_direct_hash, g_direct_equal);
01513 
01514         g_hash_table_insert(threads, file, thread);
01515         G_UNLOCK(threads);
01516 
01517         return file;
01518     }
01519 }
01520 
01521 static gboolean
01522 wait_for_gzip(FILE *file)
01523 {
01524     gboolean retval = TRUE;
01525 
01526     G_LOCK(threads);
01527     if (threads)
01528     {
01529         GThread *thread = g_hash_table_lookup(threads, file);
01530         if (thread)
01531         {
01532             g_hash_table_remove(threads, file);
01533             retval = GPOINTER_TO_INT(g_thread_join(thread));
01534         }
01535     }
01536     G_UNLOCK(threads);
01537 
01538     return retval;
01539 }
01540 
01541 gboolean
01542 gnc_book_write_to_xml_file_v2(
01543     QofBook *book,
01544     const char *filename,
01545     gboolean compress)
01546 {
01547     FILE *out;
01548     gboolean success = TRUE;
01549 
01550     out = try_gz_open(filename, "w", compress, TRUE);
01551 
01552     /* Try to write as much as possible */
01553     if (!out
01554             || !gnc_book_write_to_xml_filehandle_v2(book, out)
01555             || !write_emacs_trailer(out))
01556         success = FALSE;
01557 
01558     /* Close the output stream */
01559     if (out && fclose(out))
01560         success = FALSE;
01561 
01562     /* Optionally wait for parallel compression threads */
01563     if (out && compress)
01564         if (!wait_for_gzip(out))
01565             success = FALSE;
01566 
01567     return success;
01568 }
01569 
01570 /*
01571  * Have to pass in the backend as this routine needs the temporary
01572  * backend for file export, not the real backend which could be
01573  * postgress or anything else.
01574  */
01575 gboolean
01576 gnc_book_write_accounts_to_xml_file_v2(
01577     QofBackend *be,
01578     QofBook *book,
01579     const char *filename)
01580 {
01581     FILE *out;
01582     gboolean success = TRUE;
01583 
01584     out = g_fopen(filename, "w");
01585 
01586     /* Try to write as much as possible */
01587     if (!out
01588             || !gnc_book_write_accounts_to_xml_filehandle_v2 (be, book, out)
01589             || !write_emacs_trailer(out))
01590         success = FALSE;
01591 
01592     /* Close the output stream */
01593     if (out && fclose(out))
01594         success = FALSE;
01595 
01596     if (!success
01597             && qof_backend_get_error(be) == ERR_BACKEND_NO_ERR)
01598     {
01599 
01600         /* Use a generic write error code */
01601         qof_backend_set_error(be, ERR_FILEIO_WRITE_ERROR);
01602     }
01603 
01604     return success;
01605 }
01606 
01607 /***********************************************************************/
01608 static gboolean
01609 is_gzipped_file(const gchar *name)
01610 {
01611     unsigned char buf[2];
01612     int fd = g_open(name, O_RDONLY, 0);
01613 
01614     if (fd == -1)
01615     {
01616         return FALSE;
01617     }
01618 
01619     if (read(fd, buf, 2) != 2)
01620     {
01621         close(fd);
01622         return FALSE;
01623     }
01624     close(fd);
01625 
01626     if (buf[0] == 037 && buf[1] == 0213)
01627     {
01628         return TRUE;
01629     }
01630 
01631     return FALSE;
01632 }
01633 
01634 QofBookFileType
01635 gnc_is_xml_data_file_v2(const gchar *name, gboolean *with_encoding)
01636 {
01637     if (is_gzipped_file(name))
01638     {
01639         gzFile file = NULL;
01640         char first_chunk[256];
01641         int num_read;
01642 
01643 #ifdef G_OS_WIN32
01644         {
01645             gchar *conv_name = g_win32_locale_filename_from_utf8(name);
01646             if (!conv_name)
01647                 g_warning("Could not convert '%s' to system codepage", name);
01648             else
01649             {
01650                 file = gzopen(conv_name, "rb");
01651                 g_free(conv_name);
01652             }
01653         }
01654 #else
01655         file = gzopen(name, "r");
01656 #endif
01657         if (file == NULL)
01658             return GNC_BOOK_NOT_OURS;
01659 
01660         num_read = gzread(file, first_chunk, sizeof(first_chunk) - 1);
01661         gzclose(file);
01662 
01663         if (num_read < 1)
01664             return GNC_BOOK_NOT_OURS;
01665 
01666         return gnc_is_our_first_xml_chunk(first_chunk, with_encoding);
01667     }
01668 
01669     return (gnc_is_our_xml_file(name, with_encoding));
01670 }
01671 
01672 
01673 static void
01674 replace_character_references(gchar *string)
01675 {
01676     gchar *cursor, *semicolon, *tail;
01677     glong number;
01678 
01679     for (cursor = strstr(string, "&#");
01680             cursor && *cursor;
01681             cursor = strstr(cursor, "&#"))
01682     {
01683         semicolon = strchr(cursor, ';');
01684         if (semicolon && *semicolon)
01685         {
01686 
01687             /* parse number */
01688             errno = 0;
01689             if (*(cursor + 2) == 'x')
01690             {
01691                 number = strtol(cursor + 3, &tail, 16);
01692             }
01693             else
01694             {
01695                 number = strtol(cursor + 2, &tail, 10);
01696             }
01697             if (errno || tail != semicolon || number < 0 || number > 255)
01698             {
01699                 PWARN("Illegal character reference");
01700                 return;
01701             }
01702 
01703             /* overwrite '&' with the specified character */
01704             *cursor = (gchar) number;
01705             cursor++;
01706             if (*(semicolon + 1))
01707             {
01708                 /* move text after semicolon the the left */
01709                 tail = g_strdup(semicolon + 1);
01710                 strcpy(cursor, tail);
01711                 g_free(tail);
01712             }
01713             else
01714             {
01715                 /* cut here */
01716                 *cursor = '\0';
01717             }
01718 
01719         }
01720         else
01721         {
01722             PWARN("Unclosed character reference");
01723             return;
01724         }
01725     }
01726 }
01727 
01728 static void
01729 conv_free(conv_type *conv)
01730 {
01731     if (conv)
01732     {
01733         g_free(conv->utf8_string);
01734         g_free(conv);
01735     }
01736 }
01737 
01738 static void
01739 conv_list_free(GList *conv_list)
01740 {
01741     g_list_foreach(conv_list, (GFunc) conv_free, NULL);
01742     g_list_free(conv_list);
01743 }
01744 
01745 typedef struct
01746 {
01747     GQuark encoding;
01748     GIConv iconv;
01749 } iconv_item_type;
01750 
01751 gint
01752 gnc_xml2_find_ambiguous(const gchar *filename, GList *encodings,
01753                         GHashTable **unique, GHashTable **ambiguous,
01754                         GList **impossible)
01755 {
01756     FILE *file = NULL;
01757     GList *iconv_list = NULL, *conv_list = NULL, *iter;
01758     iconv_item_type *iconv_item = NULL, *ascii = NULL;
01759     const gchar *enc;
01760     GHashTable *processed = NULL;
01761     gint n_impossible = 0;
01762     GError *error = NULL;
01763     gboolean is_compressed;
01764     gboolean clean_return = FALSE;
01765 
01766     is_compressed = is_gzipped_file(filename);
01767     file = try_gz_open(filename, "r", is_compressed, FALSE);
01768     if (file == NULL)
01769     {
01770         PWARN("Unable to open file %s", filename);
01771         goto cleanup_find_ambs;
01772     }
01773 
01774     /* we need ascii */
01775     ascii = g_new(iconv_item_type, 1);
01776     ascii->encoding = g_quark_from_string("ASCII");
01777     ascii->iconv = g_iconv_open("UTF-8", "ASCII");
01778     if (ascii->iconv == (GIConv) - 1)
01779     {
01780         PWARN("Unable to open ASCII ICONV conversion descriptor");
01781         goto cleanup_find_ambs;
01782     }
01783 
01784     /* call iconv_open on encodings */
01785     for (iter = encodings; iter; iter = iter->next)
01786     {
01787         iconv_item = g_new(iconv_item_type, 1);
01788         iconv_item->encoding = GPOINTER_TO_UINT (iter->data);
01789         if (iconv_item->encoding == ascii->encoding)
01790         {
01791             continue;
01792         }
01793 
01794         enc = g_quark_to_string(iconv_item->encoding);
01795         iconv_item->iconv = g_iconv_open("UTF-8", enc);
01796         if (iconv_item->iconv == (GIConv) - 1)
01797         {
01798             PWARN("Unable to open IConv conversion descriptor for '%s'", enc);
01799             goto cleanup_find_ambs;
01800         }
01801         else
01802         {
01803             iconv_list = g_list_prepend(iconv_list, iconv_item);
01804         }
01805     }
01806 
01807     /* prepare data containers */
01808     if (unique)
01809         *unique = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
01810                                         (GDestroyNotify) conv_free);
01811     if (ambiguous)
01812         *ambiguous = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
01813                                            (GDestroyNotify) conv_list_free);
01814     if (impossible)
01815         *impossible = NULL;
01816     processed = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
01817 
01818     /* loop through lines */
01819     while (1)
01820     {
01821         gchar line[256], *word, *utf8;
01822         gchar **word_array, **word_cursor;
01823         conv_type *conv = NULL;
01824 
01825         if (!fgets(line, sizeof(line) - 1, file))
01826         {
01827             if (feof(file))
01828             {
01829                 break;
01830             }
01831             else
01832             {
01833                 goto cleanup_find_ambs;
01834             }
01835         }
01836 
01837         g_strchomp(line);
01838         replace_character_references(line);
01839         word_array = g_strsplit_set(line, "> <", 0);
01840 
01841         /* loop through words */
01842         for (word_cursor = word_array; *word_cursor; word_cursor++)
01843         {
01844             word = *word_cursor;
01845             if (!word)
01846                 continue;
01847 
01848             utf8 = g_convert_with_iconv(word, -1, ascii->iconv,
01849                                         NULL, NULL, &error);
01850             if (utf8)
01851             {
01852                 /* pure ascii */
01853                 g_free(utf8);
01854                 continue;
01855             }
01856             g_error_free(error);
01857             error = NULL;
01858 
01859             if (g_hash_table_lookup_extended(processed, word, NULL, NULL))
01860             {
01861                 /* already processed */
01862                 continue;
01863             }
01864 
01865             /* loop through encodings */
01866             conv_list = NULL;
01867             for (iter = iconv_list; iter; iter = iter->next)
01868             {
01869                 iconv_item = iter->data;
01870                 utf8 = g_convert_with_iconv(word, -1, iconv_item->iconv,
01871                                             NULL, NULL, &error);
01872                 if (utf8)
01873                 {
01874                     conv = g_new(conv_type, 1);
01875                     conv->encoding = iconv_item->encoding;
01876                     conv->utf8_string = utf8;
01877                     conv_list = g_list_prepend(conv_list, conv);
01878                 }
01879                 else
01880                 {
01881                     g_error_free(error);
01882                     error = NULL;
01883                 }
01884             }
01885 
01886             /* no successful conversion */
01887             if (!conv_list)
01888             {
01889                 if (impossible)
01890                     *impossible = g_list_append(*impossible, g_strdup(word));
01891                 n_impossible++;
01892             }
01893 
01894             /* more than one successful conversion */
01895             else if (conv_list->next)
01896             {
01897                 if (ambiguous)
01898                 {
01899                     g_hash_table_insert(*ambiguous, g_strdup(word), conv_list);
01900                 }
01901                 else
01902                 {
01903                     conv_list_free(conv_list);
01904                 }
01905             }
01906 
01907             /* only one successful conversion */
01908             else
01909             {
01910                 if (unique)
01911                 {
01912                     g_hash_table_insert(*unique, g_strdup(word), conv);
01913                 }
01914                 else
01915                 {
01916                     conv_free(conv);
01917                 }
01918                 g_list_free(conv_list);
01919             }
01920 
01921             g_hash_table_insert(processed, g_strdup(word), NULL);
01922         }
01923         g_strfreev(word_array);
01924     }
01925 
01926     clean_return = TRUE;
01927 
01928 cleanup_find_ambs:
01929 
01930     if (iconv_list)
01931     {
01932         for (iter = iconv_list; iter; iter = iter->next)
01933         {
01934             if (iter->data)
01935             {
01936                 g_iconv_close(((iconv_item_type*) iter->data)->iconv);
01937                 g_free(iter->data);
01938             }
01939         }
01940         g_list_free(iconv_list);
01941     }
01942     if (processed)
01943         g_hash_table_destroy(processed);
01944     if (ascii)
01945         g_free(ascii);
01946     if (file)
01947     {
01948         fclose(file);
01949         if (is_compressed)
01950             wait_for_gzip(file);
01951     }
01952 
01953     return (clean_return) ? n_impossible : -1;
01954 }
01955 
01956 typedef struct
01957 {
01958     gchar *filename;
01959     GHashTable *subst;
01960 } push_data_type;
01961 
01962 static void
01963 parse_with_subst_push_handler (xmlParserCtxtPtr xml_context,
01964                                push_data_type *push_data)
01965 {
01966     const gchar *filename;
01967     FILE *file = NULL;
01968     GIConv ascii = (GIConv) - 1;
01969     GString *output = NULL;
01970     GError *error = NULL;
01971     gboolean is_compressed;
01972 
01973     filename = push_data->filename;
01974     is_compressed = is_gzipped_file(filename);
01975     file = try_gz_open(filename, "r", is_compressed, FALSE);
01976     if (file == NULL)
01977     {
01978         PWARN("Unable to open file %s", filename);
01979         goto cleanup_push_handler;
01980     }
01981 
01982     ascii = g_iconv_open("UTF-8", "ASCII");
01983     if (ascii == (GIConv) - 1)
01984     {
01985         PWARN("Unable to open ASCII ICONV conversion descriptor");
01986         goto cleanup_push_handler;
01987     }
01988 
01989     /* loop through lines */
01990     while (1)
01991     {
01992         gchar line[256], *word, *repl, *utf8;
01993         gint pos, len;
01994         gchar *start, *cursor;
01995 
01996         if (!fgets(line, sizeof(line) - 1, file))
01997         {
01998             if (feof(file))
01999             {
02000                 break;
02001             }
02002             else
02003             {
02004                 goto cleanup_push_handler;
02005             }
02006         }
02007 
02008         replace_character_references(line);
02009         output = g_string_new(line);
02010 
02011         /* loop through words */
02012         cursor = output->str;
02013         pos = 0;
02014         while (1)
02015         {
02016             /* ignore delimiters */
02017             while (*cursor == '>' || *cursor == ' ' || *cursor == '<' ||
02018                     *cursor == '\n')
02019             {
02020                 cursor++;
02021                 pos += 1;
02022             }
02023 
02024             if (!*cursor)
02025                 /* welcome to EOL */
02026                 break;
02027 
02028             /* search for a delimiter */
02029             start = cursor;
02030             len = 0;
02031             while (*cursor && *cursor != '>' && *cursor != ' ' && *cursor != '<' &&
02032                     *cursor != '\n')
02033             {
02034                 cursor++;
02035                 len++;
02036             }
02037 
02038             utf8 = g_convert_with_iconv(start, len, ascii, NULL, NULL, &error);
02039 
02040             if (utf8)
02041             {
02042                 /* pure ascii */
02043                 g_free(utf8);
02044                 pos += len;
02045             }
02046             else
02047             {
02048                 g_error_free(error);
02049                 error = NULL;
02050 
02051                 word = g_strndup(start, len);
02052                 repl = g_hash_table_lookup(push_data->subst, word);
02053                 g_free(word);
02054                 if (repl)
02055                 {
02056                     /* there is a replacement */
02057                     output = g_string_insert(g_string_erase(output, pos, len),
02058                                              pos, repl);
02059                     pos += strlen(repl);
02060                     cursor = output->str + pos;
02061                 }
02062                 else
02063                 {
02064                     /* there is no replacement, return immediately */
02065                     goto cleanup_push_handler;
02066                 }
02067             }
02068         }
02069 
02070         if (xmlParseChunk(xml_context, output->str, output->len, 0) != 0)
02071         {
02072             goto cleanup_push_handler;
02073         }
02074     }
02075 
02076     /* last chunk */
02077     xmlParseChunk(xml_context, "", 0, 1);
02078 
02079 cleanup_push_handler:
02080 
02081     if (output)
02082         g_string_free(output, TRUE);
02083     if (ascii != (GIConv) - 1)
02084         g_iconv_close(ascii);
02085     if (file)
02086     {
02087         fclose(file);
02088         if (is_compressed)
02089             wait_for_gzip(file);
02090     }
02091 }
02092 
02093 gboolean
02094 gnc_xml2_parse_with_subst (FileBackend *fbe, QofBook *book, GHashTable *subst)
02095 {
02096     push_data_type *push_data;
02097     gboolean success;
02098 
02099     push_data = g_new(push_data_type, 1);
02100     push_data->filename = fbe->fullpath;
02101     push_data->subst = subst;
02102 
02103     success = qof_session_load_from_xml_file_v2_full(
02104                   fbe, book, (sixtp_push_handler) parse_with_subst_push_handler,
02105                   push_data, GNC_BOOK_XML2_FILE);
02106 
02107     if (success)
02108         qof_book_kvp_changed(book);
02109 
02110     return success;
02111 }
02112 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines