2006-04-07 13:49:12 -05:00
|
|
|
/********************************************************************\
|
|
|
|
* qofutil.c -- QOF utility functions *
|
|
|
|
* Copyright (C) 1997 Robin D. Clark *
|
|
|
|
* Copyright (C) 1997-2001,2004 Linas Vepstas <linas@linas.org> *
|
|
|
|
* Copyright 2006 Neil Williams <linux@codehelp.co.uk> *
|
|
|
|
* *
|
|
|
|
* 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 *
|
|
|
|
* *
|
|
|
|
* Author: Rob Clark (rclark@cs.hmc.edu) *
|
|
|
|
* Author: Linas Vepstas (linas@linas.org) *
|
|
|
|
\********************************************************************/
|
|
|
|
|
2017-10-26 04:14:21 -05:00
|
|
|
#include <config.h>
|
2006-04-07 13:49:12 -05:00
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "qof.h"
|
2016-11-27 16:12:43 -06:00
|
|
|
#include "qof-backend.hpp"
|
2006-04-07 13:49:12 -05:00
|
|
|
|
2012-05-26 18:47:34 -05:00
|
|
|
G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_UTIL;
|
2006-04-07 13:49:12 -05:00
|
|
|
|
Bug #638225: Sort when saving as XML
Patch by Jim Radford (with code beautified and re-indented by myself):
The attached patches sort the slots, lots, book accounts, bill terms,
customers, employees, entries, invoices, jobs, orders, tax tables and
vendors before saving them to the GnuCash XML file.
This is an attempt to make saves more idempotent thereby facilitating
the use of a revision control system on the GnuCash XML files.
With these patches most of the needless and seemingly random churn is
gone and I can add or remove a transaction and expect
there to be no unrelated changes to the GnuCash file.
I've been using and refining this patches for the last few years, so it has
received quite a bit of testing.
David Fraser adds: Without specific testing, I'm using this on an average-sized gnucash file
(5.7MB) without noticing any particular slowdown in saving, but a wonderful
reduction in diffs when comparing changes.
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@20067 57a11ea4-9604-0410-9ed3-97b8803252fd
2011-01-11 14:40:19 -06:00
|
|
|
void
|
|
|
|
g_hash_table_foreach_sorted(GHashTable *hash_table, GHFunc func, gpointer user_data, GCompareFunc compare_func)
|
|
|
|
{
|
|
|
|
GList *iter;
|
|
|
|
GList *keys = g_list_sort(g_hash_table_get_keys(hash_table), compare_func);
|
|
|
|
|
|
|
|
for (iter = keys; iter; iter = iter->next)
|
|
|
|
{
|
|
|
|
func(iter->data, g_hash_table_lookup(hash_table, iter->data), user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_list_free(keys);
|
|
|
|
}
|
|
|
|
|
2007-10-02 20:18:13 -05:00
|
|
|
gboolean
|
|
|
|
qof_utf8_substr_nocase (const gchar *haystack, const gchar *needle)
|
2006-04-07 13:49:12 -05:00
|
|
|
{
|
2007-10-02 20:18:13 -05:00
|
|
|
gchar *haystack_casefold, *haystack_normalized;
|
|
|
|
gchar *needle_casefold, *needle_normalized;
|
|
|
|
gchar *p;
|
|
|
|
|
|
|
|
g_return_val_if_fail (haystack && needle, FALSE);
|
|
|
|
|
|
|
|
haystack_casefold = g_utf8_casefold (haystack, -1);
|
|
|
|
haystack_normalized = g_utf8_normalize (haystack_casefold, -1,
|
|
|
|
G_NORMALIZE_ALL);
|
|
|
|
g_free (haystack_casefold);
|
|
|
|
|
|
|
|
needle_casefold = g_utf8_casefold (needle, -1);
|
|
|
|
needle_normalized = g_utf8_normalize (needle_casefold, -1, G_NORMALIZE_ALL);
|
|
|
|
g_free (needle_casefold);
|
|
|
|
|
|
|
|
p = strstr (haystack_normalized, needle_normalized);
|
|
|
|
g_free (haystack_normalized);
|
|
|
|
g_free (needle_normalized);
|
|
|
|
|
|
|
|
return p != NULL;
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
|
|
|
|
2011-04-04 04:25:10 -05:00
|
|
|
/** Use g_utf8_casefold and g_utf8_collate to compare two utf8 strings,
|
|
|
|
* ignore case. Return < 0 if da compares before db, 0 if they compare
|
|
|
|
* equal, > 0 if da compares after db. */
|
|
|
|
static gint
|
2007-10-02 20:18:13 -05:00
|
|
|
qof_utf8_strcasecmp (const gchar *da, const gchar *db)
|
2006-04-07 13:49:12 -05:00
|
|
|
{
|
2007-10-02 20:18:13 -05:00
|
|
|
gchar *da_casefold, *db_casefold;
|
|
|
|
gint retval;
|
|
|
|
|
|
|
|
g_return_val_if_fail (da != NULL, 0);
|
|
|
|
g_return_val_if_fail (db != NULL, 0);
|
|
|
|
|
|
|
|
da_casefold = g_utf8_casefold (da, -1);
|
|
|
|
db_casefold = g_utf8_casefold (db, -1);
|
|
|
|
retval = g_utf8_collate (da_casefold, db_casefold);
|
|
|
|
g_free (da_casefold);
|
|
|
|
g_free (db_casefold);
|
|
|
|
|
|
|
|
return retval;
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
gint
|
2006-04-07 13:49:12 -05:00
|
|
|
safe_strcasecmp (const gchar * da, const gchar * db)
|
|
|
|
{
|
2009-09-18 14:40:57 -05:00
|
|
|
if ((da) && (db))
|
|
|
|
{
|
|
|
|
if ((da) != (db))
|
|
|
|
{
|
|
|
|
gint retval = qof_utf8_strcasecmp ((da), (db));
|
|
|
|
/* if strings differ, return */
|
|
|
|
if (retval) return retval;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ((!(da)) && (db))
|
|
|
|
{
|
|
|
|
return -1;
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
2009-09-18 14:40:57 -05:00
|
|
|
else if ((da) && (!(db)))
|
|
|
|
{
|
|
|
|
return +1;
|
|
|
|
}
|
|
|
|
return 0;
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
gint
|
2006-04-07 13:49:12 -05:00
|
|
|
null_strcmp (const gchar * da, const gchar * db)
|
|
|
|
{
|
2009-09-18 14:40:57 -05:00
|
|
|
if (da && db) return strcmp (da, db);
|
|
|
|
if (!da && db && 0 == db[0]) return 0;
|
|
|
|
if (!db && da && 0 == da[0]) return 0;
|
|
|
|
if (!da && db) return -1;
|
|
|
|
if (da && !db) return +1;
|
|
|
|
return 0;
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_DIGITS 50
|
|
|
|
|
|
|
|
/* inverse of strtoul */
|
|
|
|
gchar *
|
|
|
|
ultostr (gulong val, gint base)
|
|
|
|
{
|
2009-09-18 14:40:57 -05:00
|
|
|
gchar buf[MAX_DIGITS];
|
|
|
|
gulong broke[MAX_DIGITS];
|
|
|
|
gint i;
|
|
|
|
gulong places = 0, reval;
|
|
|
|
|
|
|
|
if ((2 > base) || (36 < base)) return NULL;
|
|
|
|
|
|
|
|
/* count digits */
|
|
|
|
places = 0;
|
|
|
|
for (i = 0; i < MAX_DIGITS; i++)
|
|
|
|
{
|
|
|
|
broke[i] = val;
|
|
|
|
places ++;
|
|
|
|
val /= base;
|
|
|
|
if (0 == val) break;
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
/* normalize */
|
|
|
|
reval = 0;
|
|
|
|
for (i = places - 2; i >= 0; i--)
|
|
|
|
{
|
|
|
|
reval += broke[i+1];
|
|
|
|
reval *= base;
|
|
|
|
broke[i] -= reval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* print */
|
|
|
|
for (i = 0; i < (gint)places; i++)
|
|
|
|
{
|
|
|
|
if (10 > broke[i])
|
|
|
|
{
|
|
|
|
buf[places-1-i] = 0x30 + broke[i]; /* ascii digit zero */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
buf[places-1-i] = 0x41 - 10 + broke[i]; /* ascii capital A */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buf[places] = 0x0;
|
|
|
|
|
|
|
|
return g_strdup (buf);
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/* =================================================================== */
|
|
|
|
/* returns TRUE if the string is a number, possibly with whitespace */
|
|
|
|
/* =================================================================== */
|
|
|
|
|
|
|
|
gboolean
|
2009-07-24 15:07:14 -05:00
|
|
|
gnc_strisnum(const gchar *s)
|
2006-04-07 13:49:12 -05:00
|
|
|
{
|
2009-09-18 14:40:57 -05:00
|
|
|
if (s == NULL) return FALSE;
|
|
|
|
if (*s == 0) return FALSE;
|
2006-04-07 13:49:12 -05:00
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
while (*s && isspace(*s))
|
|
|
|
s++;
|
2006-04-07 13:49:12 -05:00
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
if (*s == 0) return FALSE;
|
|
|
|
if (!isdigit(*s)) return FALSE;
|
2006-04-07 13:49:12 -05:00
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
while (*s && isdigit(*s))
|
|
|
|
s++;
|
2006-04-07 13:49:12 -05:00
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
if (*s == 0) return TRUE;
|
2006-04-07 13:49:12 -05:00
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
while (*s && isspace(*s))
|
|
|
|
s++;
|
2006-04-07 13:49:12 -05:00
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
if (*s == 0) return TRUE;
|
2006-04-07 13:49:12 -05:00
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
return FALSE;
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/* =================================================================== */
|
2009-09-18 14:40:57 -05:00
|
|
|
/* Return NULL if the field is whitespace (blank, tab, formfeed etc.)
|
2006-04-07 13:49:12 -05:00
|
|
|
* Else return pointer to first non-whitespace character. */
|
|
|
|
/* =================================================================== */
|
|
|
|
|
2012-05-26 18:47:34 -05:00
|
|
|
G_GNUC_UNUSED static const gchar *
|
2006-04-07 13:49:12 -05:00
|
|
|
qof_util_whitespace_filter (const gchar * val)
|
|
|
|
{
|
2009-09-18 14:40:57 -05:00
|
|
|
size_t len;
|
|
|
|
if (!val) return NULL;
|
2006-04-07 13:49:12 -05:00
|
|
|
|
2009-09-18 14:40:57 -05:00
|
|
|
len = strspn (val, "\a\b\t\n\v\f\r ");
|
|
|
|
if (0 == val[len]) return NULL;
|
|
|
|
return val + len;
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef THESE_CAN_BE_USEFUL_FOR_DEGUGGING
|
2009-09-18 14:40:57 -05:00
|
|
|
static guint g_str_hash_KEY(gconstpointer v)
|
|
|
|
{
|
2006-04-07 13:49:12 -05:00
|
|
|
return g_str_hash(v);
|
|
|
|
}
|
2009-09-18 14:40:57 -05:00
|
|
|
static guint g_str_hash_VAL(gconstpointer v)
|
|
|
|
{
|
2006-04-07 13:49:12 -05:00
|
|
|
return g_str_hash(v);
|
|
|
|
}
|
2009-09-18 14:40:57 -05:00
|
|
|
static gpointer g_strdup_VAL(gpointer v)
|
|
|
|
{
|
2006-04-07 13:49:12 -05:00
|
|
|
return g_strdup(v);
|
|
|
|
}
|
2009-09-18 14:40:57 -05:00
|
|
|
static gpointer g_strdup_KEY(gpointer v)
|
|
|
|
{
|
2006-04-07 13:49:12 -05:00
|
|
|
return g_strdup(v);
|
|
|
|
}
|
2009-09-18 14:40:57 -05:00
|
|
|
static void g_free_VAL(gpointer v)
|
|
|
|
{
|
2006-04-07 13:49:12 -05:00
|
|
|
return g_free(v);
|
|
|
|
}
|
2009-09-18 14:40:57 -05:00
|
|
|
static void g_free_KEY(gpointer v)
|
|
|
|
{
|
2006-04-07 13:49:12 -05:00
|
|
|
return g_free(v);
|
|
|
|
}
|
|
|
|
static gboolean qof_util_str_equal(gconstpointer v, gconstpointer v2)
|
|
|
|
{
|
|
|
|
return (v && v2) ? g_str_equal(v, v2) : FALSE;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void
|
|
|
|
qof_init (void)
|
|
|
|
{
|
2009-09-18 14:40:57 -05:00
|
|
|
qof_log_init();
|
2012-05-21 12:28:30 -05:00
|
|
|
qof_string_cache_init();
|
2009-09-18 14:40:57 -05:00
|
|
|
qof_object_initialize ();
|
|
|
|
qof_query_init ();
|
|
|
|
qof_book_register ();
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
qof_close(void)
|
|
|
|
{
|
2009-09-18 14:40:57 -05:00
|
|
|
qof_query_shutdown ();
|
|
|
|
qof_object_shutdown ();
|
2016-11-28 12:27:09 -06:00
|
|
|
QofBackend::release_backends();
|
2012-05-21 12:28:30 -05:00
|
|
|
qof_string_cache_destroy ();
|
2007-02-09 11:35:00 -06:00
|
|
|
qof_log_shutdown();
|
2006-04-07 13:49:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ************************ END OF FILE ***************************** */
|