GnuCash  5.6-150-g038405b370+
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 
77 static QofLogModule log_module = GNC_MOD_IO;
78 
79 typedef 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 
99 static std::vector<GncXmlDataType_t> backend_registry;
100 void
101 gnc_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 */
108 const gchar* gnc_v2_xml_version_string = GNC_V2_STRING;
109 extern const gchar*
110 gnc_v2_book_version_string; /* see gnc-book-xml-v2 */
111 
112 /* Forward declarations */
113 static std::pair<FILE*, GThread*> try_gz_open (const char* filename,
114  const char* perms,
115  gboolean compress,
116  gboolean write);
117 static bool is_gzipped_file (const gchar* name);
118 
119 static void
120 clear_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",
152  gnc_commodity_table_insert (tbl, com);
153  }
154  else
155  {
156  setter (act, gcom);
157  if (old_scu != 0 && scu_setter)
158  scu_setter (act, old_scu);
159  gnc_commodity_destroy (com);
160  }
161 }
162 
163 static void
164 clear_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",
188  gnc_commodity_table_insert (tbl, com);
189  }
190  else
191  {
192  xaccTransBeginEdit (trans);
193  setter (trans, gcom);
194  xaccTransCommitEdit (trans);
195  gnc_commodity_destroy (com);
196  }
197 }
198 
199 static gboolean
200 add_account_local (sixtp_gdv2* data, Account* act)
201 {
202  gnc_commodity_table* table;
203  Account* parent, *root;
204  int type;
205 
206  table = gnc_commodity_table_get_table (data->book);
207 
208  clear_up_account_commodity (table, act,
211  NULL, NULL);
212 
213  clear_up_account_commodity (table, act,
218 
220  xaccAccountScrubKvp (act);
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 
246 static gboolean
247 add_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 
255 static gboolean
256 add_commodity_local (sixtp_gdv2* data, gnc_commodity* com)
257 {
258  gnc_commodity_table* table;
259 
260  table = gnc_commodity_table_get_table (data->book);
261 
263 
264  data->counter.commodities_loaded++;
265  sixtp_run_callback (data, "commodities");
266 
267  return TRUE;
268 }
269 
270 static gboolean
271 add_transaction_local (sixtp_gdv2* data, Transaction* trn)
272 {
273  gnc_commodity_table* table;
274 
275  table = gnc_commodity_table_get_table (data->book);
276 
277  xaccTransBeginEdit (trn);
278  clear_up_transaction_commodity (table, trn,
281 
284  xaccTransCommitEdit (trn);
285 
286  data->counter.transactions_loaded++;
287  sixtp_run_callback (data, "transaction");
288  return TRUE;
289 }
290 
291 static gboolean
292 add_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 
302 static gboolean
303 add_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 
345 static gboolean
346 add_pricedb_local (sixtp_gdv2* data, GNCPriceDB* db)
347 {
348  /* gnc_pricedb_print_contents(db, stdout); */
349  return TRUE;
350 }
351 
352 static void
353 counter (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 
366 static gboolean
367 gnc_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  char* strval;
373  gint64 val;
374  char* type;
375  xmlNodePtr tree = (xmlNodePtr)data_for_children;
376  gxpf_data* gdata = (gxpf_data*)global_data;
377  sixtp_gdv2* sixdata = (sixtp_gdv2*)gdata->parsedata;
378  gboolean ret = TRUE;
379 
380  if (parent_data)
381  return TRUE;
382 
383  /* OK. For some messed up reason this is getting called again with a
384  NULL tag. So we ignore those cases */
385  if (!tag)
386  return TRUE;
387 
388  g_return_val_if_fail (tree, FALSE);
389 
390  /* Note: BADXML.
391  *
392  * This is invalid xml because the namespace isn't declared in the
393  * tag itself. This should be changed to 'type' at some point. */
394  type = (char*)xmlGetProp (tree, BAD_CAST "cd:type");
395  strval = dom_tree_to_text (tree);
396  if (!string_to_gint64 (strval, &val))
397  {
398  PERR ("string_to_gint64 failed with input: %s",
399  strval ? strval : "(null)");
400  ret = FALSE;
401  }
402  else if (g_strcmp0 (type, "transaction") == 0)
403  {
404  sixdata->counter.transactions_total = val;
405  }
406  else if (g_strcmp0 (type, "account") == 0)
407  {
408  sixdata->counter.accounts_total = val;
409  }
410  else if (g_strcmp0 (type, "book") == 0)
411  {
412  sixdata->counter.books_total = val;
413  }
414  else if (g_strcmp0 (type, "commodity") == 0)
415  {
416  sixdata->counter.commodities_total = val;
417  }
418  else if (g_strcmp0 (type, "schedxaction") == 0)
419  {
420  sixdata->counter.schedXactions_total = val;
421  }
422  else if (g_strcmp0 (type, "budget") == 0)
423  {
424  sixdata->counter.budgets_total = val;
425  }
426  else if (g_strcmp0 (type, "price") == 0)
427  {
428  sixdata->counter.prices_total = val;
429  }
430  else
431  {
432  struct file_backend be_data;
433 
434  be_data.ok = FALSE;
435  be_data.tag = type;
436  for(auto data : backend_registry)
437  counter(data, &be_data);
438 
439  if (be_data.ok == FALSE)
440  {
441  PERR ("Unknown type: %s", type ? type : "(null)");
442  /* Do *NOT* flag this as an error. Gnucash 1.8 writes invalid
443  * xml by writing the 'cd:type' attribute without providing
444  * the namespace in the gnc:count-data tag. The parser is
445  * entirely within its rights to refuse to read this bad
446  * attribute. Gnucash will function correctly without the data
447  * in this tag, so just let the error pass. */
448  ret = TRUE;
449  }
450  }
451 
452  g_free (strval);
453  xmlFree (type);
454  xmlFreeNode (tree);
455  return ret;
456 }
457 
458 static sixtp*
459 gnc_counter_sixtp_parser_create (void)
460 {
461  return sixtp_dom_parser_new (gnc_counter_end_handler, NULL, NULL);
462 }
463 
464 static void
465 debug_print_counter_data (load_counter* data)
466 {
467  DEBUG ("Transactions: Total: %d, Loaded: %d",
468  data->transactions_total, data->transactions_loaded);
469  DEBUG ("Accounts: Total: %d, Loaded: %d",
470  data->accounts_total, data->accounts_loaded);
471  DEBUG ("Books: Total: %d, Loaded: %d",
472  data->books_total, data->books_loaded);
473  DEBUG ("Commodities: Total: %d, Loaded: %d",
474  data->commodities_total, data->commodities_loaded);
475  DEBUG ("Scheduled Transactions: Total: %d, Loaded: %d",
476  data->schedXactions_total, data->schedXactions_loaded);
477  DEBUG ("Budgets: Total: %d, Loaded: %d",
478  data->budgets_total, data->budgets_loaded);
479 }
480 
481 static void
482 file_rw_feedback (sixtp_gdv2* gd, const char* type)
483 {
484  load_counter* counter;
485  int loaded, total, percentage;
486 
487  g_assert (gd != NULL);
488  if (!gd->gui_display_fn)
489  return;
490 
491  counter = &gd->counter;
492  loaded = counter->transactions_loaded + counter->accounts_loaded +
493  counter->books_loaded + counter->commodities_loaded +
494  counter->schedXactions_loaded + counter->budgets_loaded +
495  counter->prices_loaded;
496  total = counter->transactions_total + counter->accounts_total +
497  counter->books_total + counter->commodities_total +
498  counter->schedXactions_total + counter->budgets_total +
499  counter->prices_total;
500  if (total == 0)
501  total = 1;
502 
503  percentage = (loaded * 100) / total;
504  if (percentage > 100)
505  {
506  /* FIXME: Perhaps the below should be replaced by:
507  print_counter_data(counter); */
508 // printf("Transactions: Total: %d, Loaded: %d\n",
509 // counter->transactions_total, counter->transactions_loaded);
510 // printf("Accounts: Total: %d, Loaded: %d\n",
511 // counter->accounts_total, counter->accounts_loaded);
512 // printf("Books: Total: %d, Loaded: %d\n",
513 // counter->books_total, counter->books_loaded);
514 // printf("Commodities: Total: %d, Loaded: %d\n",
515 // counter->commodities_total, counter->commodities_loaded);
516 // printf("Scheduled Transactions: Total: %d, Loaded: %d\n",
517 // counter->schedXactions_total, counter->schedXactions_loaded);
518 // printf("Budgets: Total: %d, Loaded: %d\n",
519 // counter->budgets_total, counter->budgets_loaded);
520  }
521  gd->gui_display_fn (NULL, percentage);
522 }
523 
524 static const char* BOOK_TAG = "gnc:book";
525 static const char* BOOK_ID_TAG = "book:id";
526 static const char* BOOK_SLOTS_TAG = "book:slots";
527 static const char* ACCOUNT_TAG = "gnc:account";
528 static const char* PRICEDB_TAG = "gnc:pricedb";
529 static const char* COMMODITY_TAG = "gnc:commodity";
530 static const char* COUNT_DATA_TAG = "gnc:count-data";
531 static const char* TRANSACTION_TAG = "gnc:transaction";
532 static const char* SCHEDXACTION_TAG = "gnc:schedxaction";
533 static const char* TEMPLATE_TRANSACTION_TAG = "gnc:template-transactions";
534 static const char* BUDGET_TAG = "gnc:budget";
535 
536 static void
537 add_item (const GncXmlDataType_t& data, struct file_backend* be_data)
538 {
539  g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
540 
541  if (be_data->ok)
542  return;
543 
544  if (!g_strcmp0 (be_data->tag, data.type_name))
545  {
546  if (data.add_item)
547  (data.add_item)(be_data->gd, be_data->data);
548 
549  be_data->ok = TRUE;
550  }
551 }
552 
553 static gboolean
554 book_callback (const char* tag, gpointer globaldata, gpointer data)
555 {
556  sixtp_gdv2* gd = (sixtp_gdv2*)globaldata;
557 
558  if (g_strcmp0 (tag, ACCOUNT_TAG) == 0)
559  {
560  add_account_local (gd, (Account*)data);
561  }
562  else if (g_strcmp0 (tag, PRICEDB_TAG) == 0)
563  {
564  add_pricedb_local (gd, (GNCPriceDB*)data);
565  }
566  else if (g_strcmp0 (tag, COMMODITY_TAG) == 0)
567  {
568  add_commodity_local (gd, (gnc_commodity*)data);
569  }
570  else if (g_strcmp0 (tag, TRANSACTION_TAG) == 0)
571  {
572  add_transaction_local (gd, (Transaction*)data);
573  }
574  else if (g_strcmp0 (tag, SCHEDXACTION_TAG) == 0)
575  {
576  add_schedXaction_local (gd, (SchedXaction*)data);
577  }
578  else if (g_strcmp0 (tag, TEMPLATE_TRANSACTION_TAG) == 0)
579  {
580  add_template_transaction_local (gd, (gnc_template_xaction_data*)data);
581  }
582  else if (g_strcmp0 (tag, BUDGET_TAG) == 0)
583  {
584  // Nothing needed here.
585  }
586  else
587  {
588  struct file_backend be_data;
589 
590  be_data.ok = FALSE;
591  be_data.tag = tag;
592  be_data.gd = gd;
593  be_data.data = data;
594 
595  for (auto data : backend_registry)
596  add_item(data, &be_data);
597 
598  if (be_data.ok == FALSE)
599  {
600  PWARN ("unexpected tag %s", tag);
601  }
602  }
603  return TRUE;
604 }
605 
606 static gboolean
607 generic_callback (const char* tag, gpointer globaldata, gpointer data)
608 {
609  sixtp_gdv2* gd = (sixtp_gdv2*)globaldata;
610 
611  if (g_strcmp0 (tag, BOOK_TAG) == 0)
612  {
613  add_book_local (gd, (QofBook*)data);
614  book_callback (tag, globaldata, data);
615  }
616  else
617  {
618  // PWARN ("importing pre-book-style XML data file");
619  book_callback (tag, globaldata, data);
620  }
621  return TRUE;
622 }
623 
624 static void
625 add_parser(const GncXmlDataType_t& data, struct file_backend* be_data)
626 {
627  g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
628 
629  if (be_data->ok == FALSE)
630  return;
631 
632  if (data.create_parser)
633  if (!sixtp_add_some_sub_parsers(
634  be_data->parser, TRUE,
635  data.type_name, (data.create_parser)(),
636  NULL, NULL))
637  be_data->ok = FALSE;
638 }
639 
640 static void
641 scrub (const GncXmlDataType_t& data, struct file_backend* be_data)
642 {
643  g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
644 
645  if (data.scrub)
646  (data.scrub)(be_data->book);
647 }
648 
649 static sixtp_gdv2*
650 gnc_sixtp_gdv2_new (
651  QofBook* book,
652  gboolean exporting,
653  countCallbackFn countcallback,
654  QofBePercentageFunc gui_display_fn)
655 {
656  sixtp_gdv2* gd = g_new0 (sixtp_gdv2, 1);
657 
658  if (gd == NULL) return NULL;
659 
660  gd->book = book;
661  gd->counter.accounts_loaded = 0;
662  gd->counter.accounts_total = 0;
663  gd->counter.books_loaded = 0;
664  gd->counter.books_total = 0;
665  gd->counter.commodities_loaded = 0;
666  gd->counter.commodities_total = 0;
667  gd->counter.transactions_loaded = 0;
668  gd->counter.transactions_total = 0;
669  gd->counter.prices_loaded = 0;
670  gd->counter.prices_total = 0;
671  gd->counter.schedXactions_loaded = 0;
672  gd->counter.schedXactions_total = 0;
673  gd->counter.budgets_loaded = 0;
674  gd->counter.budgets_total = 0;
675  gd->exporting = exporting;
676  gd->countCallback = countcallback;
677  gd->gui_display_fn = gui_display_fn;
678  return gd;
679 }
680 
681 static gboolean
682 qof_session_load_from_xml_file_v2_full (
683  GncXmlBackend* xml_be, QofBook* book,
684  sixtp_push_handler push_handler, gpointer push_user_data,
685  QofBookFileType type)
686 {
687  Account* root;
688  Account* template_root;
689  sixtp_gdv2* gd;
690  sixtp* top_parser;
691  sixtp* main_parser;
692  sixtp* book_parser;
693  struct file_backend be_data;
694  gboolean retval;
695  char* v2type = NULL;
696 
697  gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
698  xml_be->get_percentage());
699 
700  top_parser = sixtp_new ();
701  main_parser = sixtp_new ();
702  book_parser = sixtp_new ();
703 
704  if (type == GNC_BOOK_XML2_FILE)
705  v2type = g_strdup (GNC_V2_STRING);
706 
707  if (!sixtp_add_some_sub_parsers (
708  top_parser, TRUE,
709  v2type, main_parser,
710  NULL, NULL))
711  {
712  g_free (v2type);
713  goto bail;
714  }
715 
716  g_free (v2type);
717 
718  if (!sixtp_add_some_sub_parsers (
719  main_parser, TRUE,
720  COUNT_DATA_TAG, gnc_counter_sixtp_parser_create (),
721  BOOK_TAG, book_parser,
722 
723  /* the following are present here only to support
724  * the older, pre-book format. Basically, the top-level
725  * book is implicit. */
726  PRICEDB_TAG, gnc_pricedb_sixtp_parser_create (),
727  COMMODITY_TAG, gnc_commodity_sixtp_parser_create (),
728  ACCOUNT_TAG, gnc_account_sixtp_parser_create (),
729  TRANSACTION_TAG, gnc_transaction_sixtp_parser_create (),
730  SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create (),
731  TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create (),
732  NULL, NULL))
733  {
734  goto bail;
735  }
736 
737  if (!sixtp_add_some_sub_parsers (
738  book_parser, TRUE,
739  BOOK_ID_TAG, gnc_book_id_sixtp_parser_create (),
740  BOOK_SLOTS_TAG, gnc_book_slots_sixtp_parser_create (),
741  COUNT_DATA_TAG, gnc_counter_sixtp_parser_create (),
742  PRICEDB_TAG, gnc_pricedb_sixtp_parser_create (),
743  COMMODITY_TAG, gnc_commodity_sixtp_parser_create (),
744  ACCOUNT_TAG, gnc_account_sixtp_parser_create (),
745  BUDGET_TAG, gnc_budget_sixtp_parser_create (),
746  TRANSACTION_TAG, gnc_transaction_sixtp_parser_create (),
747  SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create (),
748  TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create (),
749  NULL, NULL))
750  {
751  goto bail;
752  }
753 
754  be_data.ok = TRUE;
755  be_data.parser = book_parser;
756  for (auto data : backend_registry)
757  add_parser(data, &be_data);
758  if (be_data.ok == FALSE)
759  goto bail;
760 
761  /* stop logging while we load */
762  xaccLogDisable ();
763  xaccDisableDataScrubbing ();
764 
765  if (push_handler)
766  {
767  gpointer parse_result = NULL;
768  gxpf_data gpdata;
769 
770  gpdata.cb = generic_callback;
771  gpdata.parsedata = gd;
772  gpdata.bookdata = book;
773 
774  retval = sixtp_parse_push (top_parser, push_handler, push_user_data,
775  NULL, &gpdata, &parse_result);
776  }
777  else
778  {
779  /* Even though libxml2 knows how to decompress zipped files, we
780  * do it ourself since as of version 2.9.1 it has a bug that
781  * causes it to fail to decompress certain files. See
782  * https://bugs.gnucash.org/show_bug.cgi?id=712528 for more
783  * info.
784  */
785  auto filename = xml_be->get_filename();
786  auto [file, thread] = try_gz_open (filename, "r",
787  is_gzipped_file (filename), FALSE);
788  if (!file)
789  {
790  PWARN ("Unable to open file %s", filename);
791  retval = false;
792  }
793  else
794  {
795  retval = gnc_xml_parse_fd (top_parser, file,
796  generic_callback, gd, book);
797  fclose (file);
798  if (thread)
799  g_thread_join (thread);
800  }
801  }
802 
803  if (!retval)
804  {
805  sixtp_destroy (top_parser);
806  xaccLogEnable ();
807  xaccEnableDataScrubbing ();
808  goto bail;
809  }
810  debug_print_counter_data (&gd->counter);
811 
812  /* destroy the parser */
813  sixtp_destroy (top_parser);
814  g_free (gd);
815 
816  xaccEnableDataScrubbing ();
817 
818  /* Mark the session as saved */
820 
821  /* Call individual scrub functions */
822  memset (&be_data, 0, sizeof (be_data));
823  be_data.book = book;
824  for (auto data : backend_registry)
825  scrub(data, &be_data);
826 
827  /* fix price quote sources */
828  root = gnc_book_get_root_account (book);
830 
831  /* Fix account and transaction commodities */
833 
834  /* Fix split amount/value */
835  xaccAccountTreeScrubSplits (root);
836 
837  /* commit all groups, this completes the BeginEdit started when the
838  * account_end_handler finished reading the account.
839  */
840  template_root = gnc_book_get_template_root (book);
841  gnc_account_foreach_descendant (root,
842  (AccountCb) xaccAccountCommitEdit,
843  NULL);
844  gnc_account_foreach_descendant (template_root,
845  (AccountCb) xaccAccountCommitEdit,
846  NULL);
847  /* if these exist in the XML file then they will be uncommitted */
848  if (qof_instance_get_editlevel(root) != 0)
849  xaccAccountCommitEdit(root);
850  if (qof_instance_get_editlevel(template_root) != 0)
851  xaccAccountCommitEdit(template_root);
852 
853  /* start logging again */
854  xaccLogEnable ();
855 
856  return TRUE;
857 
858 bail:
859  g_free (gd);
860  return FALSE;
861 }
862 
863 gboolean
864 qof_session_load_from_xml_file_v2 (GncXmlBackend* xml_be, QofBook* book,
865  QofBookFileType type)
866 {
867  return qof_session_load_from_xml_file_v2_full (xml_be, book, NULL, NULL, type);
868 }
869 
870 /***********************************************************************/
871 
872 static gboolean
873 write_counts (FILE* out, ...)
874 {
875  va_list ap;
876  const char* type;
877  gboolean success = TRUE;
878 
879  va_start (ap, out);
880  type = va_arg (ap, char*);
881 
882  while (success && type)
883  {
884  int amount = va_arg (ap, int);
885 
886  if (amount != 0)
887  {
888 #if GNUCASH_REALLY_BUILD_AN_XML_TREE_ON_OUTPUT
889  char *type_dup = g_strdup (type);
890  char* val;
891  xmlNodePtr node;
892 
893  val = g_strdup_printf ("%d", amount);
894 
895  node = xmlNewNode (NULL, BAD_CAST COUNT_DATA_TAG);
896  /* Note: BADXML.
897  *
898  * This is invalid xml because the namespace isn't
899  * declared in the tag itself. This should be changed to
900  * 'type' at some point. */
901  xmlSetProp (node, BAD_CAST "cd:type", checked_char_cast (type_dup));
902  xmlNodeAddContent (node, checked_char_cast (val));
903  g_free (val);
904  g_free (type_dup);
905 
906  xmlElemDump (out, NULL, node);
907  xmlFreeNode (node);
908 
909  if (ferror (out) || fprintf (out, "\n") < 0)
910  {
911  success = FALSE;
912  break;
913  }
914 #else
915  if (fprintf (out, "<%s %s=\"%s\">%d</%s>\n",
916  COUNT_DATA_TAG, "cd:type", type, amount, COUNT_DATA_TAG) < 0)
917  {
918  success = FALSE;
919  break;
920  }
921 #endif
922 
923  }
924 
925  type = va_arg (ap, char*);
926  }
927 
928  va_end (ap);
929  return success;
930 }
931 
932 static gint
933 compare_namespaces (gconstpointer a, gconstpointer b)
934 {
935  const gchar* sa = (const gchar*) a;
936  const gchar* sb = (const gchar*) b;
937  return (g_strcmp0 (sa, sb));
938 }
939 
940 static gint
941 compare_commodity_ids (gconstpointer a, gconstpointer b)
942 {
943  const gnc_commodity* ca = (const gnc_commodity*) a;
944  const gnc_commodity* cb = (const gnc_commodity*) b;
945  return (g_strcmp0 (gnc_commodity_get_mnemonic (ca),
947 }
948 
949 static gboolean write_pricedb (FILE* out, QofBook* book, sixtp_gdv2* gd);
950 static gboolean write_transactions (FILE* out, QofBook* book, sixtp_gdv2* gd);
951 static gboolean write_template_transaction_data (FILE* out, QofBook* book,
952  sixtp_gdv2* gd);
953 static gboolean write_schedXactions (FILE* out, QofBook* book, sixtp_gdv2* gd);
954 static void write_budget (QofInstance* ent, gpointer data);
955 
956 static void
957 write_counts(const GncXmlDataType_t& data, struct file_backend* be_data)
958 {
959  g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
960 
961  if (data.get_count)
962  write_counts (be_data->out, data.type_name,
963  (data.get_count) (be_data->book),
964  NULL);
965 }
966 
967 static void
968 write_data(const GncXmlDataType_t& data, struct file_backend* be_data)
969 {
970  g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
971 
972  if (data.write && !ferror(be_data->out))
973  (data.write)(be_data->out, be_data->book);
974 }
975 
976 static gboolean
977 write_book (FILE* out, QofBook* book, sixtp_gdv2* gd)
978 {
979  struct file_backend be_data;
980 
981  be_data.out = out;
982  be_data.book = book;
983  be_data.gd = gd;
984  if (fprintf (out, "<%s version=\"%s\">\n", BOOK_TAG,
985  gnc_v2_book_version_string) < 0)
986  return FALSE;
987  if (!write_book_parts (out, book))
988  return FALSE;
989 
990  /* gd->counter.{foo}_total fields should have all these totals
991  already collected. I don't know why we're re-calling all these
992  functions. */
993  if (!write_counts (out,
994  "commodity",
997  "account",
998  1 + gnc_account_n_descendants (gnc_book_get_root_account (book)),
999  "transaction",
1001  "schedxaction",
1002  g_list_length (gnc_book_get_schedxactions (book)->sx_list),
1003  "budget", qof_collection_count (
1004  qof_book_get_collection (book, GNC_ID_BUDGET)),
1006  NULL))
1007  return FALSE;
1008 
1009  for (auto data : backend_registry)
1010  write_counts(data, &be_data);
1011 
1012  if (ferror (out)
1013  || !write_commodities (out, book, gd)
1014  || !write_pricedb (out, book, gd)
1015  || !write_accounts (out, book, gd)
1016  || !write_transactions (out, book, gd)
1017  || !write_template_transaction_data (out, book, gd)
1018  || !write_schedXactions (out, book, gd))
1019 
1020  return FALSE;
1021 
1022  qof_collection_foreach (qof_book_get_collection (book, GNC_ID_BUDGET),
1023  write_budget, &be_data);
1024  if (ferror (out))
1025  return FALSE;
1026 
1027  for (auto data : backend_registry)
1028  write_data(data, &be_data);
1029  if (ferror(out))
1030  return FALSE;
1031 
1032  if (fprintf (out, "</%s>\n", BOOK_TAG) < 0)
1033  return FALSE;
1034 
1035  return TRUE;
1036 }
1037 
1038 gboolean
1039 write_commodities (FILE* out, QofBook* book, sixtp_gdv2* gd)
1040 {
1041  gnc_commodity_table* tbl;
1042  GList* namespaces;
1043  GList* lp;
1044  gboolean success = TRUE;
1045 
1046  tbl = gnc_commodity_table_get_table (book);
1047 
1048  namespaces = gnc_commodity_table_get_namespaces (tbl);
1049  if (namespaces)
1050  {
1051  namespaces = g_list_sort (namespaces, compare_namespaces);
1052  }
1053 
1054  for (lp = namespaces; success && lp; lp = lp->next)
1055  {
1056  GList* comms, *lp2;
1057  xmlNodePtr comnode;
1058 
1060  static_cast<const char*> (lp->data));
1061  comms = g_list_sort (comms, compare_commodity_ids);
1062 
1063  for (lp2 = comms; lp2; lp2 = lp2->next)
1064  {
1065  comnode = gnc_commodity_dom_tree_create (static_cast<const gnc_commodity*>
1066  (lp2->data));
1067  if (comnode == NULL)
1068  continue;
1069 
1070  xmlElemDump (out, NULL, comnode);
1071  if (ferror (out) || fprintf (out, "\n") < 0)
1072  {
1073  success = FALSE;
1074  break;
1075  }
1076 
1077  xmlFreeNode (comnode);
1078  gd->counter.commodities_loaded++;
1079  sixtp_run_callback (gd, "commodities");
1080  }
1081 
1082  g_list_free (comms);
1083  }
1084 
1085  if (namespaces) g_list_free (namespaces);
1086 
1087  return success;
1088 }
1089 
1090 static gboolean
1091 write_pricedb (FILE* out, QofBook* book, sixtp_gdv2* gd)
1092 {
1093  xmlNodePtr node;
1094  xmlNodePtr parent;
1095  xmlOutputBufferPtr outbuf;
1096 
1097  parent = gnc_pricedb_dom_tree_create (gnc_pricedb_get_db (book));
1098 
1099  if (!parent)
1100  {
1101  return TRUE;
1102  }
1103 
1104  /* Write out the parent pricedb tag then loop to write out each price.
1105  We do it this way instead of just calling xmlElemDump so that we can
1106  increment the progress bar as we go. */
1107 
1108  if (fprintf (out, "<%s version=\"%s\">\n", parent->name,
1109  xmlGetProp (parent, BAD_CAST "version")) < 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 
1146 static int
1147 xml_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 
1165 static gboolean
1166 write_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 
1178 static gboolean
1179 write_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 
1201 static gboolean
1202 write_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 
1229 static void
1230 write_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 
1250 gboolean
1251 gnc_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 
1258 static void
1259 write_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 
1267 static gboolean
1268 write_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 
1301 gboolean
1302 gnc_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  */
1340 gboolean
1341 gnc_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 
1376 static inline gzFile
1377 do_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 
1405 constexpr uint32_t BUFLEN{4096};
1406 
1407 static inline bool
1408 gz_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 
1447 static inline bool
1448 gz_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. */
1483 static gpointer
1484 gz_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 
1514 cleanup_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 
1523 static std::pair<FILE*, GThread*>
1524 try_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 
1596 gboolean
1597 gnc_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, "w", 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  */
1629 gboolean
1630 gnc_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, "w");
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 /***********************************************************************/
1658 static bool
1659 is_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 
1685 QofBookFileType
1686 gnc_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 
1712 static void
1713 replace_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 
1767 static void
1768 conv_free (conv_type* conv)
1769 {
1770  if (conv)
1771  {
1772  g_free (conv->utf8_string);
1773  g_free (conv);
1774  }
1775 }
1776 
1777 static void
1778 conv_list_free (GList* conv_list)
1779 {
1780  g_list_foreach (conv_list, (GFunc) conv_free, NULL);
1781  g_list_free (conv_list);
1782 }
1783 
1784 typedef struct
1785 {
1786  GQuark encoding;
1787  GIConv iconv;
1788 } iconv_item_type;
1789 
1790 gint
1791 gnc_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 
1966 cleanup_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 
1994 typedef struct
1995 {
1996  const char* filename;
1997  GHashTable* subst;
1998 } push_data_type;
1999 
2000 static void
2001 parse_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 
2115 cleanup_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 
2129 gboolean
2130 gnc_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 }
gnc_commodity * gnc_commodity_table_insert(gnc_commodity_table *table, gnc_commodity *comm)
Add a new commodity to the commodity table.
Account * gnc_account_get_parent(const Account *acc)
This routine returns a pointer to the parent of the specified account.
Definition: Account.cpp:2886
int xaccAccountTreeForEachTransaction(Account *acc, TransactionCallback proc, void *data)
Traverse all of the transactions in the given account group.
void xaccAccountScrubKvp(Account *account)
Removes empty "notes", "placeholder", and "hbci" KVP slots from Accounts.
Definition: Scrub.cpp:1368
gnc_commodity_table * gnc_commodity_table_get_table(QofBook *book)
Returns the commodity table associated with a book.
Definition: sixtp.h:129
void xaccTransScrubCurrency(Transaction *trans)
The xaccTransScrubCurrency method fixes transactions without a common_currency by looking for the mos...
Definition: Scrub.cpp:1105
void gnc_account_append_child(Account *new_parent, Account *child)
This function will remove from the child account any pre-existing parent relationship, and will then add the account as a child of the new parent.
Definition: Account.cpp:2787
gint gnc_account_n_descendants(const Account *account)
Return the number of descendants of the specified account.
Definition: Account.cpp:2952
const char * gnc_commodity_get_mnemonic(const gnc_commodity *cm)
Retrieve the mnemonic for the specified commodity.
int xaccAccountGetCommoditySCUi(const Account *acc)
Return the &#39;internal&#39; SCU setting.
Definition: Account.cpp:2689
void xaccAccountTreeScrubCommodities(Account *acc)
The xaccAccountTreeScrubCommodities will scrub the currency/commodity of all accounts & transactions ...
Definition: Scrub.cpp:1287
gnc_commodity * DxaccAccountGetCurrency(const Account *acc)
Definition: Account.cpp:3347
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account&#39;s account type.
Definition: Account.cpp:3217
void xaccAccountScrubCommodity(Account *account)
The xaccAccountScrubCommodity method fixed accounts without a commodity by using the old account curr...
Definition: Scrub.cpp:1222
STRUCTS.
void qof_backend_set_error(QofBackend *qof_be, QofBackendError err)
Set the error on the specified QofBackend.
void xaccLogDisable(void)
document me
Definition: TransLog.cpp:95
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
Account * gnc_book_get_template_root(const QofBook *book)
Returns the template group from the book.
Definition: SX-book.cpp:65
const char * gnc_commodity_get_namespace(const gnc_commodity *cm)
Retrieve the namespace for the specified commodity.
couldn&#39;t write to the file
Definition: qofbackend.h:97
void xaccTransScrubPostedDate(Transaction *trans)
Changes Transaction date_posted timestamps from 00:00 local to 11:00 UTC.
Definition: Scrub.cpp:1527
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
GNCPriceDB * gnc_pricedb_get_db(QofBook *book)
Return the pricedb associated with the book.
void xaccTransSetCurrency(Transaction *trans, gnc_commodity *curr)
Set a new currency on a transaction.
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
convert single-entry accounts to clean double-entry
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
api for GnuCash version 2 XML-based file format
GList * gnc_commodity_table_get_namespaces(const gnc_commodity_table *table)
Return a list of all namespaces in the commodity table.
gboolean gnc_xml2_parse_with_subst(GncXmlBackend *xml_be, QofBook *book, GHashTable *subst)
Parse a file in push mode, but replace byte sequences in the file given a hash table of substitutions...
guint gnc_pricedb_get_num_prices(GNCPriceDB *db)
Return the number of prices in the database.
Anchor Scheduled Transaction info in a book.
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
All type declarations for the whole Gnucash engine.
void xaccAccountSetCommoditySCU(Account *acc, int scu)
Set the SCU for the account.
Definition: Account.cpp:2673
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.
API for the transaction logger.
guint gnc_book_count_transactions(QofBook *book)
gint gnc_xml2_find_ambiguous(const gchar *filename, GList *encodings, GHashTable **unique, GHashTable **ambiguous, GList **impossible)
Read a file as plain byte stream to find words that are not completely ASCII.
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account&#39;s commodity.
Definition: Account.cpp:3359
gnc_commodity * xaccTransGetCurrency(const Transaction *trans)
Returns the valuation commodity of this transaction.
const char * gnc_commodity_get_unique_name(const gnc_commodity *cm)
Retrieve the &#39;unique&#39; name for the specified commodity.
void(* QofBePercentageFunc)(const char *message, double percent)
DOCUMENT ME!
Definition: qofbackend.h:163
bool check_error()
Report if there is an error.
Definition: qof-backend.cpp:73
QofCollection * qof_book_get_collection(const QofBook *book, QofIdType entity_type)
Return The table of entities of the given type.
Definition: qofbook.cpp:521
guint gnc_commodity_table_get_size(const gnc_commodity_table *tbl)
Returns the number of commodities in the commodity table.
guint qof_collection_count(const QofCollection *col)
return the number of entities in the collection.
Definition: qofid.cpp:244
void DxaccAccountSetCurrency(Account *acc, gnc_commodity *currency)
Definition: Account.cpp:2736
QofBackend * qof_book_get_backend(const QofBook *book)
Retrieve the backend used by this book.
Definition: qofbook.cpp:440
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:1345
API for Transactions and Splits (journal entries)
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition: Account.cpp:1516
The hidden root account of an account tree.
Definition: Account.h:153
void gnc_commodity_destroy(gnc_commodity *cm)
Destroy a commodity.
void xaccLogEnable(void)
document me
Definition: TransLog.cpp:99
void xaccAccountSetCommodity(Account *acc, gnc_commodity *com)
Set the account&#39;s commodity.
Definition: Account.cpp:2629