1 /********************************************************************\
2  * Copyright (C) 2000,2001 Gnumatic Inc.                            *
3  *                                                                  *
4  * This program is free software; you can redistribute it and/or    *
5  * modify it under the terms of the GNU General Public License as   *
6  * published by the Free Software Foundation; either version 2 of   *
7  * the License, or (at your option) any later version.              *
8  *                                                                  *
9  * This program is distributed in the hope that it will be useful,  *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
12  * GNU General Public License for more details.                     *
13  *                                                                  *
14  * You should have received a copy of the GNU General Public License*
15  * along with this program; if not, contact:                        *
16  *                                                                  *
17  * Free Software Foundation           Voice:  +1-617-542-5942       *
18  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
19  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
20 \********************************************************************/
21 #include <glib.h>
22 #include <glib/gstdio.h>
23 
24 extern "C"
25 {
26 #include <config.h>
27 
28 #include <platform.h>
29 #if PLATFORM(WINDOWS)
30 #ifdef __STRICT_ANSI__
31 #undef __STRICT_ANSI__
32 #define __STRICT_ANSI_UNSET__ 1
33 #endif
34 #ifdef _NO_OLDNAMES
35 #undef _NO_OLDNAMES
36 #endif
37 #ifdef _UWIN
38 #undef _UWIN
39 #endif
40 #include <windows.h>
41 #endif
42 #include <fcntl.h>
43 #include <string.h>
44 #ifdef HAVE_UNISTD_H
45 # include <unistd.h>
46 #endif
47 #include <zlib.h>
48 #include <errno.h>
49 
50 #include "gnc-engine.h"
51 #include "gnc-pricedb-p.h"
52 #include "Scrub.h"
53 #include "SX-book.h"
54 #include "SX-book-p.h"
55 #include "Transaction.h"
56 #include "TransactionP.h"
57 #include "TransLog.h"
58 #if PLATFORM(WINDOWS)
59 #ifdef __STRICT_ANSI_UNSET__
60 #undef __STRICT_ANSI_UNSET__
61 #define __STRICT_ANSI__ 1
62 #endif
63 #endif
64 #if COMPILER(MSVC)
65 # define g_fopen fopen
66 # define g_open _open
67 #endif
68 }
69 
70 #include "gnc-xml-backend.hpp"
71 #include "sixtp-parsers.h"
72 #include "sixtp-utils.h"
73 #include "gnc-xml.h"
74 #include "io-utils.h"
75 #include "sixtp-dom-parsers.h"
76 #include "io-gncxml-v2.h"
77 #include "io-gncxml-gen.h"
78 
79 /* Do not treat -Wstrict-aliasing warnings as errors because of problems of the
80  * G_LOCK* macros as declared by glib.  See
81  * https://bugs.gnucash.org/show_bug.cgi?id=316221 for additional information.
82  */
83 #if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 2)
84 #    pragma GCC diagnostic warning "-Wstrict-aliasing"
85 #endif
86 
87 static QofLogModule log_module = GNC_MOD_IO;
88 
89 /* map pointers, e.g. of type FILE*, to GThreads */
90 static GHashTable* threads = NULL;
91 G_LOCK_DEFINE_STATIC (threads);
92 
93 typedef struct
94 {
95     gint fd;
96     gchar* filename;
97     gchar* perms;
98     gboolean write;
99 } gz_thread_params_t;
100 
101 /* Callback structure */
102 struct file_backend
103 {
104     gboolean        ok;
105     gpointer        data;
106     sixtp_gdv2*     gd;
107     const char*     tag;
108     sixtp*          parser;
109     FILE*           out;
110     QofBook*        book;
111 };
112 
113 static std::vector<GncXmlDataType_t> backend_registry;
114 void
gnc_xml_register_backend(GncXmlDataType_t & xmlbe)115 gnc_xml_register_backend(GncXmlDataType_t& xmlbe)
116 {
117     backend_registry.push_back(xmlbe);
118 }
119 
120 #define GNC_V2_STRING "gnc-v2"
121 /* non-static because they are used in sixtp.c */
122 const gchar* gnc_v2_xml_version_string = GNC_V2_STRING;
123 extern const gchar*
124 gnc_v2_book_version_string;        /* see gnc-book-xml-v2 */
125 
126 /* Forward declarations */
127 static FILE* try_gz_open (const char* filename, const char* perms,
128                           gboolean compress,
129                           gboolean write);
130 static gboolean is_gzipped_file (const gchar* name);
131 static gboolean wait_for_gzip (FILE* file);
132 
133 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))134 clear_up_account_commodity (
135     gnc_commodity_table* tbl, Account* act,
136     gnc_commodity * (*getter) (const Account* account),
137     void (*setter) (Account* account, gnc_commodity* comm),
138     int (*scu_getter) (const Account* account),
139     void (*scu_setter) (Account* account, int scu))
140 {
141     gnc_commodity* gcom;
142     gnc_commodity* com = getter (act);
143     int old_scu;
144 
145     if (scu_getter)
146         old_scu = scu_getter (act);
147     else
148         old_scu = 0;
149 
150     if (!com)
151     {
152         return;
153     }
154 
155     gcom = gnc_commodity_table_lookup (tbl, gnc_commodity_get_namespace (com),
156                                        gnc_commodity_get_mnemonic (com));
157 
158     if (gcom == com)
159     {
160         return;
161     }
162     else if (!gcom)
163     {
164         PWARN ("unable to find global commodity for %s adding new",
165                gnc_commodity_get_unique_name (com));
166         gnc_commodity_table_insert (tbl, com);
167     }
168     else
169     {
170         setter (act, gcom);
171         if (old_scu != 0 && scu_setter)
172             scu_setter (act, old_scu);
173         gnc_commodity_destroy (com);
174     }
175 }
176 
177 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))178 clear_up_transaction_commodity (
179     gnc_commodity_table* tbl, Transaction* trans,
180     gnc_commodity * (*getter) (const Transaction* trans),
181     void (*setter) (Transaction* trans, gnc_commodity* comm))
182 {
183     gnc_commodity* gcom;
184     gnc_commodity* com = getter (trans);
185 
186     if (!com)
187     {
188         return;
189     }
190 
191     gcom = gnc_commodity_table_lookup (tbl, gnc_commodity_get_namespace (com),
192                                        gnc_commodity_get_mnemonic (com));
193 
194     if (gcom == com)
195     {
196         return;
197     }
198     else if (!gcom)
199     {
200         PWARN ("unable to find global commodity for %s adding new",
201                gnc_commodity_get_unique_name (com));
202         gnc_commodity_table_insert (tbl, com);
203     }
204     else
205     {
206         xaccTransBeginEdit (trans);
207         setter (trans, gcom);
208         xaccTransCommitEdit (trans);
209         gnc_commodity_destroy (com);
210     }
211 }
212 
213 static gboolean
add_account_local(sixtp_gdv2 * data,Account * act)214 add_account_local (sixtp_gdv2* data, Account* act)
215 {
216     gnc_commodity_table* table;
217     Account* parent, *root;
218     int type;
219 
220     table = gnc_commodity_table_get_table (data->book);
221 
222     clear_up_account_commodity (table, act,
223                                 DxaccAccountGetCurrency,
224                                 DxaccAccountSetCurrency,
225                                 NULL, NULL);
226 
227     clear_up_account_commodity (table, act,
228                                 xaccAccountGetCommodity,
229                                 xaccAccountSetCommodity,
230                                 xaccAccountGetCommoditySCUi,
231                                 xaccAccountSetCommoditySCU);
232 
233     xaccAccountScrubCommodity (act);
234     xaccAccountScrubKvp (act);
235 
236     /* Backwards compatibility.  If there's no parent, see if this
237      * account is of type ROOT.  If not, find or create a ROOT
238      * account and make that the parent. */
239     type = xaccAccountGetType (act);
240     if (type == ACCT_TYPE_ROOT)
241     {
242         gnc_book_set_root_account (data->book, act);
243     }
244     else
245     {
246         parent = gnc_account_get_parent (act);
247         if (parent == NULL)
248         {
249             root = gnc_book_get_root_account (data->book);
250             gnc_account_append_child (root, act);
251         }
252     }
253 
254     data->counter.accounts_loaded++;
255     sixtp_run_callback (data, "account");
256 
257     return FALSE;
258 }
259 
260 static gboolean
add_book_local(sixtp_gdv2 * data,QofBook * book)261 add_book_local (sixtp_gdv2* data, QofBook* book)
262 {
263     data->counter.books_loaded++;
264     sixtp_run_callback (data, "book");
265 
266     return FALSE;
267 }
268 
269 static gboolean
add_commodity_local(sixtp_gdv2 * data,gnc_commodity * com)270 add_commodity_local (sixtp_gdv2* data, gnc_commodity* com)
271 {
272     gnc_commodity_table* table;
273 
274     table = gnc_commodity_table_get_table (data->book);
275 
276     gnc_commodity_table_insert (table, com);
277 
278     data->counter.commodities_loaded++;
279     sixtp_run_callback (data, "commodities");
280 
281     return TRUE;
282 }
283 
284 static gboolean
add_transaction_local(sixtp_gdv2 * data,Transaction * trn)285 add_transaction_local (sixtp_gdv2* data, Transaction* trn)
286 {
287     gnc_commodity_table* table;
288 
289     table = gnc_commodity_table_get_table (data->book);
290 
291     xaccTransBeginEdit (trn);
292     clear_up_transaction_commodity (table, trn,
293                                     xaccTransGetCurrency,
294                                     xaccTransSetCurrency);
295 
296     xaccTransScrubCurrency (trn);
297     xaccTransScrubPostedDate (trn);
298     xaccTransCommitEdit (trn);
299 
300     data->counter.transactions_loaded++;
301     sixtp_run_callback (data, "transaction");
302     return TRUE;
303 }
304 
305 static gboolean
add_schedXaction_local(sixtp_gdv2 * data,SchedXaction * sx)306 add_schedXaction_local (sixtp_gdv2* data, SchedXaction* sx)
307 {
308     SchedXactions* sxes;
309     sxes = gnc_book_get_schedxactions (data->book);
310     gnc_sxes_add_sx (sxes, sx);
311     data->counter.schedXactions_loaded++;
312     sixtp_run_callback (data, "schedXactions");
313     return TRUE;
314 }
315 
316 static gboolean
add_template_transaction_local(sixtp_gdv2 * data,gnc_template_xaction_data * txd)317 add_template_transaction_local (sixtp_gdv2* data,
318                                 gnc_template_xaction_data* txd)
319 {
320     GList* n;
321     Account* acctRoot = NULL;
322     QofBook* book;
323 
324     book = data->book;
325 
326     /* expect a struct of: */
327     /* . template accounts. */
328     /* . transactions in those accounts. */
329     for (n = txd->accts; n; n = n->next)
330     {
331         if (gnc_account_get_parent ((Account*)n->data) == NULL)
332         {
333             if (xaccAccountGetType ((Account*)n->data) == ACCT_TYPE_ROOT)
334             {
335                 /* replace the gnc_book_init-created root account */
336                 gnc_book_set_template_root (book, (Account*)n->data);
337             }
338             else
339             {
340                 /* This is an old data file that doesn't have a template root
341                    account and this is a top level account.  Make it a child
342                    of the template root account. */
343                 acctRoot = gnc_book_get_template_root (book);
344                 gnc_account_append_child (acctRoot, (Account*)n->data);
345             }
346         }
347 
348     }
349 
350     for (n = txd->transactions; n; n = n->next)
351     {
352         /* insert transactions into accounts */
353         add_transaction_local (data, (Transaction*)n->data);
354     }
355 
356     return TRUE;
357 }
358 
359 static gboolean
add_pricedb_local(sixtp_gdv2 * data,GNCPriceDB * db)360 add_pricedb_local (sixtp_gdv2* data, GNCPriceDB* db)
361 {
362     /* gnc_pricedb_print_contents(db, stdout); */
363     return TRUE;
364 }
365 
366 static void
counter(const GncXmlDataType_t & data,file_backend * be_data)367 counter (const GncXmlDataType_t& data, file_backend* be_data)
368 {
369     g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
370 
371     if (be_data->ok == TRUE)
372         return;
373 
374     if (!g_strcmp0 (be_data->tag, data.type_name))
375         be_data->ok = TRUE;
376 
377     /* XXX: should we do anything with this counter? */
378 }
379 
380 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)381 gnc_counter_end_handler (gpointer data_for_children,
382                          GSList* data_from_children, GSList* sibling_data,
383                          gpointer parent_data, gpointer global_data,
384                          gpointer* result, const gchar* tag)
385 {
386     char* strval;
387     gint64 val;
388     char* type;
389     xmlNodePtr tree = (xmlNodePtr)data_for_children;
390     gxpf_data* gdata = (gxpf_data*)global_data;
391     sixtp_gdv2* sixdata = (sixtp_gdv2*)gdata->parsedata;
392     gboolean ret = TRUE;
393 
394     if (parent_data)
395         return TRUE;
396 
397     /* OK.  For some messed up reason this is getting called again with a
398        NULL tag.  So we ignore those cases */
399     if (!tag)
400         return TRUE;
401 
402     g_return_val_if_fail (tree, FALSE);
403 
404     /* Note: BADXML.
405      *
406      * This is invalid xml because the namespace isn't declared in the
407      * tag itself. This should be changed to 'type' at some point. */
408     type = (char*)xmlGetProp (tree, BAD_CAST "cd:type");
409     strval = dom_tree_to_text (tree);
410     if (!string_to_gint64 (strval, &val))
411     {
412         PERR ("string_to_gint64 failed with input: %s",
413               strval ? strval : "(null)");
414         ret = FALSE;
415     }
416     else if (g_strcmp0 (type, "transaction") == 0)
417     {
418         sixdata->counter.transactions_total = val;
419     }
420     else if (g_strcmp0 (type, "account") == 0)
421     {
422         sixdata->counter.accounts_total = val;
423     }
424     else if (g_strcmp0 (type, "book") == 0)
425     {
426         sixdata->counter.books_total = val;
427     }
428     else if (g_strcmp0 (type, "commodity") == 0)
429     {
430         sixdata->counter.commodities_total = val;
431     }
432     else if (g_strcmp0 (type, "schedxaction") == 0)
433     {
434         sixdata->counter.schedXactions_total = val;
435     }
436     else if (g_strcmp0 (type, "budget") == 0)
437     {
438         sixdata->counter.budgets_total = val;
439     }
440     else if (g_strcmp0 (type, "price") == 0)
441     {
442         sixdata->counter.prices_total = val;
443     }
444     else
445     {
446         struct file_backend be_data;
447 
448         be_data.ok = FALSE;
449         be_data.tag = type;
450         for(auto data : backend_registry)
451             counter(data, &be_data);
452 
453         if (be_data.ok == FALSE)
454         {
455             PERR ("Unknown type: %s", type ? type : "(null)");
456             /* Do *NOT* flag this as an error. Gnucash 1.8 writes invalid
457              * xml by writing the 'cd:type' attribute without providing
458              * the namespace in the gnc:count-data tag.  The parser is
459              * entirely within its rights to refuse to read this bad
460              * attribute. Gnucash will function correctly without the data
461              * in this tag, so just let the error pass. */
462             ret = TRUE;
463         }
464     }
465 
466     g_free (strval);
467     xmlFree (type);
468     xmlFreeNode (tree);
469     return ret;
470 }
471 
472 static sixtp*
gnc_counter_sixtp_parser_create(void)473 gnc_counter_sixtp_parser_create (void)
474 {
475     return sixtp_dom_parser_new (gnc_counter_end_handler, NULL, NULL);
476 }
477 
478 static void
debug_print_counter_data(load_counter * data)479 debug_print_counter_data (load_counter* data)
480 {
481     DEBUG ("Transactions: Total: %d, Loaded: %d",
482            data->transactions_total, data->transactions_loaded);
483     DEBUG ("Accounts: Total: %d, Loaded: %d",
484            data->accounts_total, data->accounts_loaded);
485     DEBUG ("Books: Total: %d, Loaded: %d",
486            data->books_total, data->books_loaded);
487     DEBUG ("Commodities: Total: %d, Loaded: %d",
488            data->commodities_total, data->commodities_loaded);
489     DEBUG ("Scheduled Transactions: Total: %d, Loaded: %d",
490            data->schedXactions_total, data->schedXactions_loaded);
491     DEBUG ("Budgets: Total: %d, Loaded: %d",
492            data->budgets_total, data->budgets_loaded);
493 }
494 
495 static void
file_rw_feedback(sixtp_gdv2 * gd,const char * type)496 file_rw_feedback (sixtp_gdv2* gd, const char* type)
497 {
498     load_counter* counter;
499     int loaded, total, percentage;
500 
501     g_assert (gd != NULL);
502     if (!gd->gui_display_fn)
503         return;
504 
505     counter = &gd->counter;
506     loaded = counter->transactions_loaded + counter->accounts_loaded +
507              counter->books_loaded + counter->commodities_loaded +
508              counter->schedXactions_loaded + counter->budgets_loaded +
509              counter->prices_loaded;
510     total = counter->transactions_total + counter->accounts_total +
511             counter->books_total + counter->commodities_total +
512             counter->schedXactions_total + counter->budgets_total +
513             counter->prices_total;
514     if (total == 0)
515         total = 1;
516 
517     percentage = (loaded * 100) / total;
518     if (percentage > 100)
519     {
520         /* FIXME: Perhaps the below should be replaced by:
521         print_counter_data(counter); */
522 //      printf("Transactions: Total: %d, Loaded: %d\n",
523 //             counter->transactions_total, counter->transactions_loaded);
524 //      printf("Accounts: Total: %d, Loaded: %d\n",
525 //             counter->accounts_total, counter->accounts_loaded);
526 //      printf("Books: Total: %d, Loaded: %d\n",
527 //             counter->books_total, counter->books_loaded);
528 //      printf("Commodities: Total: %d, Loaded: %d\n",
529 //             counter->commodities_total, counter->commodities_loaded);
530 //      printf("Scheduled Transactions: Total: %d, Loaded: %d\n",
531 //             counter->schedXactions_total, counter->schedXactions_loaded);
532 //      printf("Budgets: Total: %d, Loaded: %d\n",
533 //       counter->budgets_total, counter->budgets_loaded);
534     }
535     gd->gui_display_fn (NULL, percentage);
536 }
537 
538 static const char* BOOK_TAG = "gnc:book";
539 static const char* BOOK_ID_TAG = "book:id";
540 static const char* BOOK_SLOTS_TAG = "book:slots";
541 static const char* ACCOUNT_TAG = "gnc:account";
542 static const char* PRICEDB_TAG = "gnc:pricedb";
543 static const char* COMMODITY_TAG = "gnc:commodity";
544 static const char* COUNT_DATA_TAG = "gnc:count-data";
545 static const char* TRANSACTION_TAG = "gnc:transaction";
546 static const char* SCHEDXACTION_TAG = "gnc:schedxaction";
547 static const char* TEMPLATE_TRANSACTION_TAG = "gnc:template-transactions";
548 static const char* BUDGET_TAG = "gnc:budget";
549 
550 static void
add_item(const GncXmlDataType_t & data,struct file_backend * be_data)551 add_item (const GncXmlDataType_t& data, struct file_backend* be_data)
552 {
553     g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
554 
555     if (be_data->ok)
556         return;
557 
558     if (!g_strcmp0 (be_data->tag, data.type_name))
559     {
560         if (data.add_item)
561             (data.add_item)(be_data->gd, be_data->data);
562 
563         be_data->ok = TRUE;
564     }
565 }
566 
567 static gboolean
book_callback(const char * tag,gpointer globaldata,gpointer data)568 book_callback (const char* tag, gpointer globaldata, gpointer data)
569 {
570     sixtp_gdv2* gd = (sixtp_gdv2*)globaldata;
571 
572     if (g_strcmp0 (tag, ACCOUNT_TAG) == 0)
573     {
574         add_account_local (gd, (Account*)data);
575     }
576     else if (g_strcmp0 (tag, PRICEDB_TAG) == 0)
577     {
578         add_pricedb_local (gd, (GNCPriceDB*)data);
579     }
580     else if (g_strcmp0 (tag, COMMODITY_TAG) == 0)
581     {
582         add_commodity_local (gd, (gnc_commodity*)data);
583     }
584     else if (g_strcmp0 (tag, TRANSACTION_TAG) == 0)
585     {
586         add_transaction_local (gd, (Transaction*)data);
587     }
588     else if (g_strcmp0 (tag, SCHEDXACTION_TAG) == 0)
589     {
590         add_schedXaction_local (gd, (SchedXaction*)data);
591     }
592     else if (g_strcmp0 (tag, TEMPLATE_TRANSACTION_TAG) == 0)
593     {
594         add_template_transaction_local (gd, (gnc_template_xaction_data*)data);
595     }
596     else if (g_strcmp0 (tag, BUDGET_TAG) == 0)
597     {
598         // Nothing needed here.
599     }
600     else
601     {
602         struct file_backend be_data;
603 
604         be_data.ok = FALSE;
605         be_data.tag = tag;
606         be_data.gd = gd;
607         be_data.data = data;
608 
609         for (auto data : backend_registry)
610             add_item(data, &be_data);
611 
612         if (be_data.ok == FALSE)
613         {
614             PWARN ("unexpected tag %s", tag);
615         }
616     }
617     return TRUE;
618 }
619 
620 static gboolean
generic_callback(const char * tag,gpointer globaldata,gpointer data)621 generic_callback (const char* tag, gpointer globaldata, gpointer data)
622 {
623     sixtp_gdv2* gd = (sixtp_gdv2*)globaldata;
624 
625     if (g_strcmp0 (tag, BOOK_TAG) == 0)
626     {
627         add_book_local (gd, (QofBook*)data);
628         book_callback (tag, globaldata, data);
629     }
630     else
631     {
632         // PWARN ("importing pre-book-style XML data file");
633         book_callback (tag, globaldata, data);
634     }
635     return TRUE;
636 }
637 
638 static void
add_parser(const GncXmlDataType_t & data,struct file_backend * be_data)639 add_parser(const GncXmlDataType_t& data, struct file_backend* be_data)
640 {
641     g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
642 
643     if (be_data->ok == FALSE)
644         return;
645 
646     if (data.create_parser)
647         if (!sixtp_add_some_sub_parsers(
648                     be_data->parser, TRUE,
649                     data.type_name, (data.create_parser)(),
650                     NULL, NULL))
651             be_data->ok = FALSE;
652 }
653 
654 static void
scrub(const GncXmlDataType_t & data,struct file_backend * be_data)655 scrub (const GncXmlDataType_t& data, struct file_backend* be_data)
656 {
657     g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
658 
659     if (data.scrub)
660         (data.scrub)(be_data->book);
661 }
662 
663 static sixtp_gdv2*
gnc_sixtp_gdv2_new(QofBook * book,gboolean exporting,countCallbackFn countcallback,QofBePercentageFunc gui_display_fn)664 gnc_sixtp_gdv2_new (
665     QofBook* book,
666     gboolean exporting,
667     countCallbackFn countcallback,
668     QofBePercentageFunc gui_display_fn)
669 {
670     sixtp_gdv2* gd = g_new0 (sixtp_gdv2, 1);
671 
672     if (gd == NULL) return NULL;
673 
674     gd->book = book;
675     gd->counter.accounts_loaded = 0;
676     gd->counter.accounts_total = 0;
677     gd->counter.books_loaded = 0;
678     gd->counter.books_total = 0;
679     gd->counter.commodities_loaded = 0;
680     gd->counter.commodities_total = 0;
681     gd->counter.transactions_loaded = 0;
682     gd->counter.transactions_total = 0;
683     gd->counter.prices_loaded = 0;
684     gd->counter.prices_total = 0;
685     gd->counter.schedXactions_loaded = 0;
686     gd->counter.schedXactions_total = 0;
687     gd->counter.budgets_loaded = 0;
688     gd->counter.budgets_total = 0;
689     gd->exporting = exporting;
690     gd->countCallback = countcallback;
691     gd->gui_display_fn = gui_display_fn;
692     return gd;
693 }
694 
695 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)696 qof_session_load_from_xml_file_v2_full (
697     GncXmlBackend* xml_be, QofBook* book,
698     sixtp_push_handler push_handler, gpointer push_user_data,
699     QofBookFileType type)
700 {
701     Account* root;
702     Account* template_root;
703     sixtp_gdv2* gd;
704     sixtp* top_parser;
705     sixtp* main_parser;
706     sixtp* book_parser;
707     struct file_backend be_data;
708     gboolean retval;
709     char* v2type = NULL;
710 
711     gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
712                              xml_be->get_percentage());
713 
714     top_parser = sixtp_new ();
715     main_parser = sixtp_new ();
716     book_parser = sixtp_new ();
717 
718     if (type == GNC_BOOK_XML2_FILE)
719         v2type = g_strdup (GNC_V2_STRING);
720 
721     if (!sixtp_add_some_sub_parsers (
722             top_parser, TRUE,
723             v2type, main_parser,
724             NULL, NULL))
725     {
726         g_free (v2type);
727         goto bail;
728     }
729 
730     g_free (v2type);
731 
732     if (!sixtp_add_some_sub_parsers (
733             main_parser, TRUE,
734             COUNT_DATA_TAG, gnc_counter_sixtp_parser_create (),
735             BOOK_TAG, book_parser,
736 
737             /* the following are present here only to support
738              * the older, pre-book format.  Basically, the top-level
739              * book is implicit. */
740             PRICEDB_TAG, gnc_pricedb_sixtp_parser_create (),
741             COMMODITY_TAG, gnc_commodity_sixtp_parser_create (),
742             ACCOUNT_TAG, gnc_account_sixtp_parser_create (),
743             TRANSACTION_TAG, gnc_transaction_sixtp_parser_create (),
744             SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create (),
745             TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create (),
746             NULL, NULL))
747     {
748         goto bail;
749     }
750 
751     if (!sixtp_add_some_sub_parsers (
752             book_parser, TRUE,
753             BOOK_ID_TAG, gnc_book_id_sixtp_parser_create (),
754             BOOK_SLOTS_TAG, gnc_book_slots_sixtp_parser_create (),
755             COUNT_DATA_TAG, gnc_counter_sixtp_parser_create (),
756             PRICEDB_TAG, gnc_pricedb_sixtp_parser_create (),
757             COMMODITY_TAG, gnc_commodity_sixtp_parser_create (),
758             ACCOUNT_TAG, gnc_account_sixtp_parser_create (),
759             BUDGET_TAG, gnc_budget_sixtp_parser_create (),
760             TRANSACTION_TAG, gnc_transaction_sixtp_parser_create (),
761             SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create (),
762             TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create (),
763             NULL, NULL))
764     {
765         goto bail;
766     }
767 
768     be_data.ok = TRUE;
769     be_data.parser = book_parser;
770     for (auto data : backend_registry)
771         add_parser(data, &be_data);
772     if (be_data.ok == FALSE)
773         goto bail;
774 
775     /* stop logging while we load */
776     xaccLogDisable ();
777     xaccDisableDataScrubbing ();
778 
779     if (push_handler)
780     {
781         gpointer parse_result = NULL;
782         gxpf_data gpdata;
783 
784         gpdata.cb = generic_callback;
785         gpdata.parsedata = gd;
786         gpdata.bookdata = book;
787 
788         retval = sixtp_parse_push (top_parser, push_handler, push_user_data,
789                                    NULL, &gpdata, &parse_result);
790     }
791     else
792     {
793         /* Even though libxml2 knows how to decompress zipped files, we
794          * do it ourself since as of version 2.9.1 it has a bug that
795          * causes it to fail to decompress certain files. See
796          * https://bugs.gnucash.org/show_bug.cgi?id=712528 for more
797          * info.
798          */
799          const char* filename = xml_be->get_filename();
800         FILE* file;
801         gboolean is_compressed = is_gzipped_file (filename);
802         file = try_gz_open (filename, "r", is_compressed, FALSE);
803         if (file == NULL)
804         {
805             PWARN ("Unable to open file %s", filename);
806             retval = FALSE;
807         }
808         else
809         {
810             retval = gnc_xml_parse_fd (top_parser, file,
811                                        generic_callback, gd, book);
812             fclose (file);
813             if (is_compressed)
814                 wait_for_gzip (file);
815         }
816     }
817 
818     if (!retval)
819     {
820         sixtp_destroy (top_parser);
821         xaccLogEnable ();
822         xaccEnableDataScrubbing ();
823         goto bail;
824     }
825     debug_print_counter_data (&gd->counter);
826 
827     /* destroy the parser */
828     sixtp_destroy (top_parser);
829     g_free (gd);
830 
831     xaccEnableDataScrubbing ();
832 
833     /* Mark the session as saved */
834     qof_book_mark_session_saved (book);
835 
836     /* Call individual scrub functions */
837     memset (&be_data, 0, sizeof (be_data));
838     be_data.book = book;
839     for (auto data : backend_registry)
840         scrub(data, &be_data);
841 
842     /* fix price quote sources */
843     root = gnc_book_get_root_account (book);
844     xaccAccountTreeScrubQuoteSources (root, gnc_commodity_table_get_table (book));
845 
846     /* Fix account and transaction commodities */
847     xaccAccountTreeScrubCommodities (root);
848 
849     /* Fix split amount/value */
850     xaccAccountTreeScrubSplits (root);
851 
852     /* commit all groups, this completes the BeginEdit started when the
853      * account_end_handler finished reading the account.
854      */
855     template_root = gnc_book_get_template_root (book);
856     gnc_account_foreach_descendant (root,
857                                     (AccountCb) xaccAccountCommitEdit,
858                                     NULL);
859     gnc_account_foreach_descendant (template_root,
860                                     (AccountCb) xaccAccountCommitEdit,
861                                     NULL);
862     /* if these exist in the XML file then they will be uncommitted */
863     if (qof_instance_get_editlevel(root) != 0)
864         xaccAccountCommitEdit(root);
865     if (qof_instance_get_editlevel(template_root) != 0)
866         xaccAccountCommitEdit(template_root);
867 
868     /* start logging again */
869     xaccLogEnable ();
870 
871     return TRUE;
872 
873 bail:
874     g_free (gd);
875     return FALSE;
876 }
877 
878 gboolean
qof_session_load_from_xml_file_v2(GncXmlBackend * xml_be,QofBook * book,QofBookFileType type)879 qof_session_load_from_xml_file_v2 (GncXmlBackend* xml_be, QofBook* book,
880                                    QofBookFileType type)
881 {
882     return qof_session_load_from_xml_file_v2_full (xml_be, book, NULL, NULL, type);
883 }
884 
885 /***********************************************************************/
886 
887 static gboolean
write_counts(FILE * out,...)888 write_counts (FILE* out, ...)
889 {
890     va_list ap;
891     char* type;
892     gboolean success = TRUE;
893 
894     va_start (ap, out);
895     type = g_strdup (va_arg (ap, char*));
896 
897     while (success && type)
898     {
899         int amount = va_arg (ap, int);
900 
901         if (amount != 0)
902         {
903 #if GNUCASH_REALLY_BUILD_AN_XML_TREE_ON_OUTPUT
904             char* val;
905             xmlNodePtr node;
906 
907             val = g_strdup_printf ("%d", amount);
908 
909             node = xmlNewNode (NULL, BAD_CAST COUNT_DATA_TAG);
910             /* Note: BADXML.
911              *
912              * This is invalid xml because the namespace isn't
913              * declared in the tag itself. This should be changed to
914              * 'type' at some point. */
915             xmlSetProp (node, BAD_CAST "cd:type", checked_char_cast (type));
916             xmlNodeAddContent (node, checked_char_cast (val));
917             g_free (val);
918             g_free (type);
919 
920             xmlElemDump (out, NULL, node);
921             xmlFreeNode (node);
922 
923             if (ferror (out) || fprintf (out, "\n") < 0)
924             {
925                 success = FALSE;
926                 break;
927             }
928 #else
929             if (fprintf (out, "<%s %s=\"%s\">%d</%s>\n",
930                          COUNT_DATA_TAG, "cd:type", type, amount, COUNT_DATA_TAG) < 0)
931             {
932                 success = FALSE;
933                 break;
934             }
935 #endif
936 
937         }
938 
939         type = va_arg (ap, char*);
940     }
941 
942     va_end (ap);
943     return success;
944 }
945 
946 static gint
compare_namespaces(gconstpointer a,gconstpointer b)947 compare_namespaces (gconstpointer a, gconstpointer b)
948 {
949     const gchar* sa = (const gchar*) a;
950     const gchar* sb = (const gchar*) b;
951     return (g_strcmp0 (sa, sb));
952 }
953 
954 static gint
compare_commodity_ids(gconstpointer a,gconstpointer b)955 compare_commodity_ids (gconstpointer a, gconstpointer b)
956 {
957     const gnc_commodity* ca = (const gnc_commodity*) a;
958     const gnc_commodity* cb = (const gnc_commodity*) b;
959     return (g_strcmp0 (gnc_commodity_get_mnemonic (ca),
960                        gnc_commodity_get_mnemonic (cb)));
961 }
962 
963 static gboolean write_pricedb (FILE* out, QofBook* book, sixtp_gdv2* gd);
964 static gboolean write_transactions (FILE* out, QofBook* book, sixtp_gdv2* gd);
965 static gboolean write_template_transaction_data (FILE* out, QofBook* book,
966                                                  sixtp_gdv2* gd);
967 static gboolean write_schedXactions (FILE* out, QofBook* book, sixtp_gdv2* gd);
968 static void write_budget (QofInstance* ent, gpointer data);
969 
970 static void
write_counts(const GncXmlDataType_t & data,struct file_backend * be_data)971 write_counts(const GncXmlDataType_t& data, struct file_backend* be_data)
972 {
973     g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
974 
975     if (data.get_count)
976         write_counts (be_data->out, data.type_name,
977                       (data.get_count) (be_data->book),
978                       NULL);
979 }
980 
981 static void
write_data(const GncXmlDataType_t & data,struct file_backend * be_data)982 write_data(const GncXmlDataType_t& data, struct file_backend* be_data)
983 {
984     g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
985 
986     if (data.write && !ferror(be_data->out))
987         (data.write)(be_data->out, be_data->book);
988 }
989 
990 static gboolean
write_book(FILE * out,QofBook * book,sixtp_gdv2 * gd)991 write_book (FILE* out, QofBook* book, sixtp_gdv2* gd)
992 {
993     struct file_backend be_data;
994 
995     be_data.out = out;
996     be_data.book = book;
997     be_data.gd = gd;
998     if (fprintf (out, "<%s version=\"%s\">\n", BOOK_TAG,
999                  gnc_v2_book_version_string) < 0)
1000         return FALSE;
1001     if (!write_book_parts (out, book))
1002         return FALSE;
1003 
1004     /* gd->counter.{foo}_total fields should have all these totals
1005        already collected.  I don't know why we're re-calling all these
1006        functions.  */
1007     if (!write_counts (out,
1008                        "commodity",
1009                        gnc_commodity_table_get_size (
1010                            gnc_commodity_table_get_table (book)),
1011                        "account",
1012                        1 + gnc_account_n_descendants (gnc_book_get_root_account (book)),
1013                        "transaction",
1014                        gnc_book_count_transactions (book),
1015                        "schedxaction",
1016                        g_list_length (gnc_book_get_schedxactions (book)->sx_list),
1017                        "budget", qof_collection_count (
1018                            qof_book_get_collection (book, GNC_ID_BUDGET)),
1019                        "price", gnc_pricedb_get_num_prices (gnc_pricedb_get_db (book)),
1020                        NULL))
1021         return FALSE;
1022 
1023     for (auto data : backend_registry)
1024         write_counts(data, &be_data);
1025 
1026     if (ferror (out)
1027         || !write_commodities (out, book, gd)
1028         || !write_pricedb (out, book, gd)
1029         || !write_accounts (out, book, gd)
1030         || !write_transactions (out, book, gd)
1031         || !write_template_transaction_data (out, book, gd)
1032         || !write_schedXactions (out, book, gd))
1033 
1034         return FALSE;
1035 
1036     qof_collection_foreach (qof_book_get_collection (book, GNC_ID_BUDGET),
1037                             write_budget, &be_data);
1038     if (ferror (out))
1039         return FALSE;
1040 
1041     for (auto data : backend_registry)
1042         write_data(data, &be_data);
1043     if (ferror(out))
1044         return FALSE;
1045 
1046     if (fprintf (out, "</%s>\n", BOOK_TAG) < 0)
1047         return FALSE;
1048 
1049     return TRUE;
1050 }
1051 
1052 gboolean
write_commodities(FILE * out,QofBook * book,sixtp_gdv2 * gd)1053 write_commodities (FILE* out, QofBook* book, sixtp_gdv2* gd)
1054 {
1055     gnc_commodity_table* tbl;
1056     GList* namespaces;
1057     GList* lp;
1058     gboolean success = TRUE;
1059 
1060     tbl = gnc_commodity_table_get_table (book);
1061 
1062     namespaces = gnc_commodity_table_get_namespaces (tbl);
1063     if (namespaces)
1064     {
1065         namespaces = g_list_sort (namespaces, compare_namespaces);
1066     }
1067 
1068     for (lp = namespaces; success && lp; lp = lp->next)
1069     {
1070         GList* comms, *lp2;
1071         xmlNodePtr comnode;
1072 
1073         comms = gnc_commodity_table_get_commodities (tbl,
1074                                                      static_cast<const char*> (lp->data));
1075         comms = g_list_sort (comms, compare_commodity_ids);
1076 
1077         for (lp2 = comms; lp2; lp2 = lp2->next)
1078         {
1079             comnode = gnc_commodity_dom_tree_create (static_cast<const gnc_commodity*>
1080                                                      (lp2->data));
1081             if (comnode == NULL)
1082                 continue;
1083 
1084             xmlElemDump (out, NULL, comnode);
1085             if (ferror (out) || fprintf (out, "\n") < 0)
1086             {
1087                 success = FALSE;
1088                 break;
1089             }
1090 
1091             xmlFreeNode (comnode);
1092             gd->counter.commodities_loaded++;
1093             sixtp_run_callback (gd, "commodities");
1094         }
1095 
1096         g_list_free (comms);
1097     }
1098 
1099     if (namespaces) g_list_free (namespaces);
1100 
1101     return success;
1102 }
1103 
1104 static gboolean
write_pricedb(FILE * out,QofBook * book,sixtp_gdv2 * gd)1105 write_pricedb (FILE* out, QofBook* book, sixtp_gdv2* gd)
1106 {
1107     xmlNodePtr node;
1108     xmlNodePtr parent;
1109     xmlOutputBufferPtr outbuf;
1110 
1111     parent = gnc_pricedb_dom_tree_create (gnc_pricedb_get_db (book));
1112 
1113     if (!parent)
1114     {
1115         return TRUE;
1116     }
1117 
1118     /* Write out the parent pricedb tag then loop to write out each price.
1119        We do it this way instead of just calling xmlElemDump so that we can
1120        increment the progress bar as we go. */
1121 
1122     if (fprintf (out, "<%s version=\"%s\">\n", parent->name,
1123                  xmlGetProp (parent, BAD_CAST "version")) < 0)
1124         return FALSE;
1125 
1126     /* We create our own output buffer so we can call xmlNodeDumpOutput to get
1127        the indentation correct. */
1128     outbuf = xmlOutputBufferCreateFile (out, NULL);
1129     if (outbuf == NULL)
1130     {
1131         xmlFreeNode (parent);
1132         return FALSE;
1133     }
1134 
1135     for (node = parent->children; node; node = node->next)
1136     {
1137         /* Write two spaces since xmlNodeDumpOutput doesn't indent the first line */
1138         xmlOutputBufferWrite (outbuf, 2, "  ");
1139         xmlNodeDumpOutput (outbuf, NULL, node, 1, 1, NULL);
1140         /* It also doesn't terminate the last line */
1141         xmlOutputBufferWrite (outbuf, 1, "\n");
1142         if (ferror (out))
1143             break;
1144         gd->counter.prices_loaded += 1;
1145         sixtp_run_callback (gd, "prices");
1146     }
1147 
1148     xmlOutputBufferClose (outbuf);
1149 
1150     if (ferror (out) || fprintf (out, "</%s>\n", parent->name) < 0)
1151     {
1152         xmlFreeNode (parent);
1153         return FALSE;
1154     }
1155 
1156     xmlFreeNode (parent);
1157     return TRUE;
1158 }
1159 
1160 static int
xml_add_trn_data(Transaction * t,gpointer data)1161 xml_add_trn_data (Transaction* t, gpointer data)
1162 {
1163     struct file_backend* be_data = static_cast<decltype (be_data)> (data);
1164     xmlNodePtr node;
1165 
1166     node = gnc_transaction_dom_tree_create (t);
1167 
1168     xmlElemDump (be_data->out, NULL, node);
1169     xmlFreeNode (node);
1170 
1171     if (ferror (be_data->out) || fprintf (be_data->out, "\n") < 0)
1172         return -1;
1173 
1174     be_data->gd->counter.transactions_loaded++;
1175     sixtp_run_callback (be_data->gd, "transaction");
1176     return 0;
1177 }
1178 
1179 static gboolean
write_transactions(FILE * out,QofBook * book,sixtp_gdv2 * gd)1180 write_transactions (FILE* out, QofBook* book, sixtp_gdv2* gd)
1181 {
1182     struct file_backend be_data;
1183 
1184     be_data.out = out;
1185     be_data.gd = gd;
1186     return 0 ==
1187            xaccAccountTreeForEachTransaction (gnc_book_get_root_account (book),
1188                                               xml_add_trn_data,
1189                                               (gpointer) &be_data);
1190 }
1191 
1192 static gboolean
write_template_transaction_data(FILE * out,QofBook * book,sixtp_gdv2 * gd)1193 write_template_transaction_data (FILE* out, QofBook* book, sixtp_gdv2* gd)
1194 {
1195     Account* ra;
1196     struct file_backend be_data;
1197 
1198     be_data.out = out;
1199     be_data.gd = gd;
1200 
1201     ra = gnc_book_get_template_root (book);
1202     if (gnc_account_n_descendants (ra) > 0)
1203     {
1204         if (fprintf (out, "<%s>\n", TEMPLATE_TRANSACTION_TAG) < 0
1205             || !write_account_tree (out, ra, gd)
1206             || xaccAccountTreeForEachTransaction (ra, xml_add_trn_data, (gpointer)&be_data)
1207             || fprintf (out, "</%s>\n", TEMPLATE_TRANSACTION_TAG) < 0)
1208 
1209             return FALSE;
1210     }
1211 
1212     return TRUE;
1213 }
1214 
1215 static gboolean
write_schedXactions(FILE * out,QofBook * book,sixtp_gdv2 * gd)1216 write_schedXactions (FILE* out, QofBook* book, sixtp_gdv2* gd)
1217 {
1218     GList* schedXactions;
1219     SchedXaction* tmpSX;
1220     xmlNodePtr node;
1221 
1222     schedXactions = gnc_book_get_schedxactions (book)->sx_list;
1223 
1224     if (schedXactions == NULL)
1225         return TRUE;
1226 
1227     do
1228     {
1229         tmpSX = static_cast<decltype (tmpSX)> (schedXactions->data);
1230         node = gnc_schedXaction_dom_tree_create (tmpSX);
1231         xmlElemDump (out, NULL, node);
1232         xmlFreeNode (node);
1233         if (ferror (out) || fprintf (out, "\n") < 0)
1234             return FALSE;
1235         gd->counter.schedXactions_loaded++;
1236         sixtp_run_callback (gd, "schedXactions");
1237     }
1238     while ((schedXactions = schedXactions->next));
1239 
1240     return TRUE;
1241 }
1242 
1243 static void
write_budget(QofInstance * ent,gpointer data)1244 write_budget (QofInstance* ent, gpointer data)
1245 {
1246     xmlNodePtr node;
1247     struct file_backend* file_be = static_cast<decltype (file_be)> (data);
1248 
1249     GncBudget* bgt = GNC_BUDGET (ent);
1250 
1251     if (ferror (file_be->out))
1252         return;
1253 
1254     node = gnc_budget_dom_tree_create (bgt);
1255     xmlElemDump (file_be->out, NULL, node);
1256     xmlFreeNode (node);
1257     if (ferror (file_be->out) || fprintf (file_be->out, "\n") < 0)
1258         return;
1259 
1260     file_be->gd->counter.budgets_loaded++;
1261     sixtp_run_callback (file_be->gd, "budgets");
1262 }
1263 
1264 gboolean
gnc_xml2_write_namespace_decl(FILE * out,const char * name_space)1265 gnc_xml2_write_namespace_decl (FILE* out, const char* name_space)
1266 {
1267     g_return_val_if_fail (name_space, FALSE);
1268     return fprintf (out, "\n     xmlns:%s=\"http://www.gnucash.org/XML/%s\"",
1269                     name_space, name_space) >= 0;
1270 }
1271 
1272 static void
write_namespace(const GncXmlDataType_t & data,FILE * out)1273 write_namespace (const GncXmlDataType_t& data, FILE* out)
1274 {
1275     g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
1276 
1277     if (data.ns && !ferror(out))
1278         (data.ns)(out);
1279 }
1280 
1281 static gboolean
write_v2_header(FILE * out)1282 write_v2_header (FILE* out)
1283 {
1284     if (fprintf (out, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n") < 0
1285         || fprintf (out, "<" GNC_V2_STRING) < 0
1286 
1287         || !gnc_xml2_write_namespace_decl (out, "gnc")
1288         || !gnc_xml2_write_namespace_decl (out, "act")
1289         || !gnc_xml2_write_namespace_decl (out, "book")
1290         || !gnc_xml2_write_namespace_decl (out, "cd")
1291         || !gnc_xml2_write_namespace_decl (out, "cmdty")
1292         || !gnc_xml2_write_namespace_decl (out, "price")
1293         || !gnc_xml2_write_namespace_decl (out, "slot")
1294         || !gnc_xml2_write_namespace_decl (out, "split")
1295         || !gnc_xml2_write_namespace_decl (out, "sx")
1296         || !gnc_xml2_write_namespace_decl (out, "trn")
1297         || !gnc_xml2_write_namespace_decl (out, "ts")
1298         || !gnc_xml2_write_namespace_decl (out, "fs")
1299         || !gnc_xml2_write_namespace_decl (out, "bgt")
1300         || !gnc_xml2_write_namespace_decl (out, "recurrence")
1301         || !gnc_xml2_write_namespace_decl (out, "lot"))
1302 
1303         return FALSE;
1304 
1305     /* now cope with the plugins */
1306     for (auto data : backend_registry)
1307         write_namespace(data, out);
1308 
1309     if (ferror (out) || fprintf (out, ">\n") < 0)
1310         return FALSE;
1311 
1312     return TRUE;
1313 }
1314 
1315 gboolean
gnc_book_write_to_xml_filehandle_v2(QofBook * book,FILE * out)1316 gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
1317 {
1318     QofBackend* qof_be;
1319     sixtp_gdv2* gd;
1320     gboolean success = TRUE;
1321 
1322     if (!out) return FALSE;
1323 
1324     if (!write_v2_header (out)
1325         || !write_counts (out, "book", 1, NULL))
1326         return FALSE;
1327 
1328     qof_be = qof_book_get_backend (book);
1329     gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
1330                              qof_be->get_percentage());
1331     gd->counter.commodities_total =
1332         gnc_commodity_table_get_size (gnc_commodity_table_get_table (book));
1333     gd->counter.accounts_total = 1 +
1334                                  gnc_account_n_descendants (gnc_book_get_root_account (book));
1335     gd->counter.transactions_total = gnc_book_count_transactions (book);
1336     gd->counter.schedXactions_total =
1337         g_list_length (gnc_book_get_schedxactions (book)->sx_list);
1338     gd->counter.budgets_total = qof_collection_count (
1339                                     qof_book_get_collection (book, GNC_ID_BUDGET));
1340     gd->counter.prices_total = gnc_pricedb_get_num_prices (gnc_pricedb_get_db (
1341                                                                book));
1342 
1343     if (!write_book (out, book, gd)
1344         || fprintf (out, "</" GNC_V2_STRING ">\n\n") < 0)
1345         success = FALSE;
1346 
1347     g_free (gd);
1348     return success;
1349 }
1350 
1351 /*
1352  * This function is called by the "export" code.
1353  */
1354 gboolean
gnc_book_write_accounts_to_xml_filehandle_v2(QofBackend * qof_be,QofBook * book,FILE * out)1355 gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* qof_be, QofBook* book,
1356                                               FILE* out)
1357 {
1358     gnc_commodity_table* table;
1359     Account* root;
1360     int ncom, nacc;
1361     sixtp_gdv2* gd;
1362     gboolean success = TRUE;
1363 
1364     if (!out) return FALSE;
1365 
1366     root = gnc_book_get_root_account (book);
1367     nacc = 1 + gnc_account_n_descendants (root);
1368 
1369     table = gnc_commodity_table_get_table (book);
1370     ncom = gnc_commodity_table_get_size (table);
1371 
1372     if (!write_v2_header (out)
1373         || !write_counts (out, "commodity", ncom, "account", nacc, NULL))
1374         return FALSE;
1375 
1376     gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback,
1377                              qof_be->get_percentage());
1378     gd->counter.commodities_total = ncom;
1379     gd->counter.accounts_total = nacc;
1380 
1381     if (!write_commodities (out, book, gd)
1382         || !write_accounts (out, book, gd)
1383         || fprintf (out, "</" GNC_V2_STRING ">\n\n") < 0)
1384         success = FALSE;
1385 
1386     g_free (gd);
1387     return success;
1388 }
1389 
1390 #define BUFLEN 4096
1391 
1392 /* Compress or decompress function that is to be run in a separate thread.
1393  * Returns 1 on success or 0 otherwise, stuffed into a pointer type. */
1394 static gpointer
gz_thread_func(gz_thread_params_t * params)1395 gz_thread_func (gz_thread_params_t* params)
1396 {
1397     gchar buffer[BUFLEN];
1398     gssize bytes;
1399     gint gzval;
1400     gzFile file;
1401     gint success = 1;
1402 
1403 #ifdef G_OS_WIN32
1404     {
1405         gchar* conv_name = g_win32_locale_filename_from_utf8 (params->filename);
1406         gchar* perms;
1407 
1408         if (!conv_name)
1409         {
1410             g_warning ("Could not convert '%s' to system codepage",
1411                        params->filename);
1412             success = 0;
1413             goto cleanup_gz_thread_func;
1414         }
1415 
1416         if (strchr (params->perms, 'b'))
1417             perms = g_strdup (params->perms);
1418         else
1419             perms = g_strdup_printf ("%cb%s", *params->perms, params->perms + 1);
1420 
1421         file = gzopen (conv_name, perms);
1422         g_free (perms);
1423         g_free (conv_name);
1424     }
1425 #else /* !G_OS_WIN32 */
1426     file = gzopen (params->filename, params->perms);
1427 #endif /* G_OS_WIN32 */
1428 
1429     if (file == NULL)
1430     {
1431         g_warning ("Child threads gzopen failed");
1432         success = 0;
1433         goto cleanup_gz_thread_func;
1434     }
1435 
1436     if (params->write)
1437     {
1438         while (success)
1439         {
1440             bytes = read (params->fd, buffer, BUFLEN);
1441             if (bytes > 0)
1442             {
1443                 if (gzwrite (file, buffer, bytes) <= 0)
1444                 {
1445                     gint errnum;
1446                     const gchar* error = gzerror (file, &errnum);
1447                     g_warning ("Could not write the compressed file '%s'. The error is: '%s' (%d)",
1448                                params->filename, error, errnum);
1449                     success = 0;
1450                 }
1451             }
1452             else if (bytes == 0)
1453             {
1454                 printf("gz_thread_func EOF\n");
1455                 break;
1456             }
1457             else
1458             {
1459                 g_warning ("Could not read from pipe. The error is '%s' (errno %d)",
1460                            g_strerror (errno) ? g_strerror (errno) : "", errno);
1461                 success = 0;
1462             }
1463         }
1464     }
1465     else
1466     {
1467         while (success)
1468         {
1469             gzval = gzread (file, buffer, BUFLEN);
1470             if (gzval > 0)
1471             {
1472                 if (
1473 #if COMPILER(MSVC)
1474                     _write
1475 #else
1476                     write
1477 #endif
1478                     (params->fd, buffer, gzval) < 0)
1479                 {
1480                     g_warning ("Could not write to pipe. The error is '%s' (%d)",
1481                                g_strerror (errno) ? g_strerror (errno) : "", errno);
1482                     success = 0;
1483                 }
1484             }
1485             else if (gzval == 0)
1486             {
1487                 break;
1488             }
1489             else
1490             {
1491                 gint errnum;
1492                 const gchar* error = gzerror (file, &errnum);
1493                 g_warning ("Could not read from compressed file '%s'. The error is: '%s' (%d)",
1494                            params->filename, error, errnum);
1495                 success = 0;
1496             }
1497         }
1498     }
1499 
1500     if ((gzval = gzclose (file)) != Z_OK)
1501     {
1502         g_warning ("Could not close the compressed file '%s' (errnum %d)",
1503                    params->filename, gzval);
1504         success = 0;
1505     }
1506 
1507 cleanup_gz_thread_func:
1508     close (params->fd);
1509     g_free (params->filename);
1510     g_free (params->perms);
1511     g_free (params);
1512 
1513     return GINT_TO_POINTER (success);
1514 }
1515 
1516 static FILE*
try_gz_open(const char * filename,const char * perms,gboolean compress,gboolean write)1517 try_gz_open (const char* filename, const char* perms, gboolean compress,
1518              gboolean write)
1519 {
1520     if (strstr (filename, ".gz.") != NULL) /* its got a temp extension */
1521         compress = TRUE;
1522 
1523     if (!compress)
1524         return g_fopen (filename, perms);
1525 
1526     {
1527         int filedes[2]{};
1528         GThread* thread;
1529         gz_thread_params_t* params;
1530         FILE* file;
1531 
1532 #ifdef G_OS_WIN32
1533         if (_pipe (filedes, 4096, _O_BINARY) < 0)
1534         {
1535 #else
1536         /* Set CLOEXEC on the pipe FDs so that if the user runs a
1537          * report while saving WebKit's fork won't get an open copy
1538          * and keep the pipe from closing. See
1539          * https://bugs.gnucash.org/show_bug.cgi?id=798250. Win32
1540          * doesn't fork nor does it support CLOEXEC.
1541          */
1542         if (pipe (filedes) < 0 ||
1543             fcntl(filedes[0], F_SETFD, FD_CLOEXEC) == -1 ||
1544 	    fcntl(filedes[1], F_SETFD, FD_CLOEXEC) == -1)
1545         {
1546 #endif
1547             g_warning ("Pipe setup failed with errno %d. Opening uncompressed file.", errno);
1548             if (filedes[0])
1549             {
1550                 close(filedes[0]);
1551                 close(filedes[1]);
1552             }
1553             return g_fopen (filename, perms);
1554         }
1555 
1556         params = g_new (gz_thread_params_t, 1);
1557         params->fd = filedes[write ? 0 : 1];
1558         params->filename = g_strdup (filename);
1559         params->perms = g_strdup (perms);
1560         params->write = write;
1561 
1562         thread = g_thread_new ("xml_thread", (GThreadFunc) gz_thread_func,
1563                                params);
1564         if (!thread)
1565         {
1566             g_warning ("Could not create thread for (de)compression.");
1567             g_free (params->filename);
1568             g_free (params->perms);
1569             g_free (params);
1570             close (filedes[0]);
1571             close (filedes[1]);
1572 
1573             return g_fopen (filename, perms);
1574         }
1575 
1576         if (write)
1577             file = fdopen (filedes[1], "w");
1578         else
1579             file = fdopen (filedes[0], "r");
1580 
1581         G_LOCK (threads);
1582         if (!threads)
1583             threads = g_hash_table_new (g_direct_hash, g_direct_equal);
1584 
1585         g_hash_table_insert (threads, file, thread);
1586         G_UNLOCK (threads);
1587 
1588         return file;
1589     }
1590 }
1591 
1592 static gboolean
1593 wait_for_gzip (FILE* file)
1594 {
1595     gboolean retval = TRUE;
1596 
1597     G_LOCK (threads);
1598     if (threads)
1599     {
1600         GThread* thread = static_cast<decltype (thread)> (g_hash_table_lookup (threads,
1601                                                           file));
1602         if (thread)
1603         {
1604             g_hash_table_remove (threads, file);
1605             retval = GPOINTER_TO_INT (g_thread_join (thread));
1606         }
1607     }
1608     G_UNLOCK (threads);
1609 
1610     return retval;
1611 }
1612 
1613 gboolean
1614 gnc_book_write_to_xml_file_v2 (
1615     QofBook* book,
1616     const char* filename,
1617     gboolean compress)
1618 {
1619     FILE* out;
1620     gboolean success = TRUE;
1621 
1622     out = try_gz_open (filename, "w", compress, TRUE);
1623 
1624     /* Try to write as much as possible */
1625     if (!out
1626         || !gnc_book_write_to_xml_filehandle_v2 (book, out))
1627         success = FALSE;
1628 
1629     /* Close the output stream */
1630     if (out && fclose (out))
1631         success = FALSE;
1632 
1633     /* Optionally wait for parallel compression threads */
1634     if (out && compress)
1635         if (!wait_for_gzip (out))
1636             success = FALSE;
1637 
1638     return success;
1639 }
1640 
1641 /*
1642  * Have to pass in the backend as this routine needs the temporary
1643  * backend for file export, not the real backend which could be
1644  * postgresql or anything else.
1645  */
1646 gboolean
1647 gnc_book_write_accounts_to_xml_file_v2 (QofBackend* qof_be, QofBook* book,
1648                                         const char* filename)
1649 {
1650     FILE* out;
1651     gboolean success = TRUE;
1652 
1653     out = g_fopen (filename, "w");
1654 
1655     /* Try to write as much as possible */
1656     if (!out
1657         || !gnc_book_write_accounts_to_xml_filehandle_v2 (qof_be, book, out))
1658         success = FALSE;
1659 
1660     /* Close the output stream */
1661     if (out && fclose (out))
1662         success = FALSE;
1663 
1664     if (!success && !qof_be->check_error())
1665     {
1666 
1667         /* Use a generic write error code */
1668         qof_backend_set_error (qof_be, ERR_FILEIO_WRITE_ERROR);
1669     }
1670 
1671     return success;
1672 }
1673 
1674 /***********************************************************************/
1675 static gboolean
1676 is_gzipped_file (const gchar* name)
1677 {
1678     unsigned char buf[2];
1679     int fd = g_open (name, O_RDONLY, 0);
1680 
1681     if (fd == -1)
1682     {
1683         return FALSE;
1684     }
1685 
1686     if (read (fd, buf, 2) != 2)
1687     {
1688         close (fd);
1689         return FALSE;
1690     }
1691     close (fd);
1692 
1693     if (buf[0] == 037 && buf[1] == 0213)
1694     {
1695         return TRUE;
1696     }
1697 
1698     return FALSE;
1699 }
1700 
1701 QofBookFileType
1702 gnc_is_xml_data_file_v2 (const gchar* name, gboolean* with_encoding)
1703 {
1704     if (is_gzipped_file (name))
1705     {
1706         gzFile file = NULL;
1707         char first_chunk[256];
1708         int num_read;
1709 
1710 #ifdef G_OS_WIN32
1711         {
1712             gchar* conv_name = g_win32_locale_filename_from_utf8 (name);
1713             if (!conv_name)
1714                 g_warning ("Could not convert '%s' to system codepage", name);
1715             else
1716             {
1717                 file = gzopen (conv_name, "rb");
1718                 g_free (conv_name);
1719             }
1720         }
1721 #else
1722         file = gzopen (name, "r");
1723 #endif
1724         if (file == NULL)
1725             return GNC_BOOK_NOT_OURS;
1726 
1727         num_read = gzread (file, first_chunk, sizeof (first_chunk) - 1);
1728         gzclose (file);
1729 
1730         if (num_read < 1)
1731             return GNC_BOOK_NOT_OURS;
1732 
1733         return gnc_is_our_first_xml_chunk (first_chunk, with_encoding);
1734     }
1735 
1736     return (gnc_is_our_xml_file (name, with_encoding));
1737 }
1738 
1739 
1740 static void
1741 replace_character_references (gchar* string)
1742 {
1743     gchar* cursor, *semicolon, *tail;
1744     glong number;
1745 
1746     for (cursor = strstr (string, "&#");
1747          cursor && *cursor;
1748          cursor = strstr (cursor, "&#"))
1749     {
1750         semicolon = strchr (cursor, ';');
1751         if (semicolon && *semicolon)
1752         {
1753 
1754             /* parse number */
1755             errno = 0;
1756             if (* (cursor + 2) == 'x')
1757             {
1758                 number = strtol (cursor + 3, &tail, 16);
1759             }
1760             else
1761             {
1762                 number = strtol (cursor + 2, &tail, 10);
1763             }
1764             if (errno || tail != semicolon || number < 0 || number > 255)
1765             {
1766                 PWARN ("Illegal character reference");
1767                 return;
1768             }
1769 
1770             /* overwrite '&' with the specified character */
1771             *cursor = (gchar) number;
1772             cursor++;
1773             if (* (semicolon + 1))
1774             {
1775                 /* move text after semicolon the the left */
1776                 tail = g_strdup (semicolon + 1);
1777                 strcpy (cursor, tail);
1778                 g_free (tail);
1779             }
1780             else
1781             {
1782                 /* cut here */
1783                 *cursor = '\0';
1784             }
1785 
1786         }
1787         else
1788         {
1789             PWARN ("Unclosed character reference");
1790             return;
1791         }
1792     }
1793 }
1794 
1795 static void
1796 conv_free (conv_type* conv)
1797 {
1798     if (conv)
1799     {
1800         g_free (conv->utf8_string);
1801         g_free (conv);
1802     }
1803 }
1804 
1805 static void
1806 conv_list_free (GList* conv_list)
1807 {
1808     g_list_foreach (conv_list, (GFunc) conv_free, NULL);
1809     g_list_free (conv_list);
1810 }
1811 
1812 typedef struct
1813 {
1814     GQuark encoding;
1815     GIConv iconv;
1816 } iconv_item_type;
1817 
1818 gint
1819 gnc_xml2_find_ambiguous (const gchar* filename, GList* encodings,
1820                          GHashTable** unique, GHashTable** ambiguous,
1821                          GList** impossible)
1822 {
1823     FILE* file = NULL;
1824     GList* iconv_list = NULL, *conv_list = NULL, *iter;
1825     iconv_item_type* iconv_item = NULL, *ascii = NULL;
1826     const gchar* enc;
1827     GHashTable* processed = NULL;
1828     gint n_impossible = 0;
1829     GError* error = NULL;
1830     gboolean is_compressed;
1831     gboolean clean_return = FALSE;
1832 
1833     is_compressed = is_gzipped_file (filename);
1834     file = try_gz_open (filename, "r", is_compressed, FALSE);
1835     if (file == NULL)
1836     {
1837         PWARN ("Unable to open file %s", filename);
1838         goto cleanup_find_ambs;
1839     }
1840 
1841     /* we need ascii */
1842     ascii = g_new (iconv_item_type, 1);
1843     ascii->encoding = g_quark_from_string ("ASCII");
1844     ascii->iconv = g_iconv_open ("UTF-8", "ASCII");
1845     if (ascii->iconv == (GIConv) - 1)
1846     {
1847         PWARN ("Unable to open ASCII ICONV conversion descriptor");
1848         goto cleanup_find_ambs;
1849     }
1850 
1851     /* call iconv_open on encodings */
1852     for (iter = encodings; iter; iter = iter->next)
1853     {
1854         iconv_item = g_new (iconv_item_type, 1);
1855         iconv_item->encoding = GPOINTER_TO_UINT (iter->data);
1856         if (iconv_item->encoding == ascii->encoding)
1857         {
1858             continue;
1859         }
1860 
1861         enc = g_quark_to_string (iconv_item->encoding);
1862         iconv_item->iconv = g_iconv_open ("UTF-8", enc);
1863         if (iconv_item->iconv == (GIConv) - 1)
1864         {
1865             PWARN ("Unable to open IConv conversion descriptor for '%s'", enc);
1866             g_free (iconv_item);
1867             goto cleanup_find_ambs;
1868         }
1869         else
1870         {
1871             iconv_list = g_list_prepend (iconv_list, iconv_item);
1872         }
1873     }
1874 
1875     /* prepare data containers */
1876     if (unique)
1877         *unique = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
1878                                          (GDestroyNotify) conv_free);
1879     if (ambiguous)
1880         *ambiguous = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
1881                                             (GDestroyNotify) conv_list_free);
1882     if (impossible)
1883         *impossible = NULL;
1884     processed = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1885 
1886     /* loop through lines */
1887     while (1)
1888     {
1889         gchar line[256], *word, *utf8;
1890         gchar** word_array, **word_cursor;
1891         conv_type* conv = NULL;
1892 
1893         if (!fgets (line, sizeof (line) - 1, file))
1894         {
1895             if (feof (file))
1896             {
1897                 break;
1898             }
1899             else
1900             {
1901                 goto cleanup_find_ambs;
1902             }
1903         }
1904 
1905         g_strchomp (line);
1906         replace_character_references (line);
1907         word_array = g_strsplit_set (line, "> <", 0);
1908 
1909         /* loop through words */
1910         for (word_cursor = word_array; *word_cursor; word_cursor++)
1911         {
1912             word = *word_cursor;
1913             if (!word)
1914                 continue;
1915 
1916             utf8 = g_convert_with_iconv (word, -1, ascii->iconv,
1917                                          NULL, NULL, &error);
1918             if (utf8)
1919             {
1920                 /* pure ascii */
1921                 g_free (utf8);
1922                 continue;
1923             }
1924             g_error_free (error);
1925             error = NULL;
1926 
1927             if (g_hash_table_lookup_extended (processed, word, NULL, NULL))
1928             {
1929                 /* already processed */
1930                 continue;
1931             }
1932 
1933             /* loop through encodings */
1934             conv_list = NULL;
1935             for (iter = iconv_list; iter; iter = iter->next)
1936             {
1937                 iconv_item = static_cast<decltype (iconv_item)> (iter->data);
1938                 utf8 = g_convert_with_iconv (word, -1, iconv_item->iconv,
1939                                              NULL, NULL, &error);
1940                 if (utf8)
1941                 {
1942                     conv = g_new (conv_type, 1);
1943                     conv->encoding = iconv_item->encoding;
1944                     conv->utf8_string = utf8;
1945                     conv_list = g_list_prepend (conv_list, conv);
1946                 }
1947                 else
1948                 {
1949                     g_error_free (error);
1950                     error = NULL;
1951                 }
1952             }
1953 
1954             /* no successful conversion */
1955             if (!conv_list)
1956             {
1957                 if (impossible)
1958                     *impossible = g_list_append (*impossible, g_strdup (word));
1959                 n_impossible++;
1960             }
1961 
1962             /* more than one successful conversion */
1963             else if (conv_list->next)
1964             {
1965                 if (ambiguous)
1966                 {
1967                     g_hash_table_insert (*ambiguous, g_strdup (word), conv_list);
1968                 }
1969                 else
1970                 {
1971                     conv_list_free (conv_list);
1972                 }
1973             }
1974 
1975             /* only one successful conversion */
1976             else
1977             {
1978                 if (unique)
1979                 {
1980                     g_hash_table_insert (*unique, g_strdup (word), conv);
1981                 }
1982                 else
1983                 {
1984                     conv_free (conv);
1985                 }
1986                 g_list_free (conv_list);
1987             }
1988 
1989             g_hash_table_insert (processed, g_strdup (word), NULL);
1990         }
1991         g_strfreev (word_array);
1992     }
1993 
1994     clean_return = TRUE;
1995 
1996 cleanup_find_ambs:
1997 
1998     if (iconv_list)
1999     {
2000         for (iter = iconv_list; iter; iter = iter->next)
2001         {
2002             if (iter->data)
2003             {
2004                 g_iconv_close (((iconv_item_type*) iter->data)->iconv);
2005                 g_free (iter->data);
2006             }
2007         }
2008         g_list_free (iconv_list);
2009     }
2010     if (processed)
2011         g_hash_table_destroy (processed);
2012     if (ascii)
2013         g_free (ascii);
2014     if (file)
2015     {
2016         fclose (file);
2017         if (is_compressed)
2018             wait_for_gzip (file);
2019     }
2020 
2021     return (clean_return) ? n_impossible : -1;
2022 }
2023 
2024 typedef struct
2025 {
2026     const char* filename;
2027     GHashTable* subst;
2028 } push_data_type;
2029 
2030 static void
2031 parse_with_subst_push_handler (xmlParserCtxtPtr xml_context,
2032                                push_data_type* push_data)
2033 {
2034     const gchar* filename;
2035     FILE* file = NULL;
2036     GIConv ascii = (GIConv) - 1;
2037     GString* output = NULL;
2038     GError* error = NULL;
2039     gboolean is_compressed;
2040 
2041     filename = push_data->filename;
2042     is_compressed = is_gzipped_file (filename);
2043     file = try_gz_open (filename, "r", is_compressed, FALSE);
2044     if (file == NULL)
2045     {
2046         PWARN ("Unable to open file %s", filename);
2047         goto cleanup_push_handler;
2048     }
2049 
2050     ascii = g_iconv_open ("UTF-8", "ASCII");
2051     if (ascii == (GIConv) - 1)
2052     {
2053         PWARN ("Unable to open ASCII ICONV conversion descriptor");
2054         goto cleanup_push_handler;
2055     }
2056 
2057     /* loop through lines */
2058     while (1)
2059     {
2060         gchar line[256], *word, *repl, *utf8;
2061         gint pos, len;
2062         gchar* start, *cursor;
2063 
2064         if (!fgets (line, sizeof (line) - 1, file))
2065         {
2066             if (feof (file))
2067             {
2068                 break;
2069             }
2070             else
2071             {
2072                 goto cleanup_push_handler;
2073             }
2074         }
2075 
2076         replace_character_references (line);
2077         output = g_string_new (line);
2078 
2079         /* loop through words */
2080         cursor = output->str;
2081         pos = 0;
2082         while (1)
2083         {
2084             /* ignore delimiters */
2085             while (*cursor == '>' || *cursor == ' ' || *cursor == '<' ||
2086                    *cursor == '\n')
2087             {
2088                 cursor++;
2089                 pos += 1;
2090             }
2091 
2092             if (!*cursor)
2093                 /* welcome to EOL */
2094                 break;
2095 
2096             /* search for a delimiter */
2097             start = cursor;
2098             len = 0;
2099             while (*cursor && *cursor != '>' && *cursor != ' ' && *cursor != '<' &&
2100                    *cursor != '\n')
2101             {
2102                 cursor++;
2103                 len++;
2104             }
2105 
2106             utf8 = g_convert_with_iconv (start, len, ascii, NULL, NULL, &error);
2107 
2108             if (utf8)
2109             {
2110                 /* pure ascii */
2111                 g_free (utf8);
2112                 pos += len;
2113             }
2114             else
2115             {
2116                 g_error_free (error);
2117                 error = NULL;
2118 
2119                 word = g_strndup (start, len);
2120                 repl = static_cast<decltype (repl)> (g_hash_table_lookup (push_data->subst,
2121                                                                           word));
2122                 g_free (word);
2123                 if (repl)
2124                 {
2125                     /* there is a replacement */
2126                     output = g_string_insert (g_string_erase (output, pos, len),
2127                                               pos, repl);
2128                     pos += strlen (repl);
2129                     cursor = output->str + pos;
2130                 }
2131                 else
2132                 {
2133                     /* there is no replacement, return immediately */
2134                     goto cleanup_push_handler;
2135                 }
2136             }
2137         }
2138 
2139         if (xmlParseChunk (xml_context, output->str, output->len, 0) != 0)
2140         {
2141             goto cleanup_push_handler;
2142         }
2143     }
2144 
2145     /* last chunk */
2146     xmlParseChunk (xml_context, "", 0, 1);
2147 
2148 cleanup_push_handler:
2149 
2150     if (output)
2151         g_string_free (output, TRUE);
2152     if (ascii != (GIConv) - 1)
2153         g_iconv_close (ascii);
2154     if (file)
2155     {
2156         fclose (file);
2157         if (is_compressed)
2158             wait_for_gzip (file);
2159     }
2160 }
2161 
2162 gboolean
2163 gnc_xml2_parse_with_subst (GncXmlBackend* xml_be, QofBook* book, GHashTable* subst)
2164 {
2165     push_data_type* push_data;
2166     gboolean success;
2167 
2168     push_data = g_new (push_data_type, 1);
2169     push_data->filename = xml_be->get_filename();
2170     push_data->subst = subst;
2171 
2172     success = qof_session_load_from_xml_file_v2_full (
2173                   xml_be, book, (sixtp_push_handler) parse_with_subst_push_handler,
2174                   push_data, GNC_BOOK_XML2_FILE);
2175     g_free (push_data);
2176 
2177     if (success)
2178         qof_instance_set_dirty (QOF_INSTANCE (book));
2179 
2180     return success;
2181 }
2182