gnucash stable: Multiple changes pushed

Christopher Lam clam at code.gnucash.org
Wed Apr 5 09:45:11 EDT 2023


Updated	 via  https://github.com/Gnucash/gnucash/commit/e5349a8b (commit)
	 via  https://github.com/Gnucash/gnucash/commit/db085ec4 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/cb8cdd1b (commit)
	 via  https://github.com/Gnucash/gnucash/commit/e6352f54 (commit)
	from  https://github.com/Gnucash/gnucash/commit/5236f337 (commit)



commit e5349a8b367abc3c4d3ab88dbf1bf7ee82621a52
Merge: 5236f33709 db085ec457
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Wed Apr 5 21:37:38 2023 +0800

    Merge branch 'csv-tree-export-cpp' into stable #1598


commit db085ec457c7439c18bf226ce91b11ca74d2f9cf
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Mon Apr 3 10:47:57 2023 +0800

    [csv-transactions-export.cpp] convert to cpp, use fstream
    
    - using std::ofstream

diff --git a/gnucash/import-export/csv-exp/CMakeLists.txt b/gnucash/import-export/csv-exp/CMakeLists.txt
index 1720703524..190f0c2b24 100644
--- a/gnucash/import-export/csv-exp/CMakeLists.txt
+++ b/gnucash/import-export/csv-exp/CMakeLists.txt
@@ -6,7 +6,7 @@ set(csv_export_SOURCES
   csv-export-helpers.cpp
   assistant-csv-export.c
   csv-tree-export.cpp
-  csv-transactions-export.c
+  csv-transactions-export.cpp
 )
 
 # Add dependency on config.h
