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