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