LCOV - code coverage report
Current view: top level - libgnucash/backend/xml - io-gncxml-v2.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 56.1 % 886 497
Test Date: 2025-02-07 16:25:45 Functions: 82.1 % 56 46
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             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                 :             : }
        

Generated by: LCOV version 2.0-1