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