Merge Brent McBride's 'gnc-glib-utils-cpp' into future.

This commit is contained in:
John Ralls
2026-07-18 13:49:37 -07:00
44 changed files with 533 additions and 324 deletions
+1 -1
View File
@@ -22,7 +22,7 @@
%{
#include <config.h>
#include <gnc-environment.h>
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
#include <gnc-prefs.h>
#include <gnc-path.h>
#include <gnc-filepath-utils.h>
+1 -1
View File
@@ -45,7 +45,7 @@
#include <libguile.h>
#include "swig-runtime.h"
#include "guile-mappings.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-guile-utils.h"
#include "glib-guile.h"
+1 -1
View File
@@ -36,7 +36,7 @@
#include "gnc-file.h"
#include "gnc-features.h"
#include "gnc-filepath-utils.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-gui-query.h"
#include "gnc-hooks.h"
#include "gnc-keyring.h"
+1 -1
View File
@@ -69,7 +69,7 @@
#include "gnc-state.h"
#include "gnc-ui.h"
#include "gnc-ui-util.h"
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
#include "gnc-uri.hpp"
#include "gnc-version.h"
#include "gnc-warnings.h"
+1 -1
View File
@@ -39,7 +39,7 @@
#include "gnc-commodity.h"
#include "gnc-component-manager.h"
#include "gnc-engine.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-gobject-utils.h"
#include "gnc-prefs.h"
#include "gnc-hooks.h"
@@ -35,7 +35,7 @@
#include "gnc-commodity.h"
#include "gnc-component-manager.h"
#include "gnc-engine.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-gnome-utils.h"
#include "gnc-icons.h"
#include "gnc-ui-util.h"
+1 -1
View File
@@ -37,7 +37,7 @@
#include "gnc-commodity.h"
#include "gnc-component-manager.h"
#include "gnc-engine.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-gobject-utils.h"
#include "gnc-hooks.h"
#include "gnc-session.h"
+1 -1
View File
@@ -35,7 +35,7 @@
#include "gnc-pricedb.h"
#include "gnc-component-manager.h"
#include "gnc-engine.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-gnome-utils.h"
#include "gnc-icons.h"
#include "gnc-ui-util.h"
+1 -1
View File
@@ -40,7 +40,7 @@
#include "gnc-tree-view.h"
#include "gnc-engine.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-gnome-utils.h"
#include "gnc-gobject-utils.h"
#include "gnc-cell-renderer-label.h"
+1 -1
View File
@@ -33,7 +33,7 @@
#include "gnc-ui.h"
#include "gnc-ui-util.h"
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
#include "Account.h"
#define DIALOG_IMAP_CM_CLASS "dialog-imap-edit"
+1 -1
View File
@@ -50,7 +50,7 @@
#include "gncOwner.h"
#include "gncInvoice.h"
#include "gncInvoiceP.h"
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
#include "gncEntryLedger.h"
+1 -1
View File
@@ -32,7 +32,7 @@
#include "gnc-ui.h"
#include "gnc-gui-query.h"
#include "gnc-ui-util.h"
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
#include "qof.h"
#include "gnc-date.h"
#include "gnc-date-edit.h"
+1 -1
View File
@@ -48,7 +48,7 @@
#include "gnc-ui.h"
#include "gnc-ui-util.h"
#include "gnc-warnings.h"
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
#define DIALOG_PRICE_DB_CM_CLASS "dialog-price-edit-db"
+1 -1
View File
@@ -64,7 +64,7 @@
#include "gnc-ui-util.h"
#include "gnucash-sheet.h"
#include "gnc-session.h"
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
#include "gnc-split-reg.h"
+1 -1
View File
@@ -43,7 +43,7 @@
#include "gnc-prefs.h"
#include "gnc-ui.h"
#include "gnc-ui-util.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "Query.h"
#include "qof.h"
#include "gnc-ledger-display.h"
@@ -73,7 +73,7 @@
#include "window-main-summarybar.h"
#include "dialog-object-references.h"
#include "dialog-find-account.h"
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
/* This static indicates the debugging module that this .o belongs to. */
static QofLogModule log_module = GNC_MOD_GUI;
@@ -37,7 +37,7 @@
#include "dialog-utils.h"
#include "gnc-date.h"
#include "gnc-date-edit.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-ui.h"
#include "gnc-state.h"
#include "gnc-period-select.h"
+1 -1
View File
@@ -65,7 +65,7 @@
#include "gnc-engine.h"
#include "gnc-event.h"
#include "gnc-features.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-gnome-utils.h"
#include "gnc-gobject-utils.h"
#include "gnc-gui-query.h"
+1 -1
View File
@@ -47,7 +47,7 @@
#include <sys/stat.h>
#include <errno.h>
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
#include "gfec.h"
#include "dialog-custom-report.h"
#include "dialog-utils.h"
+1 -1
View File
@@ -60,7 +60,7 @@
#include "gnc-dense-cal.h"
#include "gnc-engine.h"
#include "gnc-event.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-icons.h"
#include "gnc-main-window.h"
#include "gnc-plugin.h"
@@ -58,7 +58,7 @@
#include "gnc-ab-kvp.h"
#include "gnc-ab-utils.h"
#include "gnc-component-manager.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-ui.h"
#include "gnc-ui-util.h"
#include "gnc-session.h"
+1 -1
View File
@@ -43,7 +43,7 @@
#include "Transaction.h"
#include "dialog-ab-trans.h"
#include "gnc-ab-kvp.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-gwen-gui.h"
#include "gnc-prefs.h"
#include "gnc-ui.h"
@@ -36,7 +36,7 @@
#include <glib.h>
#include <glib/gstdio.h>
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-date.h"
#include "gnc-ui.h"
#include "gnc-ui-util.h"
@@ -30,7 +30,7 @@
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-ui-util.h"
#include <regex.h>
#include "Account.h"
@@ -34,7 +34,7 @@
#include <glib.h>
#include <glib/gstdio.h>
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-ui.h"
#include "gnc-ui-util.h"
#include "gnc-gui-query.h"
@@ -49,7 +49,7 @@
#include "Account.hpp"
#include "dialog-transfer.h"
#include "dialog-utils.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-ui.h"
#include "gnc-ui-util.h"
#include "gnc-engine.h"
+1 -1
View File
@@ -46,7 +46,7 @@
#include "gnc-file.h"
#include "gnc-engine.h"
#include "gnc-ui-util.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-prefs.h"
#include "gnc-ui.h"
#include "gnc-window.h"
@@ -37,7 +37,7 @@
#include "gnc-ledger-display.h"
#include "gnc-prefs.h"
#include "gnc-ui-util.h"
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
#include "split-register-control.h"
#include "split-register-model.h"
@@ -32,7 +32,7 @@
#include "gnc-prefs.h"
#include "gnc-ui.h"
#include "gnc-uri-utils.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-filepath-utils.h"
#include "gnc-warnings.h"
#include "doclinkcell.h"
@@ -47,7 +47,7 @@
#include "gnucash-sheetP.h"
#include "table-allgui.h"
#include "Account.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#define GNC_PREF_AUTO_RAISE_LISTS "auto-raise-lists"
@@ -43,7 +43,7 @@
#include "gnucash-sheet.h"
#include "gnucash-sheetP.h"
#include "table-allgui.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include <gnc-unicode.h>
typedef struct _PopBox
+1 -1
View File
@@ -46,7 +46,7 @@
#include "gnc-date.h"
#include "gnc-event.h"
#include "gnc-exp-parser.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-sx-instance-model.h"
#include "gnc-ui-util.h"
#include "qof.h"
+2 -2
View File
@@ -10,7 +10,7 @@ set (core_utils_SOURCES
gnc-environment.c
gnc-filepath-utils.cpp
gnc-gkeyfile-utils.c
gnc-glib-utils.c
gnc-string-utils.cpp
gnc-unicode.cpp
gnc-locale-utils.c
gnc-locale-utils.cpp
@@ -28,7 +28,7 @@ set(core_utils_noinst_HEADERS
gnc-environment.h
gnc-filepath-utils.h
gnc-gkeyfile-utils.h
gnc-glib-utils.h
gnc-string-utils.h
gnc-unicode.h
gnc-locale-utils.h
gnc-locale-utils.hpp
+53 -27
View File
@@ -1,6 +1,7 @@
/********************************************************************\
* gnc-locale-utils.cpp -- provide a default locale for C++ *
* Copyright (C) 2019 John Ralls <jralls@ceridwen.us *
* Copyright (C) 2026 Brent McBride <mcbridebt@hotmail.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
@@ -71,39 +72,54 @@ gnc_get_locale()
static std::locale boost_cached;
static bool tried_boost_already = false;
static bool boost_locale_created = false;
static bool boost_messages_loaded = false;
/* Build a boost::locale from the environment. Message catalogs are loaded
* only when a path is supplied; the collation, character-set and other
* facets are always present so that gnc_get_boost_locale() is usable even
* before translations have been set up. */
static std::locale
create_boost_locale (const std::string& messages_path)
{
try
{
boost::locale::generator gen;
if (!messages_path.empty())
gen.add_messages_path(messages_path);
gen.add_messages_domain(PROJECT_NAME);
return gen ("");
}
catch (const std::runtime_error& err)
{
const char* locale = setlocale(LC_ALL, "");
g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"Failed to create C++ default locale from "
"%s because %s. Using the 'C' locale for C++.",
locale, err.what());
return std::locale::classic();
}
}
void
gnc_init_boost_locale (const std::string& messages_path)
{
if (!tried_boost_already)
{
tried_boost_already = true;
/* Once message catalogs are loaded there is nothing more to do; a locale
* that was created lazily (without catalogs) is still replaced here. */
if (boost_messages_loaded)
return;
try
{
boost::locale::generator gen;
if (!messages_path.empty())
gen.add_messages_path(messages_path);
else
g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"Attempt to initialize boost_locale without a message_path. "
"If message catalogs are not installed in the system's default locations "
"user interface strings will not be translated.");
gen.add_messages_domain(PROJECT_NAME);
boost_cached = gen ("");
}
catch (const std::runtime_error& err)
{
const char* locale = setlocale(LC_ALL, "");
if (messages_path.empty())
g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"Attempt to initialize boost_locale without a message_path. "
"If message catalogs are not installed in the system's default locations "
"user interface strings will not be translated.");
g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"Failed to create C++ default locale from"
"%s because %s. Using the 'C' locale for C++.",
locale, err.what());
boost_cached = std::locale::classic();
}
}
boost_cached = create_boost_locale (messages_path);
boost_locale_created = true;
if (!messages_path.empty())
boost_messages_loaded = true;
}
@@ -111,6 +127,16 @@ gnc_init_boost_locale (const std::string& messages_path)
const std::locale&
gnc_get_boost_locale()
{
if (!boost_locale_created)
{
/* A consumer needs boost::locale facets before the application
* initialized them -- e.g. the Python bindings, a unit test, or
* engine code such as xaccAccountOrder(). Create a usable locale
* now; message catalogs, which require a path, are installed later
* by gnc_init_boost_locale(). */
boost_cached = create_boost_locale ("");
boost_locale_created = true;
}
return boost_cached;
}
@@ -1,6 +1,7 @@
/********************************************************************\
* gnc-glib-utils.c -- utility functions based on glib functions *
* gnc-string-utils.cpp -- string and list utility functions *
* Copyright (C) 2006 David Hampton <hampton@employees.org> *
* Copyright (C) 2026 Brent McBride <mcbridebt@hotmail.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
@@ -22,13 +23,17 @@
\********************************************************************/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdbool.h>
#include "gnc-glib-utils.h"
#include <cstring>
#include <string_view>
#include <boost/locale/collator.hpp>
#include <boost/locale/conversion.hpp>
#include <boost/locale/encoding.hpp>
#include <boost/locale/info.hpp>
#include "gnc-locale-utils.hpp"
#include "gnc-string-utils.h"
#ifdef G_OS_WIN32
#include <windows.h>
@@ -38,12 +43,16 @@ int
safe_utf8_collate (const char * da, const char * db)
{
if (da && !(*da))
da = NULL;
da = nullptr;
if (db && !(*db))
db = NULL;
db = nullptr;
if (da && db)
return g_utf8_collate(da, db);
{
auto const& coll{std::use_facet<boost::locale::collator<char>>(
gnc_get_boost_locale())};
return coll.compare(boost::locale::collate_level::quaternary, da, db);
}
if (da)
return 1;
if (db)
@@ -126,20 +135,18 @@ gnc_utf8_validate(const gchar *str,
const gchar **end)
{
const gchar *p;
g_return_val_if_fail (str != NULL, FALSE);
g_return_val_if_fail (str != nullptr, false);
if (end)
*end = str;
p = str;
const gchar *p = str;
while ((max_len < 0 || (p - str) < max_len) && *p)
{
int i, mask = 0, len;
gunichar result;
unsigned char c = (unsigned char) * p;
unsigned char c = static_cast<unsigned char>(*p);
UTF8_COMPUTE (c, mask, len);
@@ -156,7 +163,7 @@ gnc_utf8_validate(const gchar *str,
if (UTF8_LENGTH (result) != len) /* Check for overlong UTF-8 */
break;
if (result == (gunichar) - 1)
if (result == static_cast<gunichar>(-1))
break;
if (!UNICODE_VALID (result))
@@ -173,32 +180,31 @@ gnc_utf8_validate(const gchar *str,
*/
if (max_len >= 0 &&
p != (str + max_len))
return FALSE;
return false;
else if (max_len < 0 &&
*p != '\0')
return FALSE;
return false;
else
return TRUE;
return true;
}
void
gnc_utf8_strip_invalid (gchar *str)
{
gchar *end;
gint len;
g_return_if_fail(str);
if (gnc_utf8_validate(str, -1, (const gchar **)&end))
if (gnc_utf8_validate(str, -1, const_cast<const gchar **>(&end)))
return;
g_warning("Invalid utf8 string: %s", str);
do
{
len = strlen(end);
int len = strlen(end);
memmove(end, end + 1, len); /* shuffle the remainder one byte */
}
while (!gnc_utf8_validate(str, -1, (const gchar **)&end));
while (!gnc_utf8_validate(str, -1, const_cast<const gchar **>(&end)));
}
gchar *
@@ -212,14 +218,13 @@ gnc_utf8_strip_invalid_strdup(const gchar* str)
void
gnc_utf8_strip_invalid_and_controls (gchar *str)
{
gchar *c = NULL;
const gchar *controls = "\b\f\n\r\t\v";
g_return_if_fail (str != NULL && strlen (str) > 0);
const char *controls = "\b\f\n\r\t\v";
g_return_if_fail (str != nullptr && strlen (str) > 0);
gnc_utf8_strip_invalid (str); /* First fix the UTF-8 */
for(c = str + strlen (str) - 1; c != str; --c)
for (gchar *c = str + strlen (str) - 1; c != str; --c)
{
gboolean line_control = ((unsigned char)(*c) < 0x20);
if (line_control || strchr(controls, *c) != NULL)
bool line_control = (static_cast<unsigned char>(*c) < 0x20);
if (line_control || strchr(controls, *c) != nullptr)
*c = ' '; /*replace controls with a single space. */
}
}
@@ -227,44 +232,54 @@ gnc_utf8_strip_invalid_and_controls (gchar *str)
gchar *
gnc_locale_from_utf8(const gchar* str)
{
gchar * locale_str;
gsize bytes_written = 0;
GError * err = NULL;
g_return_val_if_fail (str != nullptr, nullptr);
/* Convert from UTF-8 to the encoding used in the current locale. */
locale_str = g_locale_from_utf8(str, -1, NULL, &bytes_written, &err);
if (err)
// Convert from UTF-8 to the encoding used in the current locale.
auto const& info{std::use_facet<boost::locale::info> (
gnc_get_boost_locale ())};
if (info.utf8 ())
return g_strdup (str);
try
{
g_warning("g_locale_from_utf8 failed: %s", err->message);
g_error_free(err);
auto locale_str = boost::locale::conv::from_utf<char> (
str, info.encoding (), boost::locale::conv::stop);
return g_strdup (locale_str.c_str ());
}
catch (const std::exception& err)
{
g_warning ("gnc_locale_from_utf8 failed: %s", err.what ());
return nullptr;
}
return locale_str;
}
gchar *
gnc_locale_to_utf8(const gchar* str)
{
gchar * utf8_str;
gsize bytes_written = 0;
GError * err = NULL;
g_return_val_if_fail (str != nullptr, nullptr);
/* Convert to UTF-8 from the encoding used in the current locale. */
utf8_str = g_locale_to_utf8(str, -1, NULL, &bytes_written, &err);
if (err)
// Convert to UTF-8 from the encoding used in the current locale.
auto const& info{std::use_facet<boost::locale::info> (
gnc_get_boost_locale ())};
if (info.utf8 ())
return g_strdup (str);
try
{
g_warning("g_locale_to_utf8 failed: %s", err->message);
g_error_free(err);
auto utf8_str = boost::locale::conv::to_utf<char> (
str, info.encoding (), boost::locale::conv::stop);
return g_strdup (utf8_str.c_str ());
}
catch (const std::exception& err)
{
g_warning ("gnc_locale_to_utf8 failed: %s", err.what ());
return nullptr;
}
return utf8_str;
}
GList*
gnc_g_list_map(GList* list, GncGMapFunc fn, gpointer user_data)
{
GList *rtn = NULL;
for (; list != NULL; list = list->next)
GList *rtn = nullptr;
for (; list != nullptr; list = list->next)
{
rtn = g_list_prepend (rtn, (*fn)(list->data, user_data));
}
@@ -274,58 +289,54 @@ gnc_g_list_map(GList* list, GncGMapFunc fn, gpointer user_data)
void
gnc_g_list_cut(GList **list, GList *cut_point)
{
if (list == NULL || *list == NULL)
if (list == nullptr || *list == nullptr)
return;
// if it's the first element.
if (cut_point->prev == NULL)
if (cut_point->prev == nullptr)
{
*list = NULL;
*list = nullptr;
return;
}
cut_point->prev->next = NULL;
cut_point->prev = NULL;
cut_point->prev->next = nullptr;
cut_point->prev = nullptr;
}
static bool
utf8_strstr(char **needle, char *haystack)
{
char *tmp = g_utf8_normalize (*needle, -1, G_NORMALIZE_NFC);
if (haystack && *haystack)
{
char *place = strstr(haystack, tmp);
if (place)
{
g_free (tmp);
return false;
}
}
*needle = tmp; //so that haystack is already normalized
auto tmp{boost::locale::normalize (*needle, boost::locale::norm_nfc,
gnc_get_boost_locale ())};
if (haystack && *haystack &&
std::string_view{haystack}.find (tmp) != std::string_view::npos)
return false;
*needle = g_strdup (tmp.c_str ()); //so that haystack is already normalized
return true;
}
static gchar *
gnc_g_list_stringjoin_internal (GList *list_of_strings, const gchar *sep, bool testdups)
stringjoin_internal (GList *list_of_strings, const gchar *sep, bool testdups)
{
gint seplen = sep ? strlen(sep) : 0;
gint seplen = sep ? strlen (sep) : 0;
gint length = -seplen;
gchar *retval, *p;
for (GList *n = list_of_strings; n; n = n->next)
{
gchar *str = n->data;
gchar *str = static_cast<gchar*>(n->data);
if (str && *str)
length += strlen (str) + seplen;
}
if (length <= 0)
return NULL;
return nullptr;
p = retval = (gchar*) g_malloc0 (length * sizeof (gchar) + 1);
p = retval = static_cast<gchar*>(g_malloc0 (length * sizeof (gchar) + 1));
for (GList *n = list_of_strings; n; n = n->next)
{
gchar *str = n->data;
gchar *str = static_cast<gchar*>(n->data);
if (!str || !str[0])
continue;
if (!testdups || utf8_strstr (&str, retval))
@@ -344,19 +355,20 @@ gnc_g_list_stringjoin_internal (GList *list_of_strings, const gchar *sep, bool t
gchar *
gnc_g_list_stringjoin (GList *list_of_strings, const gchar *sep)
{
return gnc_g_list_stringjoin_internal (list_of_strings, sep, false);
return stringjoin_internal (list_of_strings, sep, false);
}
gchar *
gnc_g_list_stringjoin_nodups (GList *list_of_strings, const gchar *sep)
{
return gnc_g_list_stringjoin_internal (list_of_strings, sep, true);
return stringjoin_internal (list_of_strings, sep, true);
}
gint
gnc_list_length_cmp (const GList *list, size_t len)
{
for (GList *lst = (GList*) list;; lst = g_list_next (lst), len--)
for (GList *lst = const_cast<GList*>(list);;
lst = g_list_next (lst), len--)
{
if (!lst) return (len ? -1 : 0);
if (!len) return 1;
@@ -1,6 +1,7 @@
/********************************************************************\
* gnc-glib-utils.c -- utility functions based on glib functions *
* gnc-string-utils.h -- string and list utility functions *
* Copyright (C) 2006 David Hampton <hampton@employees.org> *
* Copyright (C) 2026 Brent McBride <mcbridebt@hotmail.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
@@ -23,19 +24,18 @@
/** @addtogroup GLib
@{ */
/** @addtogroup Helpers GLib Helpers
/** @addtogroup Helpers String Helpers
The API in this file is designed to provide support functions that
wrap the base glib functions and make them easier to use.
The API in this file provides string and list utility functions.
@{ */
/** @file gnc-glib-utils.h
* @brief GLib helper routines
/** @file gnc-string-utils.h
* @brief String and list utility routines
* @author Copyright (C) 2006 David Hampton <hampton@employees.org>
*/
#ifndef GNC_GLIB_UTILS_H
#define GNC_GLIB_UTILS_H
#ifndef GNC_STRING_UTILS_H
#define GNC_STRING_UTILS_H
#include <glib.h>
@@ -216,6 +216,6 @@ gint gnc_list_length_cmp (const GList *list, size_t len);
} /* extern "C" */
#endif
#endif /* GNC_GLIB_UTILS_H */
#endif /* GNC_STRING_UTILS_H */
/** @} */
/** @} */
+10 -2
View File
@@ -15,7 +15,15 @@ macro(add_core_utils_test _TARGET _SOURCE_FILES)
gnc_add_test(${_TARGET} "${_SOURCE_FILES}" CORE_UTILS_TEST_INCLUDE_DIRS CORE_UTILS_TEST_LIBS)
endmacro()
add_core_utils_test(test-gnc-glib-utils test-gnc-glib-utils.c)
set(gtest_string_utils_INCLUDES
${CMAKE_BINARY_DIR}/common # for config.h
${MODULEPATH}
${CMAKE_SOURCE_DIR}/common/test-core)
set(gtest_string_utils_LIBS gnc-core-utils test-core gtest)
gnc_add_test(test-gnc-string-utils gtest-gnc-string-utils.cpp
gtest_string_utils_INCLUDES gtest_string_utils_LIBS)
add_core_utils_test(test-resolve-file-path test-resolve-file-path.c)
add_core_utils_test(test-userdata-dir test-userdata-dir.c)
if (NOT MAC_INTEGRATION AND NOT WIN32)
@@ -67,5 +75,5 @@ gnc_add_test(test-gnc-unicode
gtest_icu_locale_LIBS)
set_dist_list(test_core_utils_DIST CMakeLists.txt
test-gnc-glib-utils.c test-resolve-file-path.c test-userdata-dir.c
gtest-gnc-string-utils.cpp test-resolve-file-path.c test-userdata-dir.c
test-userdata-dir-invalid-home.c gtest-gnc-unicode.cpp gtest-path-utilities.cpp)
@@ -0,0 +1,334 @@
/********************************************************************
* gtest-gnc-string-utils.cpp: Unit tests for gnc-string-utils. *
* Copyright 2011 John Ralls <jralls@ceridwen.us> *
* Copyright 2026 Brent McBride <mcbridebt@hotmail.com> *
* *
* 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 *
\********************************************************************/
#include <config.h>
#include <cstring>
#include <glib.h>
#include <gnc-string-utils.h>
#include <gnc-locale-utils.hpp>
#include <unittest-support.h>
#include <gtest/gtest.h>
/* safe_utf8_collate compares with boost::locale::collator via
* gnc_get_boost_locale(), which returns a usable locale only after
* gnc_init_boost_locale() has run (done in main() for the application).
* Initialize it once for the whole test run.
*
* Pin the encoding to US-ASCII (LC_ALL=C) so that gnc_locale_from_utf8 and
* gnc_locale_to_utf8 exercise their conversion (and failure) path rather
* than the UTF-8 no-op short-circuit, deterministically on any host. */
class BoostLocaleEnvironment : public ::testing::Environment
{
public:
void SetUp () override
{
g_setenv ("LC_ALL", "C", TRUE);
gnc_init_boost_locale ("");
}
};
static auto* const boost_locale_env =
::testing::AddGlobalTestEnvironment (new BoostLocaleEnvironment);
TEST(GncGlibUtils, safe_utf8_collate)
{
EXPECT_EQ (0, safe_utf8_collate ("abc", "abc"));
EXPECT_LT (safe_utf8_collate ("abc", "abd"), 0);
EXPECT_GT (safe_utf8_collate ("abd", "abc"), 0);
/* Empty strings are treated the same as nullptr. */
EXPECT_EQ (0, safe_utf8_collate ("", ""));
EXPECT_EQ (0, safe_utf8_collate (nullptr, nullptr));
EXPECT_EQ (0, safe_utf8_collate ("", nullptr));
EXPECT_EQ (0, safe_utf8_collate (nullptr, ""));
/* When only one side is empty/nullptr the non-empty side sorts later. */
EXPECT_EQ (1, safe_utf8_collate ("abc", nullptr));
EXPECT_EQ (1, safe_utf8_collate ("abc", ""));
EXPECT_EQ (-1, safe_utf8_collate (nullptr, "abc"));
EXPECT_EQ (-1, safe_utf8_collate ("", "abc"));
}
TEST(GncGlibUtils, gnc_utf8_validate)
{
const gchar *valid = "Hello, world";
const gchar *end = nullptr;
EXPECT_TRUE (gnc_utf8_validate (valid, -1, &end));
EXPECT_EQ (valid + strlen (valid), end);
/* max_len path: validate only a prefix of the string. */
EXPECT_TRUE (gnc_utf8_validate (valid, 5, &end));
EXPECT_EQ (valid + 5, end);
/* Invalid input: end is left pointing at the first bad byte. */
const gchar *invalid = "abc\xb2\xf3xyz";
EXPECT_FALSE (gnc_utf8_validate (invalid, -1, &end));
EXPECT_EQ (invalid + 3, end);
/* A multi-byte character (here "é" == 0xc3 0xa9) validates when whole,
* but fails when max_len splits it, with end left at the split point. */
const gchar *multibyte = "a\xc3\xa9";
EXPECT_TRUE (gnc_utf8_validate (multibyte, 3, &end));
EXPECT_EQ (multibyte + 3, end);
EXPECT_FALSE (gnc_utf8_validate (multibyte, 2, &end));
EXPECT_EQ (multibyte + 1, end);
/* A well-formed 6-byte lead byte (0xFC) followed by an invalid
* continuation byte decodes to (gunichar)-1 while UTF8_LENGTH still
* matches, exercising the result == -1 rejection rather than the
* overlong-length check. */
const gchar *bad_seq = "\xFC\x20";
EXPECT_FALSE (gnc_utf8_validate (bad_seq, -1, &end));
EXPECT_EQ (bad_seq, end);
}
TEST(GncGlibUtils, gnc_utf8_strip_invalid_strdup)
{
/* Valid input yields an equal, independently-allocated copy. */
gchar *result = gnc_utf8_strip_invalid_strdup ("valid string");
EXPECT_STREQ ("valid string", result);
g_free (result);
}
static void
check_strip_invalid_and_controls (const gchar *input)
{
gchar *str = g_strdup (input);
const gchar *controls = "\b\f\n\r\t\v\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\xa\xb\xc\xd\xe\xf\x10\x11\x12\x13\x14\x15\x16"
"\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f";
gchar *msg = g_strdup_printf ("Invalid utf8 string: %s", input);
const GLogLevelFlags level = static_cast<GLogLevelFlags>
(G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL);
TestErrorStruct check = {level, nullptr, msg, 0};
guint handler = g_log_set_handler (nullptr, level,
(GLogFunc)test_null_handler, &check);
gnc_utf8_strip_invalid_and_controls (str);
EXPECT_TRUE (g_utf8_validate (str, -1, nullptr));
EXPECT_EQ (nullptr, strpbrk (str, controls));
EXPECT_GT (g_utf8_strlen (str, -1), 0);
g_log_remove_handler (nullptr, handler);
g_free (str);
g_free (msg);
}
TEST(GncGlibUtils, gnc_utf8_strip_invalid_and_controls)
{
check_strip_invalid_and_controls
("Η γρήγορη καφέ αλεπού πήδηξε πάνω από την \xb2\xf3ργή σκύλο.");
check_strip_invalid_and_controls
("Η γρήγορη καφέ αλεπού\bπήδηξε\nπάνω από\tτην αργή σκύλο.");
}
TEST(GncGlibUtils, gnc_locale_utf8_roundtrip)
{
/* Plain ASCII round-trips through any locale encoding. */
const gchar *ascii = "Plain ASCII text 12345";
gchar *locale_str = gnc_locale_from_utf8 (ascii);
ASSERT_NE (nullptr, locale_str);
gchar *utf8_str = gnc_locale_to_utf8 (locale_str);
ASSERT_NE (nullptr, utf8_str);
EXPECT_STREQ (ascii, utf8_str);
g_free (locale_str);
g_free (utf8_str);
}
TEST(GncGlibUtils, gnc_locale_conversion_failure)
{
/* Input that cannot be converted makes boost::locale::conv throw with
* the stop method; the functions catch it, warn, and return nullptr.
* 0xff is invalid UTF-8 and non-ASCII, so both directions fail in any
* locale encoding. */
const gchar *invalid = "\xff\xfe";
const GLogLevelFlags level = static_cast<GLogLevelFlags>
(G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL);
TestErrorStruct check = {level, nullptr, nullptr, 0};
guint handler = g_log_set_handler (nullptr, level,
(GLogFunc)test_null_handler, &check);
EXPECT_EQ (nullptr, gnc_locale_from_utf8 (invalid));
EXPECT_EQ (nullptr, gnc_locale_to_utf8 (invalid));
g_log_remove_handler (nullptr, handler);
}
static gpointer
add_offset_map_fn (gpointer data, gpointer user_data)
{
return GINT_TO_POINTER (GPOINTER_TO_INT (data) +
GPOINTER_TO_INT (user_data));
}
TEST(GncGlibUtils, gnc_g_list_map)
{
GList *list = nullptr;
list = g_list_append (list, GINT_TO_POINTER (1));
list = g_list_append (list, GINT_TO_POINTER (2));
list = g_list_append (list, GINT_TO_POINTER (3));
GList *mapped = gnc_g_list_map (list, add_offset_map_fn,
GINT_TO_POINTER (10));
ASSERT_EQ (3u, g_list_length (mapped));
EXPECT_EQ (11, GPOINTER_TO_INT (g_list_nth_data (mapped, 0)));
EXPECT_EQ (12, GPOINTER_TO_INT (g_list_nth_data (mapped, 1)));
EXPECT_EQ (13, GPOINTER_TO_INT (g_list_nth_data (mapped, 2)));
g_list_free (list);
g_list_free (mapped);
}
TEST(GncGlibUtils, gnc_g_list_cut)
{
/* A null or empty list is left untouched. */
GList *empty = nullptr;
gnc_g_list_cut (&empty, nullptr);
EXPECT_EQ (nullptr, empty);
GList *list = nullptr;
list = g_list_append (list, GINT_TO_POINTER (1));
list = g_list_append (list, GINT_TO_POINTER (2));
list = g_list_append (list, GINT_TO_POINTER (3));
list = g_list_append (list, GINT_TO_POINTER (4));
/* Cut at the third element: head keeps [1, 2], tail becomes [3, 4]. */
GList *tail = g_list_nth (list, 2);
gnc_g_list_cut (&list, tail);
ASSERT_EQ (2u, g_list_length (list));
EXPECT_EQ (1, GPOINTER_TO_INT (g_list_nth_data (list, 0)));
EXPECT_EQ (2, GPOINTER_TO_INT (g_list_nth_data (list, 1)));
ASSERT_EQ (2u, g_list_length (tail));
EXPECT_EQ (3, GPOINTER_TO_INT (g_list_nth_data (tail, 0)));
EXPECT_EQ (4, GPOINTER_TO_INT (g_list_nth_data (tail, 1)));
g_list_free (list);
g_list_free (tail);
/* Cutting at the first element clears the caller's list pointer. */
GList *head = g_list_append (nullptr, GINT_TO_POINTER (1));
GList *single = head;
gnc_g_list_cut (&single, head);
EXPECT_EQ (nullptr, single);
g_list_free (head);
}
TEST(GncGlibUtils, gnc_g_list_stringjoin)
{
GList *test = nullptr;
gchar *ret;
EXPECT_EQ (nullptr, gnc_g_list_stringjoin (nullptr, nullptr));
EXPECT_EQ (nullptr, gnc_g_list_stringjoin (nullptr, ":"));
test = g_list_prepend (test, (gpointer)"one");
ret = gnc_g_list_stringjoin (test, nullptr);
EXPECT_STREQ ("one", ret);
g_free (ret);
ret = gnc_g_list_stringjoin (test, "");
EXPECT_STREQ ("one", ret);
g_free (ret);
ret = gnc_g_list_stringjoin (test, ":");
EXPECT_STREQ ("one", ret);
g_free (ret);
/* The following inserts a nullptr between "two" and "one". As a
result, the stringjoin effectively skips a step, i.e. it does
not insert separator repeatedly between NULL strings */
test = g_list_prepend (test, nullptr);
test = g_list_prepend (test, (gpointer)"two");
ret = gnc_g_list_stringjoin (test, nullptr);
EXPECT_STREQ ("twoone", ret);
g_free (ret);
ret = gnc_g_list_stringjoin (test, "");
EXPECT_STREQ ("twoone", ret);
g_free (ret);
ret = gnc_g_list_stringjoin (test, ":");
EXPECT_STREQ ("two:one", ret);
g_free (ret);
test = g_list_prepend (test, (gpointer)"three");
ret = gnc_g_list_stringjoin (test, nullptr);
EXPECT_STREQ ("threetwoone", ret);
g_free (ret);
ret = gnc_g_list_stringjoin (test, "");
EXPECT_STREQ ("threetwoone", ret);
g_free (ret);
ret = gnc_g_list_stringjoin (test, ":");
EXPECT_STREQ ("three:two:one", ret);
g_free (ret);
g_list_free (test);
}
TEST(GncGlibUtils, gnc_g_list_stringjoin_nodups)
{
GList *test = nullptr;
gchar *ret;
test = g_list_prepend (test, (gpointer)"one");
test = g_list_prepend (test, (gpointer)"two");
test = g_list_prepend (test, (gpointer)"two");
test = g_list_prepend (test, (gpointer)"three");
test = g_list_prepend (test, (gpointer)"one:two");
test = g_list_prepend (test, (gpointer)"four");
test = g_list_reverse (test);
ret = gnc_g_list_stringjoin_nodups (test, ":");
EXPECT_STREQ ("one:two:three:four", ret);
g_free (ret);
g_list_free (test);
}
TEST(GncGlibUtils, gnc_list_length_cmp)
{
GList *lst = nullptr;
EXPECT_EQ (0, gnc_list_length_cmp (lst, 0));
EXPECT_EQ (-1, gnc_list_length_cmp (lst, 1));
lst = g_list_prepend (lst, GINT_TO_POINTER (1));
EXPECT_EQ (1, gnc_list_length_cmp (lst, 0));
EXPECT_EQ (0, gnc_list_length_cmp (lst, 1));
EXPECT_EQ (-1, gnc_list_length_cmp (lst, 2));
lst = g_list_prepend (lst, GINT_TO_POINTER (2));
EXPECT_EQ (1, gnc_list_length_cmp (lst, 1));
EXPECT_EQ (0, gnc_list_length_cmp (lst, 2));
EXPECT_EQ (-1, gnc_list_length_cmp (lst, 3));
g_list_free (lst);
}
@@ -1,171 +0,0 @@
/********************************************************************
* testmain.c: GLib g_test test execution file. *
* Copyright 2011 John Ralls <jralls@ceridwen.us> *
* *
* 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 *
\********************************************************************/
#include <config.h>
#include <string.h>
#include <glib.h>
#include <gnc-glib-utils.h>
#include <unittest-support.h>
static void
test_gnc_utf8_strip_invalid_and_controls (gconstpointer data)
{
gchar *str = g_strdup (data);
const gchar *controls = "\b\f\n\r\t\v\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\xa\xb\xc\xd\xe\xf\x10\x11\x12\x13\x14\x15\x16"
"\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f";
char *msg1 = g_strdup_printf ("Invalid utf8 string: %s",
(const gchar*)data);
const GLogLevelFlags level = G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL;
TestErrorStruct check = {level, NULL, msg1, 0};
guint handler = g_log_set_handler (NULL, level,
(GLogFunc)test_null_handler, &check);
g_test_log_set_fatal_handler((GTestLogFatalFunc)test_checked_handler,
&check);
gnc_utf8_strip_invalid_and_controls (str);
g_assert_true (g_utf8_validate(str, -1, NULL) == TRUE);
g_assert_true (strpbrk(str, controls) == NULL);
g_assert_true (g_utf8_strlen(str, -1) > 0);
g_log_remove_handler (NULL, handler);
g_free (str);
g_free (msg1);
}
static void
test_g_list_stringjoin (gconstpointer data)
{
GList *test = NULL;
gchar *ret;
ret = gnc_g_list_stringjoin (NULL, NULL);
g_assert_true (ret == NULL);
ret = gnc_g_list_stringjoin (NULL, ":");
g_assert_true (ret == NULL);
test = g_list_prepend (test, "one");
ret = gnc_g_list_stringjoin (test, NULL);
g_assert_cmpstr (ret, ==, "one");
g_free (ret);
ret = gnc_g_list_stringjoin (test, "");
g_assert_cmpstr (ret, ==, "one");
g_free (ret);
ret = gnc_g_list_stringjoin (test, ":");
g_assert_cmpstr (ret, ==, "one");
g_free (ret);
/* The following inserts a NULL between "two" and "one". As a
result, the stringjoin effectively skips a step, i.e. it does
not insert separator repeatedly between NULL strings */
test = g_list_prepend (test, NULL);
test = g_list_prepend (test, "two");
ret = gnc_g_list_stringjoin (test, NULL);
g_assert_cmpstr (ret, ==, "twoone");
g_free (ret);
ret = gnc_g_list_stringjoin (test, "");
g_assert_cmpstr (ret, ==, "twoone");
g_free (ret);
ret = gnc_g_list_stringjoin (test, ":");
g_assert_cmpstr (ret, ==, "two:one");
g_free (ret);
test = g_list_prepend (test, "three");
ret = gnc_g_list_stringjoin (test, NULL);
g_assert_cmpstr (ret, ==, "threetwoone");
g_free (ret);
ret = gnc_g_list_stringjoin (test, "");
g_assert_cmpstr (ret, ==, "threetwoone");
g_free (ret);
ret = gnc_g_list_stringjoin (test, ":");
g_assert_cmpstr (ret, ==, "three:two:one");
g_free (ret);
g_list_free (test);
}
static void
test_g_list_stringjoin_nodups (gconstpointer data)
{
GList *test = NULL;
gchar *ret;
test = g_list_prepend (test, "one");
test = g_list_prepend (test, "two");
test = g_list_prepend (test, "two");
test = g_list_prepend (test, "three");
test = g_list_prepend (test, "one:two");
test = g_list_prepend (test, "four");
test = g_list_reverse (test);
ret = gnc_g_list_stringjoin_nodups (test, ":");
g_assert_cmpstr (ret, ==, "one:two:three:four");
g_free (ret);
}
static void
test_gnc_list_length (gconstpointer data)
{
GList *lst = NULL;
g_assert_true (gnc_list_length_cmp (lst, 0) == 0);
g_assert_true (gnc_list_length_cmp (lst, 1) == -1);
lst = g_list_prepend (lst, (gpointer)1);
g_assert_true (gnc_list_length_cmp (lst, 0) == 1);
g_assert_true (gnc_list_length_cmp (lst, 1) == 0);
g_assert_true (gnc_list_length_cmp (lst, 2) == -1);
lst = g_list_prepend (lst, (gpointer)2);
g_assert_true (gnc_list_length_cmp (lst, 1) == 1);
g_assert_true (gnc_list_length_cmp (lst, 2) == 0);
g_assert_true (gnc_list_length_cmp (lst, 3) == -1);
g_list_free (lst);
}
int
main (int argc, char *argv[])
{
const gchar *invalid_utf8 = "Η γρήγορη καφέ αλεπού πήδηξε πάνω από την \xb2\xf3ργή σκύλο.";
const gchar *controls = "Η γρήγορη καφέ αλεπού\bπήδηξε\nπάνω από\tτην αργή σκύλο.";
g_test_init (&argc, &argv, NULL); // initialize test program
g_test_add_data_func ("/core-utils/gnc_utf8_strip_invalid_and_controls invalid utf8", (gconstpointer)invalid_utf8, test_gnc_utf8_strip_invalid_and_controls);
g_test_add_data_func ("/core-utils/gnc_utf8_strip_invalid_and_controls control chars", (gconstpointer)controls, test_gnc_utf8_strip_invalid_and_controls);
g_test_add_data_func ("/core-utils/gnc_g_list_stringjoin", NULL, test_g_list_stringjoin);
g_test_add_data_func ("/core-utils/gnc_g_list_stringjoin_nodups", NULL, test_g_list_stringjoin_nodups);
g_test_add_data_func ("/core-utils/gnc_list_length", NULL, test_gnc_list_length);
return g_test_run();
}
+1 -1
View File
@@ -39,7 +39,7 @@
#include "Transaction.h"
#include "TransactionP.hpp"
#include "gnc-event.h"
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "gnc-lot.h"
#include "gnc-pricedb.h"
#include "qofevent.h"
+1 -1
View File
@@ -31,7 +31,7 @@
#include "gnc-date.h"
#include "Account.h"
#include <stdint.h>
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
#define LOG_MOD "gnc.engine.recurrence"
static QofLogModule log_module = LOG_MOD;
+1 -1
View File
@@ -22,7 +22,7 @@
**********************************************************************/
#include "gncIDSearch.h"
#include <gnc-glib-utils.h>
#include <gnc-string-utils.h>
typedef enum
{ UNDEFINED,
+1 -1
View File
@@ -28,7 +28,7 @@
#include <gnc-event.h>
#include <gnc-date.h>
/* Add specific headers for this class */
#include "gnc-glib-utils.h"
#include "gnc-string-utils.h"
#include "../Account.h"
#include "../AccountP.hpp"
#include "../Split.h"
+1 -1
View File
@@ -617,11 +617,11 @@ libgnucash/core-utils/binreloc.c
libgnucash/core-utils/gnc-environment.c
libgnucash/core-utils/gnc-filepath-utils.cpp
libgnucash/core-utils/gnc-gkeyfile-utils.c
libgnucash/core-utils/gnc-glib-utils.c
libgnucash/core-utils/gnc-locale-utils.c
libgnucash/core-utils/gnc-locale-utils.cpp
libgnucash/core-utils/gnc-path.c
libgnucash/core-utils/gnc-prefs.cpp
libgnucash/core-utils/gnc-string-utils.cpp
libgnucash/core-utils/gnc-unicode.cpp
libgnucash/core-utils/gnc-version.c
libgnucash/engine/Account.cpp