From e6352f54979a40919388a9c0ffe982b9adfbe33f Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Sat, 1 Apr 2023 13:51:14 +0800 Subject: [PATCH 1/3] [csv-export-helpers.cpp] helper function and full test suite --- gnucash/import-export/csv-exp/CMakeLists.txt | 5 + .../csv-exp/csv-export-helpers.cpp | 84 +++++++++++++++++ .../csv-exp/csv-export-helpers.hpp | 39 ++++++++ .../import-export/csv-exp/test/CMakeLists.txt | 25 +++++ .../csv-exp/test/test-csv-export-helpers.cpp | 92 +++++++++++++++++++ po/POTFILES.in | 1 + 6 files changed, 246 insertions(+) create mode 100644 gnucash/import-export/csv-exp/csv-export-helpers.cpp create mode 100644 gnucash/import-export/csv-exp/csv-export-helpers.hpp 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 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@gnu.org * +\********************************************************************/ +/** @file csv-export-helprs.cpp + @brief CSV Export helper functions + @author Christopher Lam +*/ +#include + +#include +#include +#include +#include + +#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@gnu.org * +\********************************************************************/ + +#ifndef CSV_EXPORT_HELPERS +#define CSV_EXPORT_HELPERS + +#include +#include +#include +#include + +using StringVec = std::vector; + +// 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@gnu.org * + ********************************************************************/ + +// #include "config.h" +#include "csv-export-helpers.hpp" +#include + +#include + +#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 From cb8cdd1b9930070cb9ddaf27661d14a62b25f7d1 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Sat, 1 Apr 2023 13:51:51 +0800 Subject: [PATCH 2/3] [csv-tree-export.cpp] convert to cpp, use fstream --- gnucash/import-export/csv-exp/CMakeLists.txt | 2 +- .../import-export/csv-exp/csv-tree-export.c | 265 ------------------ .../import-export/csv-exp/csv-tree-export.cpp | 112 ++++++++ .../import-export/csv-exp/csv-tree-export.h | 8 + po/POTFILES.in | 2 +- 5 files changed, 122 insertions(+), 267 deletions(-) delete mode 100644 gnucash/import-export/csv-exp/csv-tree-export.c create mode 100644 gnucash/import-export/csv-exp/csv-tree-export.cpp 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@gnu.org * -\********************************************************************/ -/** @file csv-tree-export.c - @brief CSV Export Account Tree - @author Copyright (c) 2012 Robert Fewell -*/ -#include - -#include -#include -#include - -#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@gnu.org * +\********************************************************************/ +/** @file csv-tree-export.c + @brief CSV Export Account Tree + @author Copyright (c) 2012 Robert Fewell +*/ +#include + +#include +#include +#include +#include +#include + +#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(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 From db085ec457c7439c18bf226ce91b11ca74d2f9cf Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Mon, 3 Apr 2023 10:47:57 +0800 Subject: [PATCH 3/3] [csv-transactions-export.cpp] convert to cpp, use fstream - using std::ofstream --- gnucash/import-export/csv-exp/CMakeLists.txt | 2 +- .../csv-exp/csv-transactions-export.c | 592 ------------------ .../csv-exp/csv-transactions-export.cpp | 431 +++++++++++++ .../csv-exp/csv-transactions-export.h | 8 + po/POTFILES.in | 2 +- 5 files changed, 441 insertions(+), 594 deletions(-) delete mode 100644 gnucash/import-export/csv-exp/csv-transactions-export.c create mode 100644 gnucash/import-export/csv-exp/csv-transactions-export.cpp 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@gnu.org * -\********************************************************************/ -/** @file csv-transactions-export.c - @brief CSV Export Transactions - @author Copyright (c) 2012 Robert Fewell -*/ -#include "config.h" - -#include -#include - -#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@gnu.org * +\********************************************************************/ +/** @file csv-transactions-export.c + @brief CSV Export Transactions + @author Copyright (c) 2012 Robert Fewell +*/ +#include "config.h" + +#include +#include + +#include +#include + +#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; + +/******************************************************* + * 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(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(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(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