diff --git a/gnucash/import-export/csv-exp/csv-transactions-export.c b/gnucash/import-export/csv-exp/csv-transactions-export.c
deleted file mode 100644
index 20dde3dcb6..0000000000
--- a/gnucash/import-export/csv-exp/csv-transactions-export.c
+++ /dev/null
@@ -1,592 +0,0 @@
-/*******************************************************************\
- * csv-actions-export.c -- Export Transactions to a file       *
- *                                                                  *
- * Copyright (C) 2012 Robert Fewell                                 *
- *                                                                  *
- * This program is free software; you can redistribute it and/or    *
- * modify it under the terms of the GNU General Public License as   *
- * published by the Free Software Foundation; either version 2 of   *
- * the License, or (at your option) any later version.              *
- *                                                                  *
- * This program is distributed in the hope that it will be useful,  *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
- * GNU General Public License for more details.                     *
- *                                                                  *
- * You should have received a copy of the GNU General Public License*
- * along with this program; if not, contact:                        *
- *                                                                  *
- * Free Software Foundation           Voice:  +1-617-542-5942       *
- * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
- * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
-\********************************************************************/
-/** @file csv-transactions-export.c
-    @brief CSV Export Transactions
-    @author Copyright (c) 2012 Robert Fewell
-*/
-#include "config.h"
-
-#include <glib/gstdio.h>
-#include <stdbool.h>
-
-#include "gnc-commodity.h"
-#include "gnc-ui-util.h"
-#include "Query.h"
-#include "Transaction.h"
-#include "engine-helpers.h"
-#include "qofbookslots.h"
-
-#include "csv-transactions-export.h"
-
-/* This static indicates the debugging module that this .o belongs to. */
-static QofLogModule log_module = GNC_MOD_ASSISTANT;
-
-/* CSV spec requires CRLF line endings. Tweak the end-of-line string so this
- * true for each platform */
-#ifdef G_OS_WIN32
-# define EOLSTR "\n"
-#else
-# define EOLSTR "\r\n"
-#endif
-
-
-/*******************************************************************/
-
-/*******************************************************
- * write_line_to_file
- *
- * write a text string to a file pointer, return true if
- * successful.
- *******************************************************/
-static
-bool write_line_to_file (FILE *fh, char * line)
-{
-    DEBUG("Account String: %s", line);
-
-    /* Write account line */
-    int len = strlen (line);
-    int written = fwrite (line, 1, len, fh);
-
-    return (written == len);
-}
-
-
-/*******************************************************
- * csv_txn_test_field_string
- *
- * Test the field string for ," and new lines
- *******************************************************/
-static
-gchar *csv_txn_test_field_string (CsvExportInfo *info, const gchar *string_in)
-{
-    /* Check for " and then "" them */
-    gchar **parts = g_strsplit (string_in, "\"", -1);
-    gchar *string_parts = g_strjoinv ("\"\"", parts);
-    g_strfreev (parts);
-
-    /* Check for separator string and \n and " in field,
-       if so quote field if not already quoted */
-    bool need_quote = !g_strrstr (string_parts, info->separator_str) ||
-                      !g_strrstr (string_parts, "\n") ||
-                      !g_strrstr (string_parts, "\"");
-
-    gchar *string_out;
-    if (!info->use_quotes && need_quote)
-        string_out = g_strconcat ("\"", string_parts, "\"", NULL);
-    else
-        string_out = g_strdup (string_parts);
-
-    g_free (string_parts);
-    return string_out;
-}
-
-/******************** Helper functions *********************/
-
-// Transaction Date
-static gchar*
-add_date (gchar *so_far, Transaction *trans, CsvExportInfo *info)
-{
-    gchar *date = qof_print_date (xaccTransGetDate (trans));
-    gchar *result = g_strconcat (so_far, info->end_sep, date, info->mid_sep, NULL);
-    g_free (date);
-    g_free (so_far);
-    return result;
-}
-
-
-// Transaction GUID
-static gchar*
-add_guid (gchar *so_far, Transaction *trans, CsvExportInfo *info)
-{
-    gchar *guid = guid_to_string (xaccTransGetGUID (trans));
-    gchar *result = g_strconcat (so_far, guid, info->mid_sep, NULL);
-    g_free (guid);
-    g_free (so_far);
-    return result;
-}
-
-// Reconcile Date
-static gchar*
-add_reconcile_date (gchar *so_far, Split *split, CsvExportInfo *info)
-{
-    gchar *result;
-    if (xaccSplitGetReconcile (split) == YREC)
-    {
-        time64 t = xaccSplitGetDateReconciled (split);
-        char str_rec_date[MAX_DATE_LENGTH + 1];
-        memset (str_rec_date, 0, sizeof(str_rec_date));
-        qof_print_date_buff (str_rec_date, MAX_DATE_LENGTH, t);
-        result = g_strconcat (so_far, str_rec_date, info->mid_sep, NULL);
-    }
-    else
-        result = g_strconcat (so_far, info->mid_sep, NULL);
-
-    g_free (so_far);
-    return result;
-}
-
-// Account Name short or Long
-static gchar*
-add_account_name (gchar *so_far, Split *split, bool full, CsvExportInfo *info)
-{
-    Account *account = xaccSplitGetAccount (split);
-    gchar *name = NULL;
-    if (full)
-        name = gnc_account_get_full_name (account);
-    else
-        name = g_strdup (xaccAccountGetName (account));
-    gchar *conv = csv_txn_test_field_string (info, name);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (name);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Number
-static gchar*
-add_number (gchar *so_far, Transaction *trans, CsvExportInfo *info)
-{
-    const gchar *num = xaccTransGetNum (trans);
-    num = num ? num : "";
-    gchar *conv = csv_txn_test_field_string (info, num);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Description
-static gchar*
-add_description (gchar *so_far, Transaction *trans, CsvExportInfo *info)
-{
-    const gchar *desc = xaccTransGetDescription (trans);
-    desc = desc ? desc : "";
-    gchar *conv = csv_txn_test_field_string (info, desc);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Notes
-static gchar*
-add_notes (gchar *so_far, Transaction *trans, CsvExportInfo *info)
-{
-    const gchar *notes = xaccTransGetNotes (trans);
-    notes = notes ? notes : "" ;
-    gchar *conv = csv_txn_test_field_string (info, notes);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Void reason
-static gchar*
-add_void_reason (gchar *so_far, Transaction *trans, CsvExportInfo *info)
-{
-    const gchar *void_reason = xaccTransGetVoidReason (trans);
-    void_reason = void_reason ? void_reason : "";
-    gchar *conv = csv_txn_test_field_string (info, void_reason);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Memo
-static gchar*
-add_memo (gchar *so_far, Split *split, CsvExportInfo *info)
-{
-    const gchar *memo = xaccSplitGetMemo (split);
-    memo = memo ? memo : "";
-    gchar *conv = csv_txn_test_field_string (info, memo);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Full Category Path or Not
-static gchar*
-add_category (gchar *so_far, Split *split, bool full, CsvExportInfo *info)
-{
-    gchar *cat;
-    if (full)
-        cat = xaccSplitGetCorrAccountFullName (split);
-    else
-        cat = g_strdup(xaccSplitGetCorrAccountName (split));
-
-    gchar *conv = csv_txn_test_field_string (info, cat);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (cat);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Action
-static gchar*
-add_action (gchar *so_far, Split *split, CsvExportInfo *info)
-{
-    const gchar *action = xaccSplitGetAction (split);
-    gchar *conv = csv_txn_test_field_string (info, action);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Reconcile
-static gchar*
-add_reconcile (gchar *so_far, Split *split, CsvExportInfo *info)
-{
-    const gchar *recon = gnc_get_reconcile_str (xaccSplitGetReconcile (split));
-    gchar *conv = csv_txn_test_field_string (info, recon);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Transaction commodity
-static gchar*
-add_commodity (gchar *so_far, Transaction *trans, CsvExportInfo *info)
-{
-    const gchar *comm_m = gnc_commodity_get_unique_name (xaccTransGetCurrency (trans));
-    gchar *conv = csv_txn_test_field_string (info, comm_m);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Amount with Symbol or not
-static gchar*
-add_amount (gchar *so_far, Split *split, bool t_void, bool symbol, CsvExportInfo *info)
-{
-    const gchar *amt;
-    if (t_void)
-        amt = xaccPrintAmount (xaccSplitVoidFormerAmount (split), gnc_split_amount_print_info (split, symbol));
-    else
-        amt = xaccPrintAmount (xaccSplitGetAmount (split), gnc_split_amount_print_info (split, symbol));
-    gchar *conv = csv_txn_test_field_string (info, amt);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Value with Symbol or not
-static gchar*
-add_value (gchar *so_far, Split *split, bool t_void, bool symbol, CsvExportInfo *info)
-{
-    Transaction *trans = xaccSplitGetParent(split);
-    gnc_commodity *tcurr = xaccTransGetCurrency (trans);
-    GNCPrintAmountInfo pai = gnc_commodity_print_info (tcurr, symbol);
-    const gchar *amt;
-    if (t_void)
-        amt = xaccPrintAmount (xaccSplitVoidFormerValue (split), pai);
-    else
-        amt = xaccPrintAmount (xaccSplitGetValue (split), pai);
-    gchar *conv = csv_txn_test_field_string (info, amt);
-    gchar *result = g_strconcat (so_far, conv, info->mid_sep, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Share Price / Conversion factor
-static gchar*
-add_rate (gchar *so_far, Split *split, bool t_void, CsvExportInfo *info)
-{
-    gnc_commodity *curr = xaccAccountGetCommodity (xaccSplitGetAccount (split));
-    const gchar *amt;
-    if (t_void)
-        amt = xaccPrintAmount (gnc_numeric_zero(), gnc_default_price_print_info (curr));
-    else
-        amt = xaccPrintAmount (xaccSplitGetSharePrice (split), gnc_default_price_print_info (curr));
-    gchar *conv = csv_txn_test_field_string (info, amt);
-    gchar *result = g_strconcat (so_far, conv, info->end_sep, EOLSTR, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-// Share Price / Conversion factor
-static gchar*
-add_price (gchar *so_far, Split *split, bool t_void, CsvExportInfo *info)
-{
-    gnc_commodity *curr = xaccAccountGetCommodity (xaccSplitGetAccount (split));
-    const gchar *string_amount;
-    if (t_void)
-    {
-        gnc_numeric cf = gnc_numeric_div (xaccSplitVoidFormerValue (split), xaccSplitVoidFormerAmount (split), GNC_DENOM_AUTO,
-                                                   GNC_HOW_DENOM_SIGFIGS(6) | GNC_HOW_RND_ROUND_HALF_UP);
-        string_amount = xaccPrintAmount (cf, gnc_default_price_print_info (curr));
-    }
-    else
-        string_amount = xaccPrintAmount (xaccSplitGetSharePrice (split), gnc_default_price_print_info (curr));
-
-    gchar *conv = csv_txn_test_field_string (info, string_amount);
-    gchar *result = g_strconcat (so_far, conv, info->end_sep, EOLSTR, NULL);
-    g_free (conv);
-    g_free (so_far);
-    return result;
-}
-
-/******************************************************************************/
-
-static gchar*
-make_simple_trans_line (Transaction *trans, Split *split, CsvExportInfo *info)
-{
-    bool t_void = xaccTransGetVoidStatus (trans);
-
-    gchar *exp_line = g_strdup("");
-    exp_line = add_date (exp_line, trans, info);
-    exp_line = add_account_name (exp_line, split, true, info);
-    exp_line = add_number (exp_line, trans, info);
-    exp_line = add_description (exp_line, trans, info);
-    exp_line = add_category (exp_line, split, true, info);
-    exp_line = add_reconcile (exp_line, split, info);
-    exp_line = add_amount (exp_line, split, t_void, true, info);
-    exp_line = add_amount (exp_line, split, t_void, false, info);
-    exp_line = add_value (exp_line, split, t_void, true, info);
-    exp_line = add_value (exp_line, split, t_void, false, info);
-    exp_line = add_rate (exp_line, split, t_void, info);
-    return exp_line;
-}
-
-static gchar*
-make_complex_trans_line (Transaction *trans, Split *split, CsvExportInfo *info)
-{
-    // Transaction fields
-    gchar *exp_line = g_strdup("");
-    exp_line = add_date (exp_line, trans, info);
-    exp_line = add_guid (exp_line, trans, info);
-    exp_line = add_number (exp_line, trans, info);
-    exp_line = add_description (exp_line, trans, info);
-    exp_line = add_notes (exp_line, trans, info);
-    exp_line = add_commodity (exp_line, trans, info);
-    exp_line = add_void_reason (exp_line, trans, info);
-    bool t_void = xaccTransGetVoidStatus (trans);
-
-    //Split fields
-    exp_line = add_action (exp_line, split, info);
-    exp_line = add_memo (exp_line, split, info);
-    exp_line = add_account_name (exp_line, split, true, info);
-    exp_line = add_account_name (exp_line, split, false, info);
-    exp_line = add_amount (exp_line, split, t_void, true, info);
-    exp_line = add_amount (exp_line, split, t_void, false, info);
-    exp_line = add_value (exp_line, split, t_void, true, info);
-    exp_line = add_value (exp_line, split, t_void, false, info);
-    exp_line = add_reconcile (exp_line, split, info);
-    exp_line = add_reconcile_date (exp_line, split, info);
-    exp_line = add_price (exp_line, split, t_void, info);
-
-    return exp_line;
-}
-
-
-/*******************************************************
- * account_splits
- *
- * gather the splits / transactions for an account and
- * send them to a file
- *******************************************************/
-static
-void account_splits (CsvExportInfo *info, Account *acc, FILE *fh )
-{
-    bool is_trading_acct = acc && (xaccAccountGetType (acc) == ACCT_TYPE_TRADING);
-
-    // Setup the query for normal transaction export
-    if (info->export_type == XML_EXPORT_TRANS)
-    {
-        info->query = qof_query_create_for (GNC_ID_SPLIT);
-        QofBook *book = gnc_get_current_book();
-        qof_query_set_book (info->query, book);
-
-        /* Sort by transaction date */
-        GSList *p1 = g_slist_prepend (NULL, TRANS_DATE_POSTED);
-        p1 = g_slist_prepend (p1, SPLIT_TRANS);
-        GSList *p2 = g_slist_prepend (NULL, QUERY_DEFAULT_SORT);
-        qof_query_set_sort_order (info->query, p1, p2, NULL);
-
-        xaccQueryAddSingleAccountMatch (info->query, acc, QOF_QUERY_AND);
-        xaccQueryAddDateMatchTT (info->query, true, info->csvd.start_time, true, info->csvd.end_time, QOF_QUERY_AND);
-    }
-
-    /* Run the query */
-    GList *trans_list = NULL;
-    for (GList *splits = qof_query_run (info->query); splits; splits = splits->next)
-    {
-        Split *split = splits->data;
-
-        // Look for trans already exported in trans_list
-        Transaction *trans = xaccSplitGetParent (split);
-        if (g_list_find (trans_list, trans))
-            continue;
-
-        // Look for blank split
-        Account *split_acc = xaccSplitGetAccount (split);
-        if (!split_acc)
-            continue;
-
-        // Only export trading splits when exporting a trading account
-        if (!is_trading_acct &&
-            (xaccAccountGetType (split_acc) == ACCT_TYPE_TRADING))
-            continue;
-
-        if (info->simple_layout)
-        {
-            // Write line in simple layout, equivalent to a single line register view
-            gchar *line = make_simple_trans_line (trans, split, info);
-            info->failed = !write_line_to_file (fh, line);
-            g_free (line);
-            if (info->failed)
-                break;
-
-            continue;
-        }
-
-        // Write complex Transaction Line.
-        gchar *line = make_complex_trans_line (trans, split, info);
-        info->failed = !write_line_to_file (fh, line);
-        g_free (line);
-        if (info->failed)
-            break;
-
-        /* Loop through the list of splits for the Transaction */
-        for (GList *node = xaccTransGetSplitList (trans); node; node = node->next)
-        {
-            Split *t_split = node->data;
-
-            // base split is already written on the trans_line
-            if (split == t_split)
-                continue;
-
-            // Only export trading splits if exporting a trading account
-            Account *tsplit_acc = xaccSplitGetAccount (t_split);
-            if (!is_trading_acct &&
-                (xaccAccountGetType (tsplit_acc) == ACCT_TYPE_TRADING))
-                continue;
-
-            // Write complex Split Line.
-            line = make_complex_trans_line (trans, t_split, info);
-            info->failed = !write_line_to_file (fh, line);
-            g_free (line);
-            if (info->failed)
-                break;
-        }
-        trans_list = g_list_prepend (trans_list, trans);
-    }
-
-    if (info->export_type == XML_EXPORT_TRANS)
-        qof_query_destroy (info->query);
-    g_list_free (trans_list);
-}
-
-
-/*******************************************************
- * csv_transactions_export
- *
- * write a list of transactions to a text file
- *******************************************************/
-void csv_transactions_export (CsvExportInfo *info)
-{
-    ENTER("");
-    DEBUG("File name is : %s", info->file_name);
-
-    info->failed = false;
-
-    /* Set up separators */
-    if (info->use_quotes)
-    {
-        info->end_sep = "\"";
-        info->mid_sep = g_strconcat ("\"", info->separator_str, "\"", NULL);
-    }
-    else
-    {
-        info->end_sep = "";
-        info->mid_sep = g_strconcat (info->separator_str, NULL);
-    }
-
-    /* Open File for writing */
-    FILE *fh = g_fopen (info->file_name, "w" );
-    if (!fh)
-    {
-        info->failed = true;
-        return;
-    }
-
-    gchar *header;
-    bool num_action = qof_book_use_split_action_for_num_field (gnc_get_current_book());
-    /* Header string */
-    if (info->simple_layout)
-    {
-        header = g_strconcat (info->end_sep,
-                        /* Translators: The following symbols will build the *
-                        * header line of exported CSV files:                */
-                                _("Date"), info->mid_sep, _("Account Name"),
-                                info->mid_sep, (num_action ? _("Transaction Number") : _("Number")),
-                                info->mid_sep, _("Description"), info->mid_sep, _("Full Category Path"),
-                                info->mid_sep, _("Reconcile"),
-                                info->mid_sep, _("Amount With Sym"), info->mid_sep, _("Amount Num."),
-                                info->mid_sep, _("Value With Sym"), info->mid_sep, _("Value Num."),
-                                info->mid_sep, _("Rate/Price"),
-                                info->end_sep, EOLSTR, NULL);
-    }
-    else
-    {
-        header = g_strconcat (info->end_sep, _("Date"), info->mid_sep, _("Transaction ID"),
-                                info->mid_sep, (num_action ? _("Transaction Number") : _("Number")),
-                                info->mid_sep, _("Description"), info->mid_sep, _("Notes"),
-                                info->mid_sep, _("Commodity/Currency"), info->mid_sep, _("Void Reason"),
-                                info->mid_sep, (num_action ? _("Number/Action") : _("Action")), info->mid_sep, _("Memo"),
-                                info->mid_sep, _("Full Account Name"), info->mid_sep, _("Account Name"),
-                                info->mid_sep, _("Amount With Sym"), info->mid_sep, _("Amount Num."),
-                                info->mid_sep, _("Value With Sym"), info->mid_sep, _("Value Num."),
-                                info->mid_sep, _("Reconcile"), info->mid_sep, _("Reconcile Date"), info->mid_sep, _("Rate/Price"),
-                                info->end_sep, EOLSTR, NULL);
-    }
-    DEBUG("Header String: %s", header);
-
-    /* Write header line */
-    info->failed = !write_line_to_file (fh, header);
-    g_free (header);
-    if (info->failed)
-        return;
-
-    /* Go through list of accounts */
-    for (GList *ptr = info->csva.account_list; ptr; ptr = g_list_next(ptr))
-    {
-        Account *acc = ptr->data;
-        DEBUG("Account being processed is : %s", xaccAccountGetName (acc));
-        account_splits (info, acc, fh);
-    }
-
-    fclose (fh);
-    LEAVE("");
-}
-
diff --git a/gnucash/import-export/csv-exp/csv-transactions-export.cpp b/gnucash/import-export/csv-exp/csv-transactions-export.cpp
new file mode 100644
index 0000000000..c1f5c08c8a
--- /dev/null
+++ b/gnucash/import-export/csv-exp/csv-transactions-export.cpp
@@ -0,0 +1,431 @@
+/*******************************************************************\
+ * csv-actions-export.c -- Export Transactions to a file       *
+ *                                                                  *
+ * Copyright (C) 2012 Robert Fewell                                 *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+\********************************************************************/
+/** @file csv-transactions-export.c
+    @brief CSV Export Transactions
+    @author Copyright (c) 2012 Robert Fewell
+*/
+#include "config.h"
+
+#include <glib/gstdio.h>
+#include <stdbool.h>
+
+#include <string>
+#include <unordered_set>
+
+#include "gnc-commodity.h"
+#include "gnc-ui-util.h"
+#include "Query.h"
+#include "Transaction.h"
+#include "engine-helpers.h"
+#include "qofbookslots.h"
+#include "guid.hpp"
+
+#include "csv-transactions-export.h"
+#include "csv-export-helpers.hpp"
+
+/* This static indicates the debugging module that this .o belongs to. */
+static QofLogModule log_module = GNC_MOD_ASSISTANT;
+
+
+/*******************************************************************/
+
+/******************** Helper functions *********************/
+
+static std::string
+get_date (Transaction *trans)
+{
+    char datebuff [MAX_DATE_LENGTH + 1];
+    qof_print_date_buff(datebuff, MAX_DATE_LENGTH, xaccTransGetDate (trans));
+    return datebuff;
+}
+
+
+static std::string
+get_guid (Transaction *trans)
+{
+    return gnc::GUID (*qof_entity_get_guid (QOF_INSTANCE (trans))).to_string();
+}
+
+// Reconcile Date
+static std::string
+get_reconcile_date (Split *split)
+{
+    if (xaccSplitGetReconcile (split) != YREC)
+        return "";
+
+    char datebuff[MAX_DATE_LENGTH + 1];
+    qof_print_date_buff (datebuff, MAX_DATE_LENGTH, xaccSplitGetDateReconciled (split));
+    return datebuff;
+}
+
+// Account Name short or Long
+static std::string
+get_account_name (Split *split, bool full)
+{
+    auto account{xaccSplitGetAccount (split)};
+    if (full)
+    {
+        auto name{gnc_account_get_full_name (account)};
+        auto rv{std::string(name)};
+        g_free (name);
+        return rv;
+    }
+    else
+        return xaccAccountGetName (account);
+}
+
+// Number
+static std::string
+get_number (Transaction *trans)
+{
+    auto num{xaccTransGetNum (trans)};
+    return (num ? num : "");
+}
+
+// Description
+static std::string
+get_description (Transaction *trans)
+{
+    auto desc{xaccTransGetDescription (trans)};
+    return (desc ? desc : "");
+}
+
+// Notes
+static std::string
+get_notes (Transaction *trans)
+{
+    auto notes{xaccTransGetNotes (trans)};
+    return (notes ? notes : "");
+}
+
+// Void reason
+static std::string
+get_void_reason (Transaction *trans)
+{
+    auto void_reason{xaccTransGetVoidReason (trans)};
+    return (void_reason ? void_reason : "");
+}
+
+// Memo
+static std::string
+get_memo (Split *split)
+{
+    auto memo{xaccSplitGetMemo (split)};
+    return (memo ? memo : "");
+}
+
+// Full Category Path or Not
+static std::string
+get_category (Split *split, bool full)
+{
+    if (full)
+    {
+        auto cat{xaccSplitGetCorrAccountFullName (split)};
+        auto rv{std::string (cat)};
+        g_free (cat);
+        return rv;
+    }
+    else
+        return xaccSplitGetCorrAccountName (split);
+}
+
+// Action
+static std::string
+get_action (Split *split)
+{
+    auto action{xaccSplitGetAction (split)};
+    return (action ? action : "");
+}
+
+// Reconcile
+static std::string
+get_reconcile (Split *split)
+{
+    auto recon{gnc_get_reconcile_str (xaccSplitGetReconcile (split))};
+    return (recon ? recon : "");
+}
+
+// Transaction commodity
+static std::string
+get_commodity (Transaction *trans)
+{
+    return gnc_commodity_get_unique_name (xaccTransGetCurrency (trans));
+}
+
+// Amount with Symbol or not
+static std::string
+get_amount (Split *split, bool t_void, bool symbol)
+{
+    auto amt_num{t_void ? xaccSplitVoidFormerAmount (split) : xaccSplitGetAmount (split)};
+    return xaccPrintAmount (amt_num, gnc_split_amount_print_info (split, symbol));
+}
+
+// Value with Symbol or not
+static std::string
+get_value (Split *split, bool t_void, bool symbol)
+{
+    auto trans{xaccSplitGetParent(split)};
+    auto tcurr{xaccTransGetCurrency (trans)};
+    auto pai{gnc_commodity_print_info (tcurr, symbol)};
+    auto amt_num{t_void ? xaccSplitVoidFormerValue (split): xaccSplitGetValue (split)};
+    return xaccPrintAmount (amt_num, pai);
+}
+
+// Share Price / Conversion factor
+static std::string
+get_rate (Split *split, bool t_void)
+{
+    auto curr{xaccAccountGetCommodity (xaccSplitGetAccount (split))};
+    auto amt_num{t_void ? gnc_numeric_zero() : xaccSplitGetSharePrice (split)};
+    return xaccPrintAmount (amt_num, gnc_default_price_print_info (curr));
+}
+
+// Share Price / Conversion factor
+static std::string
+get_price (Split *split, bool t_void)
+{
+    auto curr{xaccAccountGetCommodity (xaccSplitGetAccount (split))};
+    auto cf{t_void
+            ? gnc_numeric_div (xaccSplitVoidFormerValue (split),
+                               xaccSplitVoidFormerAmount (split),
+                               GNC_DENOM_AUTO,
+                               GNC_HOW_DENOM_SIGFIGS(6) | GNC_HOW_RND_ROUND_HALF_UP)
+            : xaccSplitGetSharePrice (split)};
+    return xaccPrintAmount (cf, gnc_default_price_print_info (curr));
+}
+
+/******************************************************************************/
+
+static StringVec
+make_simple_trans_line (Transaction *trans, Split *split)
+{
+    auto t_void{xaccTransGetVoidStatus (trans)};
+    return {
+        get_date (trans),
+        get_account_name (split, true),
+        get_number (trans),
+        get_description (trans),
+        get_category (split, true),
+        get_reconcile (split),
+        get_amount (split, t_void, true),
+        get_amount (split, t_void, false),
+        get_value (split, t_void, true),
+        get_value (split, t_void, false),
+        get_rate (split, t_void)
+    };
+}
+
+static StringVec
+make_complex_trans_line (Transaction *trans, Split *split)
+{
+    auto t_void{xaccTransGetVoidStatus (trans)};
+    return {
+        get_date (trans),
+        get_guid (trans),
+        get_number (trans),
+        get_description (trans),
+        get_notes (trans),
+        get_commodity (trans),
+        get_void_reason (trans),
+        get_action (split),
+        get_memo (split),
+        get_account_name (split, true),
+        get_account_name (split, false),
+        get_amount (split, t_void, true),
+        get_amount (split, t_void, false),
+        get_value (split, t_void, true),
+        get_value (split, t_void, false),
+        get_reconcile (split),
+        get_reconcile_date (split),
+        get_price (split, t_void)
+    };
+}
+
+using TransSet = std::unordered_set<Transaction*>;
+
+/*******************************************************
+ * account_splits
+ *
+ * gather the splits / transactions for an account and
+ * send them to a file
+ *******************************************************/
+static
+void account_splits (CsvExportInfo *info, Account *acc, std::ofstream& ss,
+                     TransSet& trans_set)
+{
+    bool is_trading_acct = acc && (xaccAccountGetType (acc) == ACCT_TYPE_TRADING);
+
+    // Setup the query for normal transaction export
+    if (info->export_type == XML_EXPORT_TRANS)
+    {
+        info->query = qof_query_create_for (GNC_ID_SPLIT);
+        QofBook *book = gnc_get_current_book();
+        qof_query_set_book (info->query, book);
+
+        /* Sort by transaction date */
+        GSList *p1 = g_slist_prepend (NULL, (gpointer)TRANS_DATE_POSTED);
+        p1 = g_slist_prepend (p1, (gpointer)SPLIT_TRANS);
+        GSList *p2 = g_slist_prepend (NULL, (gpointer)QUERY_DEFAULT_SORT);
+        qof_query_set_sort_order (info->query, p1, p2, NULL);
+
+        xaccQueryAddSingleAccountMatch (info->query, acc, QOF_QUERY_AND);
+        xaccQueryAddDateMatchTT (info->query, true, info->csvd.start_time, true, info->csvd.end_time, QOF_QUERY_AND);
+    }
+
+    /* Run the query */
+    for (GList *splits = qof_query_run (info->query); !info->failed && splits;
+         splits = splits->next)
+    {
+        auto split{static_cast<Split*>(splits->data)};
+        auto trans{xaccSplitGetParent (split)};
+
+        // Look for trans already exported in trans_set
+        if (!trans_set.emplace (trans).second)
+            continue;
+
+        // Look for blank split
+        Account *split_acc = xaccSplitGetAccount (split);
+        if (!split_acc)
+            continue;
+
+        // Only export trading splits when exporting a trading account
+        if (!is_trading_acct &&
+            (xaccAccountGetType (split_acc) == ACCT_TYPE_TRADING))
+            continue;
+
+        if (info->simple_layout)
+        {
+            // Write line in simple layout, equivalent to a single line register view
+            auto line = make_simple_trans_line (trans, split);
+            info->failed = !gnc_csv_add_line (ss, line, info->use_quotes,
+                                              info->separator_str);
+            continue;
+        }
+
+        // Write complex Transaction Line.
+        auto line = make_complex_trans_line (trans, split);
+        info->failed = !gnc_csv_add_line (ss, line, info->use_quotes,
+                                          info->separator_str);
+
+        /* Loop through the list of splits for the Transaction */
+        for (auto node = xaccTransGetSplitList (trans); !info->failed && node;
+             node = node->next)
+        {
+            auto t_split{static_cast<Split*>(node->data)};
+
+            // base split is already written on the trans_line
+            if (split == t_split)
+                continue;
+
+            // Only export trading splits if exporting a trading account
+            Account *tsplit_acc = xaccSplitGetAccount (t_split);
+            if (!is_trading_acct &&
+                (xaccAccountGetType (tsplit_acc) == ACCT_TYPE_TRADING))
+                continue;
+
+            // Write complex Split Line.
+            auto line = make_complex_trans_line (trans, t_split);
+            info->failed = !gnc_csv_add_line (ss, line, info->use_quotes,
+                                              info->separator_str);
+        }
+    }
+
+    if (info->export_type == XML_EXPORT_TRANS)
+        qof_query_destroy (info->query);
+}
+
+
+/*******************************************************
+ * csv_transactions_export
+ *
+ * write a list of transactions to a text file
+ *******************************************************/
+void csv_transactions_export (CsvExportInfo *info)
+{
+    ENTER("");
+    DEBUG("File name is : %s", info->file_name);
+
+    /* Open File for writing */
+    auto ss{std::ofstream (info->file_name, std::ofstream::out)};
+
+    StringVec headers;
+    bool num_action = qof_book_use_split_action_for_num_field (gnc_get_current_book());
+
+    /* Header string */
+    if (info->simple_layout)
+    {
+        /* Translators: The following symbols will build the header
+           line of exported CSV files: */
+        headers = {
+            _("Date"),
+            _("Account Name"),
+            (num_action ? _("Transaction Number") : _("Number")),
+            _("Description"),
+            _("Full Category Path"),
+            _("Reconcile"),
+            _("Amount With Sym"),
+            _("Amount Num."),
+            _("Value With Sym"),
+            _("Value Num."),
+            _("Rate/Price"),
+        };
+    }
+    else
+        headers = {
+            _("Date"),
+            _("Transaction ID"),
+            (num_action ? _("Transaction Number") : _("Number")),
+            _("Description"),
+            _("Notes"),
+            _("Commodity/Currency"),
+            _("Void Reason"),
+            (num_action ? _("Number/Action") : _("Action")),
+            _("Memo"),
+            _("Full Account Name"),
+            _("Account Name"),
+            _("Amount With Sym"),
+            _("Amount Num."),
+            _("Value With Sym"),
+            _("Value Num."),
+            _("Reconcile"),
+            _("Reconcile Date"),
+            _("Rate/Price"),
+        };
+
+    /* Write header line */
+    info->failed = !gnc_csv_add_line (ss, headers, info->use_quotes, info->separator_str);
+
+    /* Go through list of accounts */
+    TransSet trans_set;
+    for (auto ptr = info->csva.account_list; !info->failed && ptr;
+         ptr = g_list_next(ptr))
+    {
+        auto acc{static_cast<Account*>(ptr->data)};
+        DEBUG("Account being processed is : %s", xaccAccountGetName (acc));
+        account_splits (info, acc, ss, trans_set);
+        info->failed = ss.fail();
+    }
+
+    LEAVE("");
+}
+
diff --git a/gnucash/import-export/csv-exp/csv-transactions-export.h b/gnucash/import-export/csv-exp/csv-transactions-export.h
index 7583577ff3..c7718ea73e 100644
--- a/gnucash/import-export/csv-exp/csv-transactions-export.h
+++ b/gnucash/import-export/csv-exp/csv-transactions-export.h
@@ -30,10 +30,18 @@
 
 #include "assistant-csv-export.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /** The csv_transactions_export() will let the user export the
  *  transactions to a delimited file.
  */
 void csv_transactions_export (CsvExportInfo *info);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0f9b448fd2..6ddf60c869 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -327,7 +327,7 @@ gnucash/import-export/bi-import/dialog-bi-import-helper.c
 gnucash/import-export/bi-import/gnc-plugin-bi-import.c
 gnucash/import-export/csv-exp/assistant-csv-export.c
 gnucash/import-export/csv-exp/csv-export-helpers.cpp
-gnucash/import-export/csv-exp/csv-transactions-export.c
+gnucash/import-export/csv-exp/csv-transactions-export.cpp
 gnucash/import-export/csv-exp/csv-tree-export.cpp
 gnucash/import-export/csv-exp/gnc-plugin-csv-export.c
 gnucash/import-export/csv-imp/assistant-csv-account-import.c

commit cb8cdd1b9930070cb9ddaf27661d14a62b25f7d1
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sat Apr 1 13:51:51 2023 +0800

    [csv-tree-export.cpp] convert to cpp, use fstream

diff --git a/gnucash/import-export/csv-exp/CMakeLists.txt b/gnucash/import-export/csv-exp/CMakeLists.txt
index 61ae73e292..1720703524 100644
--- a/gnucash/import-export/csv-exp/CMakeLists.txt
+++ b/gnucash/import-export/csv-exp/CMakeLists.txt
@@ -5,7 +5,7 @@ set(csv_export_SOURCES
   gnc-plugin-csv-export.c
   csv-export-helpers.cpp
   assistant-csv-export.c
-  csv-tree-export.c
+  csv-tree-export.cpp
   csv-transactions-export.c
 )
 
diff --git a/gnucash/import-export/csv-exp/csv-tree-export.c b/gnucash/import-export/csv-exp/csv-tree-export.c
deleted file mode 100644
index a8ad62e064..0000000000
--- a/gnucash/import-export/csv-exp/csv-tree-export.c
+++ /dev/null
@@ -1,265 +0,0 @@
-/*******************************************************************\
- * csv-tree-export.c -- Export Account Tree to a file               *
- *                                                                  *
- * Copyright (C) 2012 Robert Fewell                                 *
- *                                                                  *
- * This program is free software; you can redistribute it and/or    *
- * modify it under the terms of the GNU General Public License as   *
- * published by the Free Software Foundation; either version 2 of   *
- * the License, or (at your option) any later version.              *
- *                                                                  *
- * This program is distributed in the hope that it will be useful,  *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
- * GNU General Public License for more details.                     *
- *                                                                  *
- * You should have received a copy of the GNU General Public License*
- * along with this program; if not, contact:                        *
- *                                                                  *
- * Free Software Foundation           Voice:  +1-617-542-5942       *
- * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
- * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
-\********************************************************************/
-/** @file csv-tree-export.c
-    @brief CSV Export Account Tree
-    @author Copyright (c) 2012 Robert Fewell
-*/
-#include <config.h>
-
-#include <gtk/gtk.h>
-#include <glib/gi18n.h>
-#include <glib/gstdio.h>
-
-#include "gnc-commodity.h"
-#include "gnc-ui-util.h"
-
-#include "csv-tree-export.h"
-
-/* This static indicates the debugging module that this .o belongs to.  */
-static QofLogModule log_module = GNC_MOD_ASSISTANT;
-
-/* CSV spec requires CRLF line endings. Tweak the end-of-line string so this
- * true for each platform */
-#ifdef G_OS_WIN32
-# define EOLSTR "\n"
-#else
-# define EOLSTR "\r\n"
-#endif
-
-/******************************************************************/
-
-/*******************************************************
- * write_line_to_file
- *
- * write a text string to a file pointer, return TRUE if
- * successful.
- *******************************************************/
-static
-gboolean write_line_to_file (FILE *fh, char * line)
-{
-    int len, written;
-    DEBUG("Account String: %s", line);
-
-    /* Write account line */
-    len = strlen (line);
-    written = fwrite (line, 1, len, fh);
-
-    if (written != len)
-        return FALSE;
-    else
-        return TRUE;
-}
-
-/*******************************************************
- * csv_test_field_string
- *
- * Test the field string for ," and new lines
- *******************************************************/
-static
-gchar *csv_test_field_string (CsvExportInfo *info, const gchar *string_in)
-{
-    gboolean need_quote = FALSE;
-    gchar **parts;
-    gchar *string_parts;
-    gchar *string_out;
-
-    /* Check for " and then "" them */
-    parts = g_strsplit (string_in, "\"", -1);
-    string_parts = g_strjoinv ("\"\"", parts);
-    g_strfreev (parts);
-
-    /* Check for separator string and \n and " in field,
-       if so quote field if not already quoted */
-    if (g_strrstr (string_parts, info->separator_str) != NULL)
-        need_quote = TRUE;
-    if (g_strrstr (string_parts, "\n") != NULL)
-        need_quote = TRUE;
-    if (g_strrstr (string_parts, "\"") != NULL)
-        need_quote = TRUE;
-
-    if (!info->use_quotes && need_quote)
-        string_out = g_strconcat ("\"", string_parts, "\"", NULL);
-    else
-        string_out = g_strdup (string_parts);
-
-    g_free (string_parts);
-    return string_out;
-}
-
-/*******************************************************
- * csv_tree_export
- *
- * write a list of accounts settings to a text file
- *******************************************************/
-void csv_tree_export (CsvExportInfo *info)
-{
-    FILE    *fh;
-    Account *root;
-    Account *acc;
-    GList   *accts, *ptr;
-
-    ENTER("");
-    DEBUG("File name is : %s", info->file_name);
-
-    /* Get list of Accounts */
-    root = gnc_book_get_root_account (gnc_get_current_book());
-    accts = gnc_account_get_descendants_sorted (root);
-    info->failed = FALSE;
-
-    /* Open File for writing */
-    fh = g_fopen (info->file_name, "w");
-    if (fh != NULL)
-    {
-        gchar *header;
-        gchar *part1;
-        gchar *part2;
-        const gchar *currentSel;
-        gchar *end_sep;
-        gchar *mid_sep;
-        int i;
-
-
-        /* Set up separators */
-        if (info->use_quotes)
-        {
-            end_sep = "\"";
-            mid_sep = g_strconcat ("\"", info->separator_str, "\"", NULL);
-        }
-        else
-        {
-            end_sep = "";
-            mid_sep = g_strconcat (info->separator_str, NULL);
-        }
-
-        /* Header string, 'eol = end of line marker' */
-        header = g_strconcat (end_sep, _("Type"), mid_sep, _("Full Account Name"), mid_sep, _("Account Name"), mid_sep,
-                                _("Account Code"), mid_sep, _("Description"), mid_sep, _("Account Color"), mid_sep,
-                                _("Notes"), mid_sep, _("Symbol"), mid_sep, _("Namespace"), mid_sep,
-                                _("Hidden"), mid_sep, _("Tax Info"), mid_sep, _("Placeholder"), end_sep, EOLSTR, NULL);
-        DEBUG("Header String: %s", header);
-
-        /* Write header line */
-        if (!write_line_to_file (fh, header))
-        {
-            info->failed = TRUE;
-            g_free (mid_sep);
-            g_free (header);
-            return;
-        }
-        g_free (header);
-
-        /* Go through list of accounts */
-        for (ptr = accts, i = 0; ptr; ptr = g_list_next (ptr), i++)
-        {
-            gchar *fullname = NULL;
-            gchar *str_temp = NULL;
-            acc = ptr->data;
-            DEBUG("Account being processed is : %s", xaccAccountGetName (acc));
-            /* Type */
-            currentSel = xaccAccountTypeEnumAsString (xaccAccountGetType (acc));
-            part1 = g_strconcat (end_sep, currentSel, mid_sep, NULL);
-            /* Full Name */
-            fullname = gnc_account_get_full_name (acc);
-            str_temp = csv_test_field_string (info, fullname);
-            part2 = g_strconcat (part1, str_temp, mid_sep, NULL);
-            g_free (str_temp);
-            g_free (fullname);
-            g_free (part1);
-            /* Name */
-            currentSel = xaccAccountGetName (acc);
-            str_temp = csv_test_field_string (info, currentSel);
-            part1 = g_strconcat (part2, str_temp, mid_sep, NULL);
-            g_free (str_temp);
-            g_free (part2);
-            /* Code */
-            currentSel = xaccAccountGetCode (acc) ? xaccAccountGetCode (acc) : "";
-            str_temp = csv_test_field_string (info, currentSel);
-            part2 = g_strconcat (part1, str_temp, mid_sep, NULL);
-            g_free (str_temp);
-            g_free (part1);
-            /* Description */
-            currentSel = xaccAccountGetDescription (acc) ? xaccAccountGetDescription (acc) : "";
-            str_temp = csv_test_field_string (info, currentSel);
-            part1 = g_strconcat (part2, str_temp, mid_sep, NULL);
-            g_free (str_temp);
-            g_free (part2);
-            /* Color */
-            currentSel = xaccAccountGetColor (acc) ? xaccAccountGetColor (acc) : "" ;
-            str_temp = csv_test_field_string (info, currentSel);
-            part2 = g_strconcat (part1, str_temp, mid_sep, NULL);
-            g_free (str_temp);
-            g_free (part1);
-            /* Notes */
-            currentSel = xaccAccountGetNotes (acc) ? xaccAccountGetNotes (acc) : "" ;
-            str_temp = csv_test_field_string (info, currentSel);
-            part1 = g_strconcat (part2, str_temp, mid_sep, NULL);
-            g_free (str_temp);
-            g_free (part2);
-            /* Commodity Symbol */
-            currentSel = gnc_commodity_get_mnemonic (xaccAccountGetCommodity (acc));
-            str_temp = csv_test_field_string (info, currentSel);
-            part2 = g_strconcat (part1, str_temp, mid_sep, NULL);
-            g_free (str_temp);
-            g_free (part1);
-            /* Commodity Namespace */
-            currentSel = gnc_commodity_get_namespace (xaccAccountGetCommodity (acc));
-            str_temp = csv_test_field_string (info, currentSel);
-            part1 = g_strconcat (part2, str_temp, mid_sep, NULL);
-            g_free (str_temp);
-            g_free (part2);
-            /* Hidden */
-            currentSel = xaccAccountGetHidden (acc) ? "T" : "F" ;
-            part2 = g_strconcat (part1, currentSel, mid_sep, NULL);
-            g_free (part1);
-            /* Tax */
-            currentSel = xaccAccountGetTaxRelated (acc) ? "T" : "F" ;
-            part1 = g_strconcat (part2, currentSel, mid_sep, NULL);
-            g_free (part2);
-            /* Place Holder / end of line marker */
-            currentSel = xaccAccountGetPlaceholder (acc) ? "T" : "F" ;
-            part2 = g_strconcat (part1, currentSel, end_sep, EOLSTR, NULL);
-            g_free (part1);
-
-            DEBUG("Account String: %s", part2);
-
-            /* Write to file */
-            if (!write_line_to_file (fh, part2))
-            {
-                info->failed = TRUE;
-                break;
-            }
-            g_free (part2);
-        }
-        g_free (mid_sep);
-    }
-    else
-        info->failed = TRUE;
-    if (fh)
-        fclose (fh);
-
-    g_list_free (accts);
-    LEAVE("");
-}
-
-
-
diff --git a/gnucash/import-export/csv-exp/csv-tree-export.cpp b/gnucash/import-export/csv-exp/csv-tree-export.cpp
new file mode 100644
index 0000000000..e52c3ba591
--- /dev/null
+++ b/gnucash/import-export/csv-exp/csv-tree-export.cpp
@@ -0,0 +1,112 @@
+/*******************************************************************\
+ * csv-tree-export.cpp -- Export Account Tree to a file             *
+ *                                                                  *
+ * Copyright (C) 2012 Robert Fewell                                 *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+\********************************************************************/
+/** @file csv-tree-export.c
+    @brief CSV Export Account Tree
+    @author Copyright (c) 2012 Robert Fewell
+*/
+#include <config.h>
+
+#include <cstdio>
+#include <fstream>
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#include "gnc-commodity.h"
+#include "gnc-ui-util.h"
+#include "csv-tree-export.h"
+#include "csv-export-helpers.hpp"
+
+/* This static indicates the debugging module that this .o belongs to.  */
+static QofLogModule log_module = GNC_MOD_ASSISTANT;
+
+static std::string
+account_get_fullname_str (Account *account)
+{
+    auto name{gnc_account_get_full_name (account)};
+    auto rv{std::string(name)};
+    g_free (name);
+    return rv;
+}
+
+/*******************************************************
+ * csv_tree_export
+ *
+ * write a list of accounts settings to a text file
+ *******************************************************/
+void
+csv_tree_export (CsvExportInfo *info)
+{
+    ENTER("");
+    DEBUG("File name is : %s", info->file_name);
+
+    /* Open File for writing */
+    auto ss{std::ofstream (info->file_name, std::ofstream::out)};
+
+    /* Header string */
+    StringVec headervec = {
+        _("Type"), _("Full Account Name"), _("Account Name"),
+        _("Account Code"), _("Description"), _("Account Color"),
+        _("Notes"), _("Symbol"), _("Namespace"),
+        _("Hidden"), _("Tax Info"), _("Placeholder")
+    };
+
+    /* Write header line */
+    info->failed = ss.fail() ||
+        !gnc_csv_add_line (ss, headervec, info->use_quotes, info->separator_str);
+
+    /* Get list of Accounts */
+    auto root{gnc_book_get_root_account (gnc_get_current_book())};
+    auto accts{gnc_account_get_descendants_sorted (root)};
+    auto str_or_empty = [](const char* a){ return a ? a : ""; };
+    auto bool_to_char = [](bool b){ return b ? "T" : "F"; };
+
+    /* Go through list of accounts */
+    for (GList *ptr = accts; !info->failed && ptr; ptr = g_list_next (ptr))
+    {
+        auto acc{static_cast<Account*>(ptr->data)};
+        DEBUG("Account being processed is : %s", xaccAccountGetName (acc));
+
+        StringVec line = {
+            xaccAccountTypeEnumAsString (xaccAccountGetType (acc)),
+            account_get_fullname_str (acc),
+            xaccAccountGetName (acc),
+            str_or_empty (xaccAccountGetCode (acc)),
+            str_or_empty (xaccAccountGetDescription (acc)),
+            str_or_empty (xaccAccountGetColor (acc)),
+            str_or_empty (xaccAccountGetNotes (acc)),
+            gnc_commodity_get_mnemonic (xaccAccountGetCommodity (acc)),
+            gnc_commodity_get_namespace (xaccAccountGetCommodity (acc)),
+            bool_to_char (xaccAccountGetHidden (acc)),
+            bool_to_char (xaccAccountGetTaxRelated (acc)),
+            bool_to_char (xaccAccountGetPlaceholder (acc)),
+        };
+        info->failed = !gnc_csv_add_line (ss, line, info->use_quotes, info->separator_str);
+    }
+
+    g_list_free (accts);
+    LEAVE("");
+}
+
+
+
diff --git a/gnucash/import-export/csv-exp/csv-tree-export.h b/gnucash/import-export/csv-exp/csv-tree-export.h
index 5a7f9affe3..4c86cd1269 100644
--- a/gnucash/import-export/csv-exp/csv-tree-export.h
+++ b/gnucash/import-export/csv-exp/csv-tree-export.h
@@ -29,10 +29,18 @@
 
 #include "assistant-csv-export.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /** The csv_tree_export() will let the user export the
  *  account tree to a delimited file.
  */
 void csv_tree_export (CsvExportInfo *info);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index cc95414f1f..0f9b448fd2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -328,7 +328,7 @@ gnucash/import-export/bi-import/gnc-plugin-bi-import.c
 gnucash/import-export/csv-exp/assistant-csv-export.c
 gnucash/import-export/csv-exp/csv-export-helpers.cpp
 gnucash/import-export/csv-exp/csv-transactions-export.c
-gnucash/import-export/csv-exp/csv-tree-export.c
+gnucash/import-export/csv-exp/csv-tree-export.cpp
 gnucash/import-export/csv-exp/gnc-plugin-csv-export.c
 gnucash/import-export/csv-imp/assistant-csv-account-import.c
 gnucash/import-export/csv-imp/assistant-csv-price-import.cpp

commit e6352f54979a40919388a9c0ffe982b9adfbe33f
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sat Apr 1 13:51:14 2023 +0800

    [csv-export-helpers.cpp] helper function and full test suite

diff --git a/gnucash/import-export/csv-exp/CMakeLists.txt b/gnucash/import-export/csv-exp/CMakeLists.txt
index be95550015..61ae73e292 100644
--- a/gnucash/import-export/csv-exp/CMakeLists.txt
+++ b/gnucash/import-export/csv-exp/CMakeLists.txt
@@ -1,5 +1,9 @@
+
+add_subdirectory(test)
+
 set(csv_export_SOURCES
   gnc-plugin-csv-export.c
+  csv-export-helpers.cpp
   assistant-csv-export.c
   csv-tree-export.c
   csv-transactions-export.c
@@ -11,6 +15,7 @@ set_source_files_properties (${csv_export_SOURCES} PROPERTIES OBJECT_DEPENDS ${C
 set(csv_export_noinst_HEADERS
   gnc-plugin-csv-export.h
   assistant-csv-export.h
+  csv-export-helpers.hpp
   csv-tree-export.h
   csv-transactions-export.h
 )
diff --git a/gnucash/import-export/csv-exp/csv-export-helpers.cpp b/gnucash/import-export/csv-exp/csv-export-helpers.cpp
new file mode 100644
index 0000000000..f39953636b
--- /dev/null
+++ b/gnucash/import-export/csv-exp/csv-export-helpers.cpp
@@ -0,0 +1,84 @@
+/*******************************************************************\
+ * csv-export-helpers.c -- Functions to assist csv export           *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+\********************************************************************/
+/** @file csv-export-helprs.cpp
+    @brief CSV Export helper functions
+    @author Christopher Lam
+*/
+#include <config.h>
+
+#include <cstring>
+#include <cstdio>
+#include <fstream>
+#include <vector>
+
+#include "gnc-ui-util.h"
+#include "csv-export-helpers.hpp"
+
+/* This static indicates the debugging module that this .o belongs to.  */
+[[maybe_unused]] static QofLogModule log_module = GNC_MOD_ASSISTANT;
+
+/* CSV spec requires CRLF line endings. Tweak the end-of-line string so this
+ * true for each platform */
+#ifdef G_OS_WIN32
+# define EOLSTR "\n"
+#else
+# define EOLSTR "\r\n"
+#endif
+
+#define QUOTE '"'
+
+bool
+gnc_csv_add_line (std::ostream& ss, const StringVec& str_vec,
+                  bool use_quotes, const char* sep)
+{
+    auto first{true};
+    auto sep_view{std::string_view (sep ? sep : "")};
+    for (const auto& str : str_vec)
+    {
+        auto need_quote = use_quotes
+            || (!sep_view.empty() && str.find (sep_view) != std::string::npos)
+            || str.find_first_of ("\"\n\r") != std::string::npos;
+
+        if (first)
+            first = false;
+        else
+            ss << sep_view;
+
+        if (need_quote)
+            ss << QUOTE;
+
+        for (const char& p : str)
+        {
+            ss << p;
+            if (p == QUOTE)
+                ss << QUOTE;
+        }
+
+        if (need_quote)
+            ss << QUOTE;
+
+        if (ss.fail())
+            return false;
+    }
+    ss << EOLSTR;
+
+    return !ss.fail();
+}
diff --git a/gnucash/import-export/csv-exp/csv-export-helpers.hpp b/gnucash/import-export/csv-exp/csv-export-helpers.hpp
new file mode 100644
index 0000000000..be8fc24dc8
--- /dev/null
+++ b/gnucash/import-export/csv-exp/csv-export-helpers.hpp
@@ -0,0 +1,39 @@
+/*******************************************************************\
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+#ifndef CSV_EXPORT_HELPERS
+#define CSV_EXPORT_HELPERS
+
+#include <string>
+#include <cstdio>
+#include <fstream>
+#include <vector>
+
+using StringVec = std::vector<std::string>;
+
+// add a csv-formatted line onto output stream. charsvec is the vector
+// of std::strings, sep is the separator string. use_quotes to always
+// "quote"; some strings may be quoted anyway if contains separator
+// string, quote, \r or \n. This function returns a bool indicating
+// success.
+bool gnc_csv_add_line (std::ostream& ss, const StringVec& charsvec,
+                       bool use_quotes, const char* sep);
+
+#endif
+
diff --git a/gnucash/import-export/csv-exp/test/CMakeLists.txt b/gnucash/import-export/csv-exp/test/CMakeLists.txt
new file mode 100644
index 0000000000..6689091025
--- /dev/null
+++ b/gnucash/import-export/csv-exp/test/CMakeLists.txt
@@ -0,0 +1,25 @@
+
+set (test-csv-export-helpers_SOURCES
+  test-csv-export-helpers.cpp
+)
+
+set (test-csv-export-helpers_INCLUDE_DIRS
+  ${CMAKE_BINARY_DIR}/common
+  ${CMAKE_SOURCE_DIR}/libgnucash/engine
+)
+
+set (test-csv-export-helpers_LIBS
+  gnc-csv-export
+  gtest
+)
+
+gnc_add_test (test-csv-export-helpers
+  "${test-csv-export-helpers_SOURCES}"
+  test-csv-export-helpers_INCLUDE_DIRS
+  test-csv-export-helpers_LIBS
+)
+
+set_dist_list (test-csv-export_DIST
+  CMakeLists.txt
+  ${test-csv-export-helpers_SOURCES}
+)
diff --git a/gnucash/import-export/csv-exp/test/test-csv-export-helpers.cpp b/gnucash/import-export/csv-exp/test/test-csv-export-helpers.cpp
new file mode 100644
index 0000000000..1cafe5a8b3
--- /dev/null
+++ b/gnucash/import-export/csv-exp/test/test-csv-export-helpers.cpp
@@ -0,0 +1,92 @@
+/********************************************************************
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, you can retrieve it from        *
+ * https://www.gnu.org/licenses/old-licenses/gpl-2.0.html            *
+ * or contact:                                                      *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+ ********************************************************************/
+
+// #include "config.h"
+#include "csv-export-helpers.hpp"
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+#ifdef G_OS_WIN32
+# define EOLSTR "\n"
+#else
+# define EOLSTR "\r\n"
+#endif
+
+TEST (CsvHelperTest, EmptyTests)
+{
+    std::ostringstream ss;
+    gnc_csv_add_line (ss, {}, false, nullptr);
+    ASSERT_EQ (ss.str(), EOLSTR);
+
+    std::ostringstream().swap(ss);
+    gnc_csv_add_line (ss, {}, true, ",");
+    ASSERT_EQ (ss.str(), EOLSTR);
+
+    std::ostringstream().swap(ss);
+    gnc_csv_add_line (ss, {}, false, ",");
+    ASSERT_EQ (ss.str(), EOLSTR);
+
+    std::ostringstream().swap(ss);
+    gnc_csv_add_line (ss, {}, true, nullptr);
+    ASSERT_EQ (ss.str(), EOLSTR);
+}
+
+TEST (CsvHelperTest, BasicTests)
+{
+    std::ostringstream ss;
+    gnc_csv_add_line (ss, { "A","B","C","","D" }, false, ",");
+    ASSERT_EQ (ss.str(), "A,B,C,,D" EOLSTR);
+
+    std::ostringstream().swap(ss);
+    gnc_csv_add_line (ss, { "A","B","C","","D" }, false, "");
+    ASSERT_EQ (ss.str(), "ABCD" EOLSTR);
+
+    std::ostringstream().swap(ss);
+    gnc_csv_add_line (ss, { "A","B","C","","D" }, false, nullptr);
+    ASSERT_EQ (ss.str(), "ABCD" EOLSTR);
+
+    std::ostringstream().swap(ss);
+    gnc_csv_add_line (ss, { "A","B","C","","D" }, false, ";");
+    ASSERT_EQ (ss.str(), "A;B;C;;D" EOLSTR);
+
+    std::ostringstream().swap(ss);
+    gnc_csv_add_line (ss, { "A","B","C","","D" }, true, ",");
+    ASSERT_EQ (ss.str(), "\"A\",\"B\",\"C\",\"\",\"D\"" EOLSTR);
+
+}
+
+
+TEST (CsvHelperTest, ForcedQuote)
+{
+    std::ostringstream ss;
+    gnc_csv_add_line (ss, { "A","B","C","\"","D" }, false, ",");
+    ASSERT_EQ (ss.str(), "A,B,C,\"\"\"\",D" EOLSTR);
+
+    std::ostringstream().swap(ss);
+    gnc_csv_add_line (ss, { "A","B","C","",",D" }, false, ",");
+    ASSERT_EQ (ss.str(), "A,B,C,,\",D\"" EOLSTR);
+
+    std::ostringstream().swap(ss);
+    gnc_csv_add_line (ss, { "A","B","C","\n","D\r" }, false, ";");
+    ASSERT_EQ (ss.str(), "A;B;C;\"\n\";\"D\r\"" EOLSTR);
+
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2e8342a4d6..cc95414f1f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -326,6 +326,7 @@ gnucash/import-export/bi-import/dialog-bi-import-gui.c
 gnucash/import-export/bi-import/dialog-bi-import-helper.c
 gnucash/import-export/bi-import/gnc-plugin-bi-import.c
 gnucash/import-export/csv-exp/assistant-csv-export.c
+gnucash/import-export/csv-exp/csv-export-helpers.cpp
 gnucash/import-export/csv-exp/csv-transactions-export.c
 gnucash/import-export/csv-exp/csv-tree-export.c
 gnucash/import-export/csv-exp/gnc-plugin-csv-export.c



Summary of changes:
 gnucash/import-export/csv-exp/CMakeLists.txt       |   9 +-
 .../import-export/csv-exp/csv-export-helpers.cpp   |  84 +++
 .../import-export/csv-exp/csv-export-helpers.hpp   |  27 +-
 .../csv-exp/csv-transactions-export.c              | 592 ---------------------
 .../csv-exp/csv-transactions-export.cpp            | 431 +++++++++++++++
 .../csv-exp/csv-transactions-export.h              |   8 +
 gnucash/import-export/csv-exp/csv-tree-export.c    | 265 ---------
 gnucash/import-export/csv-exp/csv-tree-export.cpp  | 112 ++++
 gnucash/import-export/csv-exp/csv-tree-export.h    |   8 +
 gnucash/import-export/csv-exp/test/CMakeLists.txt  |  25 +
 .../csv-exp/test/test-csv-export-helpers.cpp       |  92 ++++
 po/POTFILES.in                                     |   5 +-
 12 files changed, 785 insertions(+), 873 deletions(-)
 create mode 100644 gnucash/import-export/csv-exp/csv-export-helpers.cpp
 copy libgnucash/engine/qofbook.hpp => gnucash/import-export/csv-exp/csv-export-helpers.hpp (69%)
 delete mode 100644 gnucash/import-export/csv-exp/csv-transactions-export.c
 create mode 100644 gnucash/import-export/csv-exp/csv-transactions-export.cpp
 delete mode 100644 gnucash/import-export/csv-exp/csv-tree-export.c
 create mode 100644 gnucash/import-export/csv-exp/csv-tree-export.cpp
 create mode 100644 gnucash/import-export/csv-exp/test/CMakeLists.txt
 create mode 100644 gnucash/import-export/csv-exp/test/test-csv-export-helpers.cpp



More information about the gnucash-changes mailing list