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  auto version_str = xmlGetProp (parent, BAD_CAST "version");
1107  auto res = fprintf (out, "<%s version=\"%s\">\n", parent->name, version_str);
1108  xmlFree (version_str);
1109  if (res < 0)
1110  return FALSE;
1111 
1112  /* We create our own output buffer so we can call xmlNodeDumpOutput to get
1113  the indentation correct. */
1114  outbuf = xmlOutputBufferCreateFile (out, NULL);
1115  if (outbuf == NULL)
1116  {
1117  xmlFreeNode (parent);
1118  return FALSE;
1119  }
1120 
1121  for (node = parent->children; node; node = node->next)
1122  {
1123  /* Write two spaces since xmlNodeDumpOutput doesn't indent the first line */
1124  xmlOutputBufferWrite (outbuf, 2, " ");
1125  xmlNodeDumpOutput (outbuf, NULL, node, 1, 1, NULL);
1126  /* It also doesn't terminate the last line */
1127  xmlOutputBufferWrite (outbuf, 1, "\n");
1128  if (ferror (out))
1129  break;
1130  gd->counter.prices_loaded += 1;
1131  sixtp_run_callback (gd, "prices");
1132  }
1133 
1134  xmlOutputBufferClose (outbuf);
1135 
1136  if (ferror (out) || fprintf (out, "</%s>\n", parent->name) < 0)
1137  {
1138  xmlFreeNode (parent);
1139  return FALSE;
1140  }
1141 
1142  xmlFreeNode (parent);
1143  return TRUE;
1144 }
1145 
1146 static int
1147 xml_add_trn_data (Transaction* t, gpointer data)
1148 {
1149  struct file_backend* be_data = static_cast<decltype (be_data)> (data);
1150  xmlNodePtr node;
1151 
1152  node = gnc_transaction_dom_tree_create (t);
1153 
1154  xmlElemDump (be_data->out, NULL, node);
1155  xmlFreeNode (node);
1156 
1157  if (ferror (be_data->out) || fprintf (be_data->out, "\n") < 0)
1158  return -1;
1159 
1160  be_data->gd->counter.transactions_loaded++;
1161  sixtp_run_callback (be_data->gd, "transaction");
1162  return 0;
1163 }
1164 
1165 static gboolean
1166 write_transactions (FILE* out, QofBook* book, sixtp_gdv2* gd)
1167 {
1168  struct file_backend be_data;
1169 
1170  be_data.out = out;
1171  be_data.gd = gd;
1172  return 0 ==
1173  xaccAccountTreeForEachTransaction (gnc_book_get_root_account (book),
1174  xml_add_trn_data,
1175  (gpointer) &be_data);
1176 }
1177 
1178 static gboolean
1179 write_template_transaction_data (FILE* out, QofBook* book, sixtp_gdv2* gd)
1180 {
1181  Account* ra;
1182  struct file_backend be_data;
1183 
1184  be_data.out = out;
1185  be_data.gd = gd;
1186 
1187  ra = gnc_book_get_template_root (book);
1188  if (gnc_account_n_descendants (ra) > 0)
1189  {
1190  if (fprintf (out, "<%s>\n", TEMPLATE_TRANSACTION_TAG) < 0
1191  || !write_account_tree (out, ra, gd)
1192  || xaccAccountTreeForEachTransaction (ra, xml_add_trn_data, (gpointer)&be_data)
1193  || fprintf (out, "</%s>\n", TEMPLATE_TRANSACTION_TAG) < 0)
1194 
1195  return FALSE;
1196  }
1197 
1198  return TRUE;
1199 }
1200 
1201 static gboolean
1202 write_schedXactions (FILE* out, QofBook* book, sixtp_gdv2* gd)
1203 {
1204  GList* schedXactions;
1205  SchedXaction* tmpSX;
1206  xmlNodePtr node;
1207 
1208  schedXactions = gnc_book_get_schedxactions (book)->sx_list;
1209 
1210  if (schedXactions == NULL)
1211  return TRUE;
1212 
1213  do
1214  {
1215  tmpSX = static_cast<decltype (tmpSX)> (schedXactions->data);
1216  node = gnc_schedXaction_dom_tree_create (tmpSX);
1217  xmlElemDump (out, NULL, node);
1218  xmlFreeNode (node);
1219  if (ferror (out) || fprintf (out, "\n") < 0)
1220  return FALSE;
1221  gd->counter.schedXactions_loaded++;
1222  sixtp_run_callback (gd, "schedXactions");
1223  }
1224  while ((schedXactions = schedXactions->next));
1225 
1226  return TRUE;
1227 }
1228 
1229 static void
1230 write_budget (QofInstance* ent, gpointer data)
1231 {
1232  xmlNodePtr node;
1233  struct file_backend* file_be = static_cast<decltype (file_be)> (data);
1234 
1235  GncBudget* bgt = GNC_BUDGET (ent);
1236 
1237  if (ferror (file_be->out))
1238  return;
1239 
1240  node = gnc_budget_dom_tree_create (bgt);
1241  xmlElemDump (file_be->out, NULL, node);
1242  xmlFreeNode (node);
1243  if (ferror (file_be->out) || fprintf (file_be->out, "\n") < 0)
1244  return;
1245 
1246  file_be->gd->counter.budgets_loaded++;
1247  sixtp_run_callback (file_be->gd, "budgets");
1248 }
1249 
1250 gboolean
1251 gnc_xml2_write_namespace_decl (FILE* out, const char* name_space)
1252 {
1253  g_return_val_if_fail (name_space, FALSE);
1254  return fprintf (out, "\n xmlns:%s=\"http://www.gnucash.org/XML/%s\"",
1255  name_space, name_space) >= 0;
1256 }
1257 
1258 static void
1259 write_namespace (const GncXmlDataType_t& data, FILE* out)
1260 {
1261  g_return_if_fail (data.version == GNC_FILE_BACKEND_VERS);
1262 
1263  if (data.ns && !ferror(out))
1264  (data.ns)(out);
1265 }
1266 
1267 static gboolean
1268 write_v2_header (FILE* out)
1269 {
1270  if (fprintf (out, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n") < 0
1271  || fprintf (out, "<" GNC_V2_STRING) < 0
1272 
1273  || !gnc_xml2_write_namespace_decl (out, "gnc")
1274  || !gnc_xml2_write_namespace_decl (out, "act")
1275  || !gnc_xml2_write_namespace_decl (out, "book")
1276  || !gnc_xml2_write_namespace_decl (out, "cd")
1277  || !gnc_xml2_write_namespace_decl (out, "cmdty")
1278  || !gnc_xml2_write_namespace_decl (out, "price")
1279  || !gnc_xml2_write_namespace_decl (out, "slot")
1280  || !gnc_xml2_write_namespace_decl (out, "split")
1281  || !gnc_xml2_write_namespace_decl (out, "sx")
1282  || !gnc_xml2_write_namespace_decl (out, "trn")
1283  || !gnc_xml2_write_namespace_decl (out, "ts")
1284  || !gnc_xml2_write_namespace_decl (out, "fs")
1285  || !gnc_xml2_write_namespace_decl (out, "bgt")
1286  || !gnc_xml2_write_namespace_decl (out, "recurrence")
1287  || !gnc_xml2_write_namespace_decl (out, "lot"))
1288 
1289  return FALSE;
1290 
1291  /* now cope with the plugins */
1292  for (auto data : backend_registry)
1293  write_namespace(data, out);
1294 
1295  if (ferror (out) || fprintf (out, ">\n") < 0)
1296  return FALSE;
1297 
1298  return TRUE;
1299 }
1300 
1301 gboolean
1302 gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
1303 {
1304  QofBackend* qof_be;
1305  sixtp_gdv2* gd;
1306  gboolean success = TRUE;
1307 
1308  if (!out) return FALSE;
1309 
1310  if (!write_v2_header (out)
1311  || !write_counts (out, "book", 1, NULL))
1312  return FALSE;
1313 
1314  qof_be = qof_book_get_backend (book);
1315  gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
1316  qof_be->get_percentage());
1317  gd->counter.commodities_total =
1319  gd->counter.accounts_total = 1 +
1320  gnc_account_n_descendants (gnc_book_get_root_account (book));
1321  gd->counter.transactions_total = gnc_book_count_transactions (book);
1322  gd->counter.schedXactions_total =
1323  g_list_length (gnc_book_get_schedxactions (book)->sx_list);
1324  gd->counter.budgets_total = qof_collection_count (
1325  qof_book_get_collection (book, GNC_ID_BUDGET));
1326  gd->counter.prices_total = gnc_pricedb_get_num_prices (gnc_pricedb_get_db (
1327  book));
1328 
1329  if (!write_book (out, book, gd)
1330  || fprintf (out, "</" GNC_V2_STRING ">\n\n") < 0)
1331  success = FALSE;
1332 
1333  g_free (gd);
1334  return success;
1335 }
1336 
1337 /*
1338  * This function is called by the "export" code.
1339  */
1340 gboolean
1341 gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* qof_be, QofBook* book,
1342  FILE* out)
1343 {
1344  gnc_commodity_table* table;
1345  Account* root;
1346  int ncom, nacc;
1347  sixtp_gdv2* gd;
1348  gboolean success = TRUE;
1349 
1350  if (!out) return FALSE;
1351 
1352  root = gnc_book_get_root_account (book);
1353  nacc = 1 + gnc_account_n_descendants (root);
1354 
1357 
1358  if (!write_v2_header (out)
1359  || !write_counts (out, "commodity", ncom, "account", nacc, NULL))
1360  return FALSE;
1361 
1362  gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback,
1363  qof_be->get_percentage());
1364  gd->counter.commodities_total = ncom;
1365  gd->counter.accounts_total = nacc;
1366 
1367  if (!write_commodities (out, book, gd)
1368  || !write_accounts (out, book, gd)
1369  || fprintf (out, "</" GNC_V2_STRING ">\n\n") < 0)
1370  success = FALSE;
1371 
1372  g_free (gd);
1373  return success;
1374 }
1375 
1376 static inline gzFile
1377 do_gzopen (const char* filename, const char* perms)
1378 {
1379 #ifdef G_OS_WIN32
1380  gzFile file;
1381  char* new_perms = nullptr;
1382  char* conv_name = g_win32_locale_filename_from_utf8 (filename);
1383 
1384  if (!conv_name)
1385  {
1386  g_warning ("Could not convert '%s' to system codepage",
1387  filename);
1388  return nullptr;
1389  }
1390 
1391  if (strchr (perms, 'b'))
1392  new_perms = g_strdup (perms);
1393  else
1394  new_perms = g_strdup_printf ("%cb%s", *perms, perms + 1);
1395 
1396  file = gzopen (conv_name, new_perms);
1397  g_free (new_perms);
1398  g_free (conv_name);
1399  return file;
1400 #else
1401  return gzopen (filename, perms);
1402 #endif
1403 }
1404 
1405 constexpr uint32_t BUFLEN{4096};
1406 
1407 static inline bool
1408 gz_thread_write (gzFile file, gz_thread_params_t* params)
1409 {
1410  bool success = true;
1411  gchar buffer[BUFLEN];
1412 
1413  while (success)
1414  {
1415  auto bytes = read (params->fd, buffer, BUFLEN);
1416  if (bytes > 0)
1417  {
1418  if (gzwrite (file, buffer, bytes) <= 0)
1419  {
1420  gint errnum;
1421  auto error = gzerror (file, &errnum);
1422  g_warning ("Could not write the compressed file '%s'. The error is: '%s' (%d)",
1423  params->filename, error, errnum);
1424  success = false;
1425  }
1426  }
1427  else if (bytes == 0)
1428  {
1429  break;
1430  }
1431  else
1432  {
1433  g_warning ("Could not read from pipe. The error is '%s' (errno %d)",
1434  g_strerror (errno) ? g_strerror (errno) : "", errno);
1435  success = false;
1436  }
1437  }
1438  return success;
1439 }
1440 
1441 #if COMPILER(MSVC)
1442 #define WRITE_FN _write
1443 #else
1444 #define WRITE_FN write
1445 #endif
1446 
1447 static inline bool
1448 gz_thread_read (gzFile file, gz_thread_params_t* params)
1449 {
1450  bool success = true;
1451  gchar buffer[BUFLEN];
1452 
1453  while (success)
1454  {
1455  auto gzval = gzread (file, buffer, BUFLEN);
1456  if (gzval > 0)
1457  {
1458  if (WRITE_FN (params->fd, buffer, gzval) < 0)
1459  {
1460  g_warning ("Could not write to pipe. The error is '%s' (%d)",
1461  g_strerror (errno) ? g_strerror (errno) : "", errno);
1462  success = false;
1463  }
1464  }
1465  else if (gzval == 0)
1466  {
1467  break;
1468  }
1469  else
1470  {
1471  gint errnum;
1472  const gchar* error = gzerror (file, &errnum);
1473  g_warning ("Could not read from compressed file '%s'. The error is: '%s' (%d)",
1474  params->filename, error, errnum);
1475  success = false;
1476  }
1477  }
1478  return success;
1479 }
1480 
1481 /* Compress or decompress function that is to be run in a separate thread.
1482  * Returns 1 on success or 0 otherwise, stuffed into a pointer type. */
1483 static gpointer
1484 gz_thread_func (gz_thread_params_t* params)
1485 {
1486  gint gzval;
1487  bool success = true;
1488 
1489  auto file = do_gzopen (params->filename, params->perms);
1490 
1491  if (!file)
1492  {
1493  g_warning ("Child threads gzopen failed");
1494  success = 0;
1495  goto cleanup_gz_thread_func;
1496  }
1497 
1498  if (params->write)
1499  {
1500  success = gz_thread_write (file, params);
1501  }
1502  else
1503  {
1504  success = gz_thread_read (file, params);
1505  }
1506 
1507  if ((gzval = gzclose (file)) != Z_OK)
1508  {
1509  g_warning ("Could not close the compressed file '%s' (errnum %d)",
1510  params->filename, gzval);
1511  success = false;
1512  }
1513 
1514 cleanup_gz_thread_func:
1515  close (params->fd);
1516  g_free (params->filename);
1517  g_free (params->perms);
1518  g_free (params);
1519 
1520  return GINT_TO_POINTER (success);
1521 }
1522 
1523 static std::pair<FILE*, GThread*>
1524 try_gz_open (const char* filename, const char* perms, gboolean compress,
1525  gboolean write)
1526 {
1527  if (strstr (filename, ".gz.") != NULL) /* its got a temp extension */
1528  compress = TRUE;
1529 
1530  if (!compress)
1531  return std::pair<FILE*, GThread*>(g_fopen (filename, perms),
1532  nullptr);
1533 
1534  {
1535  int filedes[2]{};
1536 
1537 #ifdef G_OS_WIN32
1538  if (_pipe (filedes, 4096, _O_BINARY) < 0)
1539  {
1540 #else
1541  /* Set CLOEXEC on the pipe FDs so that if the user runs a
1542  * report while saving WebKit's fork won't get an open copy
1543  * and keep the pipe from closing. See
1544  * https://bugs.gnucash.org/show_bug.cgi?id=798250. Win32
1545  * doesn't fork nor does it support CLOEXEC.
1546  */
1547  if (pipe (filedes) < 0 ||
1548  fcntl(filedes[0], F_SETFD, FD_CLOEXEC) == -1 ||
1549  fcntl(filedes[1], F_SETFD, FD_CLOEXEC) == -1)
1550  {
1551 #endif
1552  g_warning ("Pipe setup failed with errno %d. Opening uncompressed file.", errno);
1553  if (filedes[0])
1554  {
1555  close(filedes[0]);
1556  close(filedes[1]);
1557  }
1558 
1559  return std::pair<FILE*, GThread*>(g_fopen (filename, perms),
1560  nullptr);
1561  }
1562 
1563  gz_thread_params_t* params = g_new (gz_thread_params_t, 1);
1564  params->fd = filedes[write ? 0 : 1];
1565  params->filename = g_strdup (filename);
1566  params->perms = g_strdup (perms);
1567  params->write = write;
1568 
1569  auto thread = g_thread_new ("xml_thread", (GThreadFunc) gz_thread_func,
1570  params);
1571 
1572  FILE* file = nullptr;
1573 
1574  if (!thread)
1575  {
1576  g_warning ("Could not create thread for (de)compression.");
1577  g_free (params->filename);
1578  g_free (params->perms);
1579  g_free (params);
1580  close (filedes[0]);
1581  close (filedes[1]);
1582  file = g_fopen (filename, perms);
1583  }
1584  else
1585  {
1586  if (write)
1587  file = fdopen (filedes[1], "w");
1588  else
1589  file = fdopen (filedes[0], "r");
1590  }
1591 
1592  return std::pair<FILE*, GThread*>(file, thread);
1593  }
1594 }
1595 
1596 gboolean
1597 gnc_book_write_to_xml_file_v2 (QofBook* book, const char* filename,
1598  gboolean compress)
1599 {
1600  bool success = true;
1601 
1602  auto [file, thread] = try_gz_open (filename, "wb", compress, TRUE);
1603  if (!file)
1604  return false;
1605 
1606  /* Try to write as much as possible */
1607  if (!gnc_book_write_to_xml_filehandle_v2 (book, file))
1608  success = false;
1609 
1610  /* Close the output stream */
1611  if (fclose (file))
1612  success = false;
1613 
1614  /* Optionally wait for parallel compression threads */
1615  if (thread)
1616  {
1617  if (g_thread_join (thread) == nullptr)
1618  success = false;
1619  }
1620 
1621  return success;
1622 }
1623 
1624 /*
1625  * Have to pass in the backend as this routine needs the temporary
1626  * backend for file export, not the real backend which could be
1627  * postgresql or anything else.
1628  */
1629 gboolean
1630 gnc_book_write_accounts_to_xml_file_v2 (QofBackend* qof_be, QofBook* book,
1631  const char* filename)
1632 {
1633  FILE* out;
1634  gboolean success = TRUE;
1635 
1636  out = g_fopen (filename, "wb");
1637 
1638  /* Try to write as much as possible */
1639  if (!out
1640  || !gnc_book_write_accounts_to_xml_filehandle_v2 (qof_be, book, out))
1641  success = FALSE;
1642 
1643  /* Close the output stream */
1644  if (out && fclose (out))
1645  success = FALSE;
1646 
1647  if (!success && !qof_be->check_error())
1648  {
1649 
1650  /* Use a generic write error code */
1652  }
1653 
1654  return success;
1655 }
1656 
1657 /***********************************************************************/
1658 static bool
1659 is_gzipped_file (const gchar* name)
1660 {
1661  unsigned char buf[2];
1662  int fd = g_open (name, O_RDONLY, 0);
1663 
1664  if (fd == -1)
1665  {
1666  return false;
1667  }
1668 
1669  if (read (fd, buf, 2) != 2)
1670  {
1671  close (fd);
1672  return false;
1673  }
1674  close (fd);
1675 
1676  /* 037 0213 are the header id bytes for a gzipped file. */
1677  if (buf[0] == 037 && buf[1] == 0213)
1678  {
1679  return true;
1680  }
1681 
1682  return false;
1683 }
1684 
1685 QofBookFileType
1686 gnc_is_xml_data_file_v2 (const gchar* name, gboolean* with_encoding)
1687 {
1688  if (is_gzipped_file (name))
1689  {
1690  gzFile file = NULL;
1691  char first_chunk[256];
1692  int num_read;
1693 
1694  file = do_gzopen (name, "r");
1695 
1696  if (file == NULL)
1697  return GNC_BOOK_NOT_OURS;
1698 
1699  num_read = gzread (file, first_chunk, sizeof (first_chunk) - 1);
1700  gzclose (file);
1701 
1702  if (num_read < 1)
1703  return GNC_BOOK_NOT_OURS;
1704 
1705  return gnc_is_our_first_xml_chunk (first_chunk, with_encoding);
1706  }
1707 
1708  return (gnc_is_our_xml_file (name, with_encoding));
1709 }
1710 
1711 
1712 static void
1713 replace_character_references (gchar* string)
1714 {
1715  gchar* cursor, *semicolon, *tail;
1716  glong number;
1717 
1718  for (cursor = strstr (string, "&#");
1719  cursor && *cursor;
1720  cursor = strstr (cursor, "&#"))
1721  {
1722  semicolon = strchr (cursor, ';');
1723  if (semicolon && *semicolon)
1724  {
1725 
1726  /* parse number */
1727  errno = 0;
1728  if (* (cursor + 2) == 'x')
1729  {
1730  number = strtol (cursor + 3, &tail, 16);
1731  }
1732  else
1733  {
1734  number = strtol (cursor + 2, &tail, 10);
1735  }
1736  if (errno || tail != semicolon || number < 0 || number > 255)
1737  {
1738  PWARN ("Illegal character reference");
1739  return;
1740  }
1741 
1742  /* overwrite '&' with the specified character */
1743  *cursor = (gchar) number;
1744  cursor++;
1745  if (* (semicolon + 1))
1746  {
1747  /* move text after semicolon the the left */
1748  tail = g_strdup (semicolon + 1);
1749  strcpy (cursor, tail);
1750  g_free (tail);
1751  }
1752  else
1753  {
1754  /* cut here */
1755  *cursor = '\0';
1756  }
1757 
1758  }
1759  else
1760  {
1761  PWARN ("Unclosed character reference");
1762  return;
1763  }
1764  }
1765 }
1766 
1767 static void
1768 conv_free (conv_type* conv)
1769 {
1770  if (conv)
1771  {
1772  g_free (conv->utf8_string);
1773  g_free (conv);
1774  }
1775 }
1776 
1777 static void
1778 conv_list_free (GList* conv_list)
1779 {
1780  g_list_foreach (conv_list, (GFunc) conv_free, NULL);
1781  g_list_free (conv_list);
1782 }
1783 
1784 typedef struct
1785 {
1786  GQuark encoding;
1787  GIConv iconv;
1788 } iconv_item_type;
1789 
1790 gint
1791 gnc_xml2_find_ambiguous (const gchar* filename, GList* encodings,
1792  GHashTable** unique, GHashTable** ambiguous,
1793  GList** impossible)
1794 {
1795  GList* iconv_list = NULL, *conv_list = NULL, *iter;
1796  iconv_item_type* iconv_item = NULL, *ascii = NULL;
1797  const gchar* enc;
1798  GHashTable* processed = NULL;
1799  gint n_impossible = 0;
1800  GError* error = NULL;
1801  gboolean clean_return = FALSE;
1802 
1803  auto [file, thread] = try_gz_open (filename, "r",
1804  is_gzipped_file (filename), FALSE);
1805  if (file == NULL)
1806  {
1807  PWARN ("Unable to open file %s", filename);
1808  goto cleanup_find_ambs;
1809  }
1810 
1811  /* we need ascii */
1812  ascii = g_new (iconv_item_type, 1);
1813  ascii->encoding = g_quark_from_string ("ASCII");
1814  ascii->iconv = g_iconv_open ("UTF-8", "ASCII");
1815  if (ascii->iconv == (GIConv) - 1)
1816  {
1817  PWARN ("Unable to open ASCII ICONV conversion descriptor");
1818  goto cleanup_find_ambs;
1819  }
1820 
1821  /* call iconv_open on encodings */
1822  for (iter = encodings; iter; iter = iter->next)
1823  {
1824  iconv_item = g_new (iconv_item_type, 1);
1825  iconv_item->encoding = GPOINTER_TO_UINT (iter->data);
1826  if (iconv_item->encoding == ascii->encoding)
1827  {
1828  continue;
1829  }
1830 
1831  enc = g_quark_to_string (iconv_item->encoding);
1832  iconv_item->iconv = g_iconv_open ("UTF-8", enc);
1833  if (iconv_item->iconv == (GIConv) - 1)
1834  {
1835  PWARN ("Unable to open IConv conversion descriptor for '%s'", enc);
1836  g_free (iconv_item);
1837  goto cleanup_find_ambs;
1838  }
1839  else
1840  {
1841  iconv_list = g_list_prepend (iconv_list, iconv_item);
1842  }
1843  }
1844 
1845  /* prepare data containers */
1846  if (unique)
1847  *unique = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
1848  (GDestroyNotify) conv_free);
1849  if (ambiguous)
1850  *ambiguous = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
1851  (GDestroyNotify) conv_list_free);
1852  if (impossible)
1853  *impossible = NULL;
1854  processed = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1855 
1856  /* loop through lines */
1857  while (1)
1858  {
1859  gchar line[256], *word, *utf8;
1860  gchar** word_array, **word_cursor;
1861  conv_type* conv = NULL;
1862 
1863  if (!fgets (line, sizeof (line) - 1, file))
1864  {
1865  if (feof (file))
1866  {
1867  break;
1868  }
1869  else
1870  {
1871  goto cleanup_find_ambs;
1872  }
1873  }
1874 
1875  g_strchomp (line);
1876  replace_character_references (line);
1877  word_array = g_strsplit_set (line, "> <", 0);
1878 
1879  /* loop through words */
1880  for (word_cursor = word_array; *word_cursor; word_cursor++)
1881  {
1882  word = *word_cursor;
1883  if (!word)
1884  continue;
1885 
1886  utf8 = g_convert_with_iconv (word, -1, ascii->iconv,
1887  NULL, NULL, &error);
1888  if (utf8)
1889  {
1890  /* pure ascii */
1891  g_free (utf8);
1892  continue;
1893  }
1894  g_error_free (error);
1895  error = NULL;
1896 
1897  if (g_hash_table_lookup_extended (processed, word, NULL, NULL))
1898  {
1899  /* already processed */
1900  continue;
1901  }
1902 
1903  /* loop through encodings */
1904  conv_list = NULL;
1905  for (iter = iconv_list; iter; iter = iter->next)
1906  {
1907  iconv_item = static_cast<decltype (iconv_item)> (iter->data);
1908  utf8 = g_convert_with_iconv (word, -1, iconv_item->iconv,
1909  NULL, NULL, &error);
1910  if (utf8)
1911  {
1912  conv = g_new (conv_type, 1);
1913  conv->encoding = iconv_item->encoding;
1914  conv->utf8_string = utf8;
1915  conv_list = g_list_prepend (conv_list, conv);
1916  }
1917  else
1918  {
1919  g_error_free (error);
1920  error = NULL;
1921  }
1922  }
1923 
1924  /* no successful conversion */
1925  if (!conv_list)
1926  {
1927  if (impossible)
1928  *impossible = g_list_append (*impossible, g_strdup (word));
1929  n_impossible++;
1930  }
1931 
1932  /* more than one successful conversion */
1933  else if (conv_list->next)
1934  {
1935  if (ambiguous)
1936  {
1937  g_hash_table_insert (*ambiguous, g_strdup (word), conv_list);
1938  }
1939  else
1940  {
1941  conv_list_free (conv_list);
1942  }
1943  }
1944 
1945  /* only one successful conversion */
1946  else
1947  {
1948  if (unique)
1949  {
1950  g_hash_table_insert (*unique, g_strdup (word), conv);
1951  }
1952  else
1953  {
1954  conv_free (conv);
1955  }
1956  g_list_free (conv_list);
1957  }
1958 
1959  g_hash_table_insert (processed, g_strdup (word), NULL);
1960  }
1961  g_strfreev (word_array);
1962  }
1963 
1964  clean_return = TRUE;
1965 
1966 cleanup_find_ambs:
1967 
1968  if (iconv_list)
1969  {
1970  for (iter = iconv_list; iter; iter = iter->next)
1971  {
1972  if (iter->data)
1973  {
1974  g_iconv_close (((iconv_item_type*) iter->data)->iconv);
1975  g_free (iter->data);
1976  }
1977  }
1978  g_list_free (iconv_list);
1979  }
1980  if (processed)
1981  g_hash_table_destroy (processed);
1982  if (ascii)
1983  g_free (ascii);
1984  if (file)
1985  {
1986  fclose (file);
1987  if (thread)
1988  g_thread_join (thread);
1989  }
1990 
1991  return (clean_return) ? n_impossible : -1;
1992 }
1993 
1994 typedef struct
1995 {
1996  const char* filename;
1997  GHashTable* subst;
1998 } push_data_type;
1999 
2000 static void
2001 parse_with_subst_push_handler (xmlParserCtxtPtr xml_context,
2002  push_data_type* push_data)
2003 {
2004  GIConv ascii = (GIConv) - 1;
2005  GString* output = NULL;
2006  GError* error = NULL;
2007 
2008  auto filename = push_data->filename;
2009  auto [file, thread] = try_gz_open (filename, "r",
2010  is_gzipped_file (filename), FALSE);
2011  if (!file)
2012  {
2013  PWARN ("Unable to open file %s", filename);
2014  goto cleanup_push_handler;
2015  }
2016 
2017  ascii = g_iconv_open ("UTF-8", "ASCII");
2018  if (ascii == (GIConv) - 1)
2019  {
2020  PWARN ("Unable to open ASCII ICONV conversion descriptor");
2021  goto cleanup_push_handler;
2022  }
2023 
2024  /* loop through lines */
2025  while (1)
2026  {
2027  gchar line[256], *word, *repl, *utf8;
2028  gint pos, len;
2029  gchar* start, *cursor;
2030 
2031  if (!fgets (line, sizeof (line) - 1, file))
2032  {
2033  if (feof (file))
2034  {
2035  break;
2036  }
2037  else
2038  {
2039  goto cleanup_push_handler;
2040  }
2041  }
2042 
2043  replace_character_references (line);
2044  output = g_string_new (line);
2045 
2046  /* loop through words */
2047  cursor = output->str;
2048  pos = 0;
2049  while (1)
2050  {
2051  /* ignore delimiters */
2052  while (*cursor == '>' || *cursor == ' ' || *cursor == '<' ||
2053  *cursor == '\n')
2054  {
2055  cursor++;
2056  pos += 1;
2057  }
2058 
2059  if (!*cursor)
2060  /* welcome to EOL */
2061  break;
2062 
2063  /* search for a delimiter */
2064  start = cursor;
2065  len = 0;
2066  while (*cursor && *cursor != '>' && *cursor != ' ' && *cursor != '<' &&
2067  *cursor != '\n')
2068  {
2069  cursor++;
2070  len++;
2071  }
2072 
2073  utf8 = g_convert_with_iconv (start, len, ascii, NULL, NULL, &error);
2074 
2075  if (utf8)
2076  {
2077  /* pure ascii */
2078  g_free (utf8);
2079  pos += len;
2080  }
2081  else
2082  {
2083  g_error_free (error);
2084  error = NULL;
2085 
2086  word = g_strndup (start, len);
2087  repl = static_cast<decltype (repl)> (g_hash_table_lookup (push_data->subst,
2088  word));
2089  g_free (word);
2090  if (repl)
2091  {
2092  /* there is a replacement */
2093  output = g_string_insert (g_string_erase (output, pos, len),
2094  pos, repl);
2095  pos += strlen (repl);
2096  cursor = output->str + pos;
2097  }
2098  else
2099  {
2100  /* there is no replacement, return immediately */
2101  goto cleanup_push_handler;
2102  }
2103  }
2104  }
2105 
2106  if (xmlParseChunk (xml_context, output->str, output->len, 0) != 0)
2107  {
2108  goto cleanup_push_handler;
2109  }
2110  }
2111 
2112  /* last chunk */
2113  xmlParseChunk (xml_context, "", 0, 1);
2114 
2115 cleanup_push_handler:
2116 
2117  if (output)
2118  g_string_free (output, TRUE);
2119  if (ascii != (GIConv) - 1)
2120  g_iconv_close (ascii);
2121  if (file)
2122  {
2123  fclose (file);
2124  if (thread)
2125  g_thread_join (thread);
2126  }
2127 }
2128 
2129 gboolean
2130 gnc_xml2_parse_with_subst (GncXmlBackend* xml_be, QofBook* book, GHashTable* subst)
2131 {
2132  push_data_type* push_data;
2133  gboolean success;
2134 
2135  push_data = g_new (push_data_type, 1);
2136  push_data->filename = xml_be->get_filename();
2137  push_data->subst = subst;
2138 
2139  success = qof_session_load_from_xml_file_v2_full (
2140  xml_be, book, (sixtp_push_handler) parse_with_subst_push_handler,
2141  push_data, GNC_BOOK_XML2_FILE);
2142  g_free (push_data);
2143 
2144  if (success)
2145  qof_instance_set_dirty (QOF_INSTANCE (book));
2146 
2147  return success;
2148 }
gnc_commodity * gnc_commodity_table_insert(gnc_commodity_table *table, gnc_commodity *comm)
Add a new commodity to the commodity table.
Account * gnc_account_get_parent(const Account *acc)
This routine returns a pointer to the parent of the specified account.
Definition: Account.cpp: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:2969
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:3356
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account&#39;s account type.
Definition: Account.cpp:3234
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:3368
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:222
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