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