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