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