1997-11-01 01:39:32 +00:00
|
|
|
/********************************************************************\
|
|
|
|
|
* util.c -- utility functions that are used everywhere else for *
|
|
|
|
|
* xacc (X-Accountant) *
|
|
|
|
|
* Copyright (C) 1997 Robin D. Clark *
|
2000-05-14 20:54:20 +00:00
|
|
|
* Copyright (C) 1997-2000 Linas Vepstas <linas@linas.org> *
|
1997-11-01 01:39:32 +00:00
|
|
|
* *
|
|
|
|
|
* 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*
|
2000-04-24 21:12:41 +00:00
|
|
|
* along with this program; if not, contact: *
|
1997-11-01 01:39:32 +00:00
|
|
|
* *
|
2000-04-24 21:12:41 +00:00
|
|
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
|
|
|
|
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
|
|
|
|
|
* Boston, MA 02111-1307, USA gnu@gnu.org *
|
|
|
|
|
* *
|
|
|
|
|
* Author: Rob Clark (rclark@cs.hmc.edu) *
|
|
|
|
|
* Author: Linas Vepstas (linas@linas.org) *
|
1997-11-01 01:39:32 +00:00
|
|
|
\********************************************************************/
|
|
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <glib.h>
|
|
|
|
|
#include <limits.h>
|
|
|
|
|
#include <locale.h>
|
1999-11-22 05:11:30 +00:00
|
|
|
#include <math.h>
|
2000-08-31 07:16:55 +00:00
|
|
|
#include <stdlib.h>
|
1998-02-02 23:54:44 +00:00
|
|
|
#include <string.h>
|
2000-03-22 10:10:50 +00:00
|
|
|
|
1998-01-28 05:55:07 +00:00
|
|
|
#include "messages.h"
|
2000-01-17 21:39:42 +00:00
|
|
|
#include "gnc-common.h"
|
1997-11-01 01:39:32 +00:00
|
|
|
#include "util.h"
|
|
|
|
|
|
1999-01-30 21:41:50 +00:00
|
|
|
/* hack alert -- stpcpy prototype is missing, use -DGNU */
|
|
|
|
|
char * stpcpy (char *dest, const char *src);
|
|
|
|
|
|
1997-11-01 01:39:32 +00:00
|
|
|
/** GLOBALS *********************************************************/
|
2000-08-22 01:35:47 +00:00
|
|
|
gncLogLevel loglevel[MOD_LAST + 1] =
|
2000-06-05 05:51:39 +00:00
|
|
|
{
|
|
|
|
|
GNC_LOG_NOTHING, /* DUMMY */
|
|
|
|
|
GNC_LOG_WARNING, /* ENGINE */
|
|
|
|
|
GNC_LOG_WARNING, /* IO */
|
|
|
|
|
GNC_LOG_WARNING, /* REGISTER */
|
|
|
|
|
GNC_LOG_WARNING, /* LEDGER */
|
|
|
|
|
GNC_LOG_WARNING, /* HTML */
|
|
|
|
|
GNC_LOG_WARNING, /* GUI */
|
|
|
|
|
GNC_LOG_WARNING, /* SCRUB */
|
|
|
|
|
GNC_LOG_WARNING, /* GTK_REG */
|
|
|
|
|
GNC_LOG_WARNING, /* GUILE */
|
|
|
|
|
GNC_LOG_DEBUG, /* BACKEND */
|
|
|
|
|
GNC_LOG_WARNING, /* QUERY */
|
1998-12-06 07:43:14 +00:00
|
|
|
};
|
1997-11-22 03:41:59 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* This static indicates the debugging module that this .o belongs to. */
|
|
|
|
|
static short module = MOD_ENGINE;
|
|
|
|
|
|
|
|
|
|
|
2000-06-05 05:51:39 +00:00
|
|
|
/* Set the logging level of the given module. */
|
|
|
|
|
void
|
|
|
|
|
gnc_set_log_level(gncModuleType module, gncLogLevel level)
|
|
|
|
|
{
|
|
|
|
|
if ((module < 0) || (module > MOD_LAST))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
loglevel[module] = level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Set the logging level for all modules. */
|
|
|
|
|
void
|
|
|
|
|
gnc_set_log_level_global(gncLogLevel level)
|
|
|
|
|
{
|
|
|
|
|
gncModuleType module;
|
|
|
|
|
|
2000-08-22 01:35:47 +00:00
|
|
|
for (module = GNC_LOG_NOTHING; module <= MOD_LAST; module++)
|
2000-06-05 05:51:39 +00:00
|
|
|
loglevel[module] = level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2000-06-30 06:12:37 +00:00
|
|
|
/* xaccParseAmount configuration */
|
2000-08-21 10:26:57 +00:00
|
|
|
static gboolean auto_decimal_enabled = FALSE;
|
2000-08-27 08:25:53 +00:00
|
|
|
static int auto_decimal_places = 2; /* default, can be changed */
|
2000-06-30 06:12:37 +00:00
|
|
|
|
|
|
|
|
/* enable/disable the auto_decimal_enabled option */
|
|
|
|
|
void
|
2000-08-21 10:26:57 +00:00
|
|
|
gnc_set_auto_decimal_enabled(gboolean enabled)
|
2000-06-30 06:12:37 +00:00
|
|
|
{
|
2000-08-27 08:25:53 +00:00
|
|
|
auto_decimal_enabled = enabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* set the number of auto decimal places to use */
|
|
|
|
|
void
|
|
|
|
|
gnc_set_auto_decimal_places( int places )
|
|
|
|
|
{
|
|
|
|
|
auto_decimal_places = places;
|
2000-06-30 06:12:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2000-05-14 20:54:20 +00:00
|
|
|
/********************************************************************\
|
|
|
|
|
\********************************************************************/
|
|
|
|
|
/* prettify() cleans up subroutine names.
|
|
|
|
|
* AIX/xlC has the habit of printing signatures not names; clean this up.
|
|
|
|
|
* On other operating systems, truncate name to 30 chars.
|
|
|
|
|
* Note this routine is not thread safe. Note we wouldn't need this
|
|
|
|
|
* routine if AIX did something more reasonable. Hope thread safety
|
|
|
|
|
* doesn't poke us in eye.
|
|
|
|
|
*/
|
|
|
|
|
char *
|
|
|
|
|
prettify (const char *name)
|
|
|
|
|
{
|
|
|
|
|
static char bf[35];
|
|
|
|
|
char *p;
|
|
|
|
|
strncpy (bf, name, 29); bf[28] = 0;
|
|
|
|
|
p = strchr (bf, '(');
|
|
|
|
|
if (p)
|
|
|
|
|
{
|
|
|
|
|
*(p+1) = ')';
|
|
|
|
|
*(p+2) = 0x0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
strcpy (&bf[26], "...()");
|
|
|
|
|
}
|
|
|
|
|
return bf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
1997-11-01 01:39:32 +00:00
|
|
|
/********************************************************************\
|
|
|
|
|
* DEBUGGING MEMORY ALLOCATION STUFF *
|
|
|
|
|
\********************************************************************/
|
1997-11-30 02:39:58 +00:00
|
|
|
#if DEBUG_MEMORY
|
1998-08-05 05:57:41 +00:00
|
|
|
|
1999-01-19 08:29:46 +00:00
|
|
|
// #if defined (__NetBSD__) || defined(__FreeBSD__)
|
|
|
|
|
|
1999-04-13 06:16:26 +00:00
|
|
|
#ifndef HAVE_MALLOC_USABLE_SIZE
|
1999-01-19 08:29:46 +00:00
|
|
|
#define malloc_usable_size(ptr) 0
|
1998-08-05 05:57:41 +00:00
|
|
|
#endif
|
|
|
|
|
|
1997-11-01 01:39:32 +00:00
|
|
|
size_t core=0;
|
1998-08-05 05:57:41 +00:00
|
|
|
|
1997-11-01 01:39:32 +00:00
|
|
|
void
|
|
|
|
|
dfree( void *ptr )
|
2000-03-22 10:10:50 +00:00
|
|
|
{
|
1997-11-01 01:39:32 +00:00
|
|
|
core -= malloc_usable_size(ptr);
|
|
|
|
|
free(ptr);
|
2000-03-22 10:10:50 +00:00
|
|
|
}
|
1997-11-01 01:39:32 +00:00
|
|
|
|
|
|
|
|
void*
|
|
|
|
|
dmalloc( size_t size )
|
2000-03-22 10:10:50 +00:00
|
|
|
{
|
1997-11-01 01:39:32 +00:00
|
|
|
int i;
|
1997-11-22 03:41:59 +00:00
|
|
|
char *ptr;
|
|
|
|
|
ptr = (char *)malloc(size);
|
1997-11-01 01:39:32 +00:00
|
|
|
for( i=0; i<size; i++ )
|
|
|
|
|
ptr[i] = '.';
|
|
|
|
|
|
|
|
|
|
core += malloc_usable_size(ptr);
|
|
|
|
|
return (void *)ptr;
|
2000-03-22 10:10:50 +00:00
|
|
|
}
|
1997-11-01 01:39:32 +00:00
|
|
|
|
|
|
|
|
size_t
|
|
|
|
|
dcoresize(void)
|
2000-03-22 10:10:50 +00:00
|
|
|
{
|
1997-11-01 01:39:32 +00:00
|
|
|
return core;
|
2000-03-22 10:10:50 +00:00
|
|
|
}
|
1997-11-01 01:39:32 +00:00
|
|
|
#endif
|
|
|
|
|
|
2000-05-25 18:30:24 +00:00
|
|
|
/********************************************************************\
|
|
|
|
|
\********************************************************************/
|
|
|
|
|
|
|
|
|
|
#define UPPER(c) (((c) >= 'a' && (c) <= 'z') ? (c) + 'A' - 'a' : (c))
|
|
|
|
|
|
|
|
|
|
/* Search for str2 in first nchar chars of str1, ignore case..
|
|
|
|
|
* Return pointer to first match, or null.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
|
strncasestr(const char *str1, const char *str2, size_t len)
|
|
|
|
|
{
|
|
|
|
|
while (*str1 && len--)
|
|
|
|
|
{
|
|
|
|
|
if (UPPER(*str1) == UPPER(*str2))
|
|
|
|
|
{
|
|
|
|
|
if (strncasecmp(str1,str2,strlen(str2)) == 0)
|
|
|
|
|
{
|
|
|
|
|
return (char *) str1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
str1++;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Search for str2 in str1, ignore case.
|
|
|
|
|
* Return pointer to first match, or null.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
|
strcasestr(const char *str1, const char *str2)
|
|
|
|
|
{
|
|
|
|
|
size_t len = strlen (str1);
|
|
|
|
|
char * retval = strncasestr (str1, str2, len);
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Reversed strstr -- search for a needle in the haystack,
|
|
|
|
|
* from the far end
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
|
rstrstr (const char *haystack, const char * needle)
|
|
|
|
|
{
|
|
|
|
|
int haylen = strlen (haystack);
|
|
|
|
|
int neelen = strlen (needle);
|
|
|
|
|
|
|
|
|
|
const char * hp = haystack + haylen - 1;
|
|
|
|
|
const char * np = needle + neelen - 1;
|
|
|
|
|
|
|
|
|
|
if ((0 == neelen) || (0 == haylen)) return 0x0;
|
|
|
|
|
|
|
|
|
|
while (hp >= haystack+neelen) {
|
|
|
|
|
if (*hp == *np) {
|
|
|
|
|
--np;
|
|
|
|
|
if (np < needle) return (char *) hp;
|
|
|
|
|
} else {
|
|
|
|
|
np = needle + neelen - 1;
|
|
|
|
|
}
|
|
|
|
|
--hp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0x0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* The strpskip() function locates the first occurrence in the
|
|
|
|
|
* string s that does not match any of the characters in "reject".
|
|
|
|
|
* This is the opposite of strpbrk()
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
|
strpskip (const char * s, const char *reject)
|
|
|
|
|
{
|
|
|
|
|
size_t i, rlen;
|
|
|
|
|
char * retval;
|
|
|
|
|
|
|
|
|
|
if (!s) return NULL;
|
|
|
|
|
if (!reject) return (char *) s;
|
|
|
|
|
|
|
|
|
|
rlen = sizeof (reject);
|
|
|
|
|
retval = (char *) s;
|
|
|
|
|
|
|
|
|
|
while (*retval) {
|
|
|
|
|
int match = 0;
|
|
|
|
|
for (i=0; i<rlen; i++) {
|
|
|
|
|
if (reject[i] == *retval) {match=1; break; }
|
|
|
|
|
}
|
|
|
|
|
if (!match) return retval;
|
|
|
|
|
retval ++;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
1998-09-21 03:42:58 +00:00
|
|
|
/********************************************************************\
|
|
|
|
|
\********************************************************************/
|
|
|
|
|
|
|
|
|
|
int
|
2000-03-22 10:10:50 +00:00
|
|
|
safe_strcmp (const char * da, const char * db)
|
|
|
|
|
{
|
1998-09-21 03:42:58 +00:00
|
|
|
SAFE_STRCMP (da, db);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
1998-10-18 21:13:40 +00:00
|
|
|
/********************************************************************\
|
|
|
|
|
\********************************************************************/
|
|
|
|
|
/* inverse of strtoul */
|
|
|
|
|
|
|
|
|
|
#define MAX_DIGITS 50
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
|
ultostr (unsigned long val, int base)
|
|
|
|
|
{
|
|
|
|
|
char buf[MAX_DIGITS];
|
|
|
|
|
unsigned long broke[MAX_DIGITS];
|
|
|
|
|
int i;
|
|
|
|
|
unsigned long 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* normalize */
|
|
|
|
|
reval = 0;
|
|
|
|
|
for (i=places-2; i>=0; i--) {
|
|
|
|
|
reval += broke[i+1];
|
|
|
|
|
reval *= base;
|
|
|
|
|
broke[i] -= reval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* print */
|
|
|
|
|
for (i=0; i<places; i++) {
|
|
|
|
|
if (10>broke[i]) {
|
|
|
|
|
buf[places-1-i] = 0x30+broke[i]; /* ascii digit zero */
|
|
|
|
|
} else {
|
2000-05-26 02:20:11 +00:00
|
|
|
buf[places-1-i] = 0x41-10+broke[i]; /* ascii capital A */
|
1998-10-18 21:13:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
buf[places] = 0x0;
|
|
|
|
|
|
|
|
|
|
return strdup (buf);
|
|
|
|
|
}
|
|
|
|
|
|
1999-12-31 00:05:41 +00:00
|
|
|
/********************************************************************\
|
|
|
|
|
* utility function to convert floating point value to a string
|
|
|
|
|
\********************************************************************/
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
util_fptostr(char *buf, double val, int prec)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
char formatString[10];
|
|
|
|
|
char prefix[] = "%0.";
|
|
|
|
|
char postfix[] = "f";
|
|
|
|
|
|
|
|
|
|
/* This routine can only handle precision between 0 and 9, so
|
|
|
|
|
* clamp precision to that range */
|
|
|
|
|
if (prec > 9) prec = 9;
|
|
|
|
|
if (prec < 0) prec = 0;
|
|
|
|
|
|
|
|
|
|
/* Make sure that the output does not resemble "-0.00" by forcing
|
|
|
|
|
* val to 0.0 when we have a very small negative number */
|
|
|
|
|
if ((val <= 0.0) && (val > -pow(0.1, prec+1) * 5.0))
|
|
|
|
|
val = 0.0;
|
|
|
|
|
|
|
|
|
|
/* Create a format string to pass into sprintf. By doing this,
|
|
|
|
|
* we can get sprintf to convert the number to a string, rather
|
|
|
|
|
* than maintaining conversion code ourselves. */
|
|
|
|
|
i = 0;
|
|
|
|
|
strcpy(&formatString[i], prefix);
|
|
|
|
|
i += strlen(prefix);
|
|
|
|
|
formatString[i] = '0' + prec; /* add prec to ASCII code for '0' */
|
|
|
|
|
i += 1;
|
|
|
|
|
strcpy(&formatString[i], postfix);
|
|
|
|
|
i += strlen(postfix);
|
|
|
|
|
|
|
|
|
|
sprintf(buf, formatString, val);
|
|
|
|
|
|
|
|
|
|
return strlen(buf);
|
|
|
|
|
}
|
|
|
|
|
|
2000-03-24 09:47:27 +00:00
|
|
|
/********************************************************************\
|
2000-08-21 10:26:57 +00:00
|
|
|
* returns TRUE if the string is a number, possibly with whitespace
|
2000-03-24 09:47:27 +00:00
|
|
|
\********************************************************************/
|
|
|
|
|
|
2000-08-21 10:26:57 +00:00
|
|
|
gboolean
|
2000-03-24 09:47:27 +00:00
|
|
|
gnc_strisnum(const char *s)
|
|
|
|
|
{
|
2000-08-21 10:26:57 +00:00
|
|
|
if (s == NULL) return FALSE;
|
|
|
|
|
if (*s == 0) return FALSE;
|
2000-03-24 09:47:27 +00:00
|
|
|
|
|
|
|
|
while (*s && isspace(*s))
|
|
|
|
|
s++;
|
|
|
|
|
|
2000-08-21 10:26:57 +00:00
|
|
|
if (*s == 0) return FALSE;
|
|
|
|
|
if (!isdigit(*s)) return FALSE;
|
2000-03-24 09:47:27 +00:00
|
|
|
|
|
|
|
|
while (*s && isdigit(*s))
|
|
|
|
|
s++;
|
|
|
|
|
|
2000-08-21 10:26:57 +00:00
|
|
|
if (*s == 0) return TRUE;
|
2000-03-24 09:47:27 +00:00
|
|
|
|
|
|
|
|
while (*s && isspace(*s))
|
|
|
|
|
s++;
|
|
|
|
|
|
2000-08-21 10:26:57 +00:00
|
|
|
if (*s == 0) return TRUE;
|
2000-03-24 09:47:27 +00:00
|
|
|
|
2000-08-21 10:26:57 +00:00
|
|
|
return FALSE;
|
2000-03-24 09:47:27 +00:00
|
|
|
}
|
|
|
|
|
|
1999-07-05 22:47:57 +00:00
|
|
|
/********************************************************************\
|
|
|
|
|
* stpcpy for those platforms that don't have it.
|
|
|
|
|
\********************************************************************/
|
|
|
|
|
|
1999-07-05 22:58:24 +00:00
|
|
|
#if !HAVE_STPCPY
|
1999-07-05 22:47:57 +00:00
|
|
|
char *
|
1999-11-22 05:11:30 +00:00
|
|
|
stpcpy (char *dest, const char *src)
|
1999-07-05 22:47:57 +00:00
|
|
|
{
|
|
|
|
|
strcpy(dest, src);
|
|
|
|
|
return(dest + strlen(src));
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
|
1997-12-06 02:59:44 +00:00
|
|
|
/********************************************************************\
|
|
|
|
|
* currency & locale related stuff.
|
|
|
|
|
\********************************************************************/
|
|
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
static void
|
|
|
|
|
gnc_lconv_set(char **p_value, char *default_value)
|
|
|
|
|
{
|
|
|
|
|
char *value = *p_value;
|
|
|
|
|
|
|
|
|
|
if ((value == NULL) || (value[0] == 0))
|
|
|
|
|
*p_value = default_value;
|
|
|
|
|
}
|
|
|
|
|
|
2000-02-27 21:33:56 +00:00
|
|
|
static void
|
|
|
|
|
gnc_lconv_set_char(char *p_value, char default_value)
|
|
|
|
|
{
|
2000-05-03 19:55:36 +00:00
|
|
|
if ((p_value != NULL) && (*p_value == CHAR_MAX))
|
2000-02-27 21:33:56 +00:00
|
|
|
*p_value = default_value;
|
|
|
|
|
}
|
|
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
struct lconv *
|
2000-09-13 22:33:15 +00:00
|
|
|
gnc_localeconv(void)
|
2000-01-17 21:39:42 +00:00
|
|
|
{
|
|
|
|
|
static struct lconv lc;
|
2000-08-21 10:26:57 +00:00
|
|
|
static gboolean lc_set = FALSE;
|
2000-01-17 21:39:42 +00:00
|
|
|
|
|
|
|
|
if (lc_set)
|
|
|
|
|
return &lc;
|
|
|
|
|
|
|
|
|
|
lc = *localeconv();
|
|
|
|
|
|
|
|
|
|
gnc_lconv_set(&lc.decimal_point, ".");
|
|
|
|
|
gnc_lconv_set(&lc.thousands_sep, ",");
|
2000-08-31 07:16:55 +00:00
|
|
|
gnc_lconv_set(&lc.grouping, "\003");
|
2000-03-23 11:31:40 +00:00
|
|
|
gnc_lconv_set(&lc.int_curr_symbol, "USD ");
|
2000-01-17 21:39:42 +00:00
|
|
|
gnc_lconv_set(&lc.currency_symbol, CURRENCY_SYMBOL);
|
|
|
|
|
gnc_lconv_set(&lc.mon_decimal_point, ".");
|
|
|
|
|
gnc_lconv_set(&lc.mon_thousands_sep, ",");
|
2000-08-31 07:16:55 +00:00
|
|
|
gnc_lconv_set(&lc.mon_grouping, "\003");
|
2000-01-17 21:39:42 +00:00
|
|
|
gnc_lconv_set(&lc.negative_sign, "-");
|
1998-11-16 07:01:09 +00:00
|
|
|
|
2000-03-22 10:10:50 +00:00
|
|
|
gnc_lconv_set_char(&lc.frac_digits, 2);
|
|
|
|
|
gnc_lconv_set_char(&lc.int_frac_digits, 2);
|
2000-02-27 21:33:56 +00:00
|
|
|
gnc_lconv_set_char(&lc.p_cs_precedes, 1);
|
|
|
|
|
gnc_lconv_set_char(&lc.p_sep_by_space, 0);
|
|
|
|
|
gnc_lconv_set_char(&lc.n_cs_precedes, 1);
|
|
|
|
|
gnc_lconv_set_char(&lc.n_sep_by_space, 0);
|
|
|
|
|
gnc_lconv_set_char(&lc.p_sign_posn, 1);
|
|
|
|
|
gnc_lconv_set_char(&lc.n_sign_posn, 1);
|
|
|
|
|
|
2000-08-21 10:26:57 +00:00
|
|
|
lc_set = TRUE;
|
1998-11-16 06:28:13 +00:00
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
return &lc;
|
|
|
|
|
}
|
|
|
|
|
|
2000-08-22 01:35:47 +00:00
|
|
|
const char *
|
2000-09-13 22:33:15 +00:00
|
|
|
gnc_locale_default_currency(void)
|
2000-03-23 11:31:40 +00:00
|
|
|
{
|
|
|
|
|
static char currency[4];
|
2000-08-27 10:05:31 +00:00
|
|
|
static gboolean got_it = FALSE;
|
2000-03-23 11:31:40 +00:00
|
|
|
struct lconv *lc;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
if (got_it)
|
|
|
|
|
return currency;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 4; i++)
|
|
|
|
|
currency[i] = 0;
|
|
|
|
|
|
|
|
|
|
lc = gnc_localeconv();
|
|
|
|
|
|
|
|
|
|
strncpy(currency, lc->int_curr_symbol, 3);
|
|
|
|
|
|
2000-08-21 10:26:57 +00:00
|
|
|
got_it = TRUE;
|
2000-03-23 11:31:40 +00:00
|
|
|
|
|
|
|
|
return currency;
|
|
|
|
|
}
|
|
|
|
|
|
2000-08-27 08:25:53 +00:00
|
|
|
|
|
|
|
|
/* Return the number of decimal places for this locale. */
|
|
|
|
|
int
|
|
|
|
|
gnc_locale_decimal_places( void )
|
|
|
|
|
{
|
|
|
|
|
static gboolean got_it = FALSE;
|
|
|
|
|
static int places;
|
|
|
|
|
struct lconv *lc;
|
|
|
|
|
|
|
|
|
|
if( got_it )
|
|
|
|
|
return( places );
|
|
|
|
|
|
|
|
|
|
lc = gnc_localeconv();
|
2000-08-27 10:05:31 +00:00
|
|
|
places = lc->frac_digits;
|
2000-08-27 08:25:53 +00:00
|
|
|
|
|
|
|
|
/* frac_digits is already initialized by gnc_localeconv,
|
|
|
|
|
* hopefully to a reasonable default. */
|
|
|
|
|
|
|
|
|
|
got_it = TRUE;
|
|
|
|
|
|
|
|
|
|
return( places );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
/* Utility function for printing non-negative amounts */
|
1998-11-16 06:28:13 +00:00
|
|
|
static int
|
2000-01-17 21:39:42 +00:00
|
|
|
PrintAmt(char *buf, double val, int prec,
|
2000-08-21 10:26:57 +00:00
|
|
|
gboolean use_separators,
|
|
|
|
|
gboolean monetary,
|
2000-02-27 21:33:56 +00:00
|
|
|
int min_trailing_zeros)
|
1998-11-16 06:28:13 +00:00
|
|
|
{
|
2000-01-17 21:39:42 +00:00
|
|
|
int i, stringLength, numWholeDigits, sepCount;
|
|
|
|
|
struct lconv *lc = gnc_localeconv();
|
1999-12-31 00:05:41 +00:00
|
|
|
char tempBuf[50];
|
|
|
|
|
char *bufPtr = buf;
|
|
|
|
|
|
|
|
|
|
/* check if we're printing infinity */
|
|
|
|
|
if (!finite(val)) {
|
2000-01-17 21:39:42 +00:00
|
|
|
strcpy (buf, "inf");
|
|
|
|
|
return 3;
|
1999-12-31 00:05:41 +00:00
|
|
|
}
|
1998-11-16 06:28:13 +00:00
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
if (val < 0.0)
|
|
|
|
|
val = DABS(val);
|
|
|
|
|
|
1999-12-31 00:05:41 +00:00
|
|
|
util_fptostr(tempBuf, val, prec);
|
2000-01-17 21:39:42 +00:00
|
|
|
|
2000-02-27 21:33:56 +00:00
|
|
|
/* Here we strip off trailing decimal zeros per the argument. */
|
|
|
|
|
if (prec > 0)
|
|
|
|
|
{
|
|
|
|
|
int max_delete;
|
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
|
|
max_delete = prec - min_trailing_zeros;
|
|
|
|
|
|
|
|
|
|
p = tempBuf + strlen(tempBuf) - 1;
|
|
|
|
|
|
|
|
|
|
while ((*p == '0') && (max_delete > 0))
|
|
|
|
|
{
|
|
|
|
|
*p-- = 0;
|
|
|
|
|
max_delete--;
|
|
|
|
|
}
|
|
|
|
|
|
2000-03-23 11:31:40 +00:00
|
|
|
if (*p == '.')
|
2000-02-27 21:33:56 +00:00
|
|
|
*p = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
if (!use_separators)
|
|
|
|
|
{
|
2000-03-28 22:38:21 +00:00
|
|
|
/* fix up the decimal place, if there is one */
|
|
|
|
|
stringLength = strlen(tempBuf);
|
|
|
|
|
numWholeDigits = -1;
|
|
|
|
|
for (i = stringLength - 1; i >= 0; i--) {
|
|
|
|
|
if (tempBuf[i] == '.') {
|
|
|
|
|
if (monetary)
|
|
|
|
|
tempBuf[i] = lc->mon_decimal_point[0];
|
|
|
|
|
else
|
|
|
|
|
tempBuf[i] = lc->decimal_point[0];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
strcpy(buf, tempBuf);
|
|
|
|
|
}
|
1999-12-31 00:05:41 +00:00
|
|
|
else
|
2000-01-17 21:39:42 +00:00
|
|
|
{
|
|
|
|
|
/* Determine where the decimal place is, if there is one */
|
|
|
|
|
stringLength = strlen(tempBuf);
|
|
|
|
|
numWholeDigits = -1;
|
|
|
|
|
for (i = stringLength - 1; i >= 0; i--) {
|
2000-03-28 22:38:21 +00:00
|
|
|
if ((tempBuf[i] == '.') || (tempBuf[i] == lc->decimal_point[0])) {
|
2000-01-17 21:39:42 +00:00
|
|
|
numWholeDigits = i;
|
|
|
|
|
if (monetary)
|
|
|
|
|
tempBuf[i] = lc->mon_decimal_point[0];
|
2000-03-23 11:31:40 +00:00
|
|
|
else
|
|
|
|
|
tempBuf[i] = lc->decimal_point[0];
|
2000-01-17 21:39:42 +00:00
|
|
|
break;
|
1998-11-16 06:28:13 +00:00
|
|
|
}
|
2000-01-17 21:39:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (numWholeDigits < 0)
|
|
|
|
|
numWholeDigits = stringLength; /* Can't find decimal place, it's
|
|
|
|
|
* a whole number */
|
|
|
|
|
|
|
|
|
|
/* We now know the number of whole digits, now insert separators while
|
|
|
|
|
* copying them from the temp buffer to the destination */
|
|
|
|
|
bufPtr = buf;
|
|
|
|
|
for (i = 0; i < numWholeDigits; i++, bufPtr++) {
|
|
|
|
|
*bufPtr = tempBuf[i];
|
|
|
|
|
sepCount = (numWholeDigits - i) - 1;
|
|
|
|
|
if ((sepCount % 3 == 0) &&
|
|
|
|
|
(sepCount != 0))
|
|
|
|
|
{
|
|
|
|
|
bufPtr++;
|
|
|
|
|
if (monetary)
|
|
|
|
|
*bufPtr = lc->mon_thousands_sep[0];
|
|
|
|
|
else
|
|
|
|
|
*bufPtr = lc->thousands_sep[0];
|
1999-12-31 00:05:41 +00:00
|
|
|
}
|
2000-01-17 21:39:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strcpy(bufPtr, &tempBuf[numWholeDigits]);
|
|
|
|
|
} /* endif */
|
1998-11-16 06:28:13 +00:00
|
|
|
|
1999-12-31 00:05:41 +00:00
|
|
|
return strlen(buf);
|
1998-11-16 06:28:13 +00:00
|
|
|
}
|
|
|
|
|
|
1999-01-30 21:22:13 +00:00
|
|
|
int
|
2000-05-16 23:48:19 +00:00
|
|
|
xaccSPrintAmountGeneral (char * bufp, double val,
|
|
|
|
|
GNCPrintAmountFlags flags,
|
|
|
|
|
int precision,
|
|
|
|
|
int min_trailing_zeros,
|
|
|
|
|
const char *curr_sym)
|
1997-12-06 02:59:44 +00:00
|
|
|
{
|
2000-01-17 21:39:42 +00:00
|
|
|
struct lconv *lc;
|
|
|
|
|
|
|
|
|
|
char *orig_bufp = bufp;
|
2000-04-24 23:20:36 +00:00
|
|
|
const char *currency_symbol;
|
|
|
|
|
const char *sign;
|
2000-01-17 21:39:42 +00:00
|
|
|
|
|
|
|
|
char cs_precedes;
|
|
|
|
|
char sep_by_space;
|
|
|
|
|
char sign_posn;
|
|
|
|
|
|
2000-08-21 10:26:57 +00:00
|
|
|
gboolean print_sign = TRUE;
|
1999-01-30 21:22:13 +00:00
|
|
|
|
|
|
|
|
if (!bufp) return 0;
|
1998-11-16 06:28:13 +00:00
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
lc = gnc_localeconv();
|
|
|
|
|
|
1999-11-22 05:09:40 +00:00
|
|
|
if (DEQ(val, 0.0))
|
|
|
|
|
val = 0.0;
|
|
|
|
|
|
2000-05-16 23:48:19 +00:00
|
|
|
if (flags & PRTSHR)
|
2000-01-17 21:39:42 +00:00
|
|
|
{
|
|
|
|
|
currency_symbol = "shrs";
|
|
|
|
|
cs_precedes = 0; /* currency symbol follows amount */
|
|
|
|
|
sep_by_space = 1; /* they are separated by a space */
|
|
|
|
|
}
|
2000-05-16 23:48:19 +00:00
|
|
|
else if (flags & PRTEUR)
|
2000-04-24 21:28:25 +00:00
|
|
|
{
|
|
|
|
|
currency_symbol = "EUR";
|
|
|
|
|
cs_precedes = 1; /* currency symbol precedes amount */
|
|
|
|
|
sep_by_space = 0; /* they are not separated by a space */
|
|
|
|
|
}
|
2000-01-17 21:39:42 +00:00
|
|
|
else
|
|
|
|
|
{
|
2000-04-24 21:28:25 +00:00
|
|
|
if (curr_sym == NULL)
|
2000-04-21 10:49:15 +00:00
|
|
|
{
|
|
|
|
|
currency_symbol = lc->currency_symbol;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
currency_symbol = curr_sym;
|
|
|
|
|
}
|
2000-04-24 21:28:25 +00:00
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
if (val < 0.0)
|
|
|
|
|
{
|
|
|
|
|
cs_precedes = lc->n_cs_precedes;
|
|
|
|
|
sep_by_space = lc->n_sep_by_space;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cs_precedes = lc->p_cs_precedes;
|
|
|
|
|
sep_by_space = lc->p_sep_by_space;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (val < 0.0)
|
|
|
|
|
{
|
|
|
|
|
sign = lc->negative_sign;
|
|
|
|
|
sign_posn = lc->n_sign_posn;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sign = lc->positive_sign;
|
|
|
|
|
sign_posn = lc->p_sign_posn;
|
|
|
|
|
}
|
1999-12-31 00:05:41 +00:00
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
if ((val == 0.0) || (sign == NULL) || (sign[0] == 0))
|
2000-08-21 10:26:57 +00:00
|
|
|
print_sign = FALSE;
|
2000-01-17 21:39:42 +00:00
|
|
|
|
|
|
|
|
/* See if we print sign now */
|
|
|
|
|
if (print_sign && (sign_posn == 1))
|
|
|
|
|
bufp = stpcpy(bufp, sign);
|
|
|
|
|
|
|
|
|
|
/* Now see if we print currency */
|
|
|
|
|
if (cs_precedes)
|
|
|
|
|
{
|
|
|
|
|
/* See if we print sign now */
|
|
|
|
|
if (print_sign && (sign_posn == 3))
|
|
|
|
|
bufp = stpcpy(bufp, sign);
|
|
|
|
|
|
2000-05-16 23:48:19 +00:00
|
|
|
if (flags & PRTSYM)
|
2000-01-17 21:39:42 +00:00
|
|
|
{
|
|
|
|
|
bufp = stpcpy(bufp, currency_symbol);
|
|
|
|
|
if (sep_by_space)
|
|
|
|
|
bufp = stpcpy(bufp, " ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* See if we print sign now */
|
|
|
|
|
if (print_sign && (sign_posn == 4))
|
|
|
|
|
bufp = stpcpy(bufp, sign);
|
1997-12-06 02:59:44 +00:00
|
|
|
}
|
|
|
|
|
|
2000-01-17 21:39:42 +00:00
|
|
|
/* Now see if we print parentheses */
|
2000-02-27 21:33:56 +00:00
|
|
|
if (print_sign && (sign_posn == 0))
|
2000-01-17 21:39:42 +00:00
|
|
|
bufp = stpcpy(bufp, "(");
|
|
|
|
|
|
|
|
|
|
/* Now print the value */
|
2000-05-16 23:48:19 +00:00
|
|
|
bufp += PrintAmt(bufp, DABS(val), precision, flags & PRTSEP,
|
|
|
|
|
!(flags & PRTNMN), min_trailing_zeros);
|
2000-01-17 21:39:42 +00:00
|
|
|
|
|
|
|
|
/* Now see if we print parentheses */
|
2000-02-27 21:33:56 +00:00
|
|
|
if (print_sign && (sign_posn == 0))
|
2000-01-17 21:39:42 +00:00
|
|
|
bufp = stpcpy(bufp, ")");
|
|
|
|
|
|
|
|
|
|
/* Now see if we print currency */
|
|
|
|
|
if (!cs_precedes)
|
|
|
|
|
{
|
|
|
|
|
/* See if we print sign now */
|
|
|
|
|
if (print_sign && (sign_posn == 3))
|
|
|
|
|
bufp = stpcpy(bufp, sign);
|
|
|
|
|
|
2000-05-16 23:48:19 +00:00
|
|
|
if (flags & PRTSYM)
|
2000-01-17 21:39:42 +00:00
|
|
|
{
|
|
|
|
|
if (sep_by_space)
|
|
|
|
|
bufp = stpcpy(bufp, " ");
|
|
|
|
|
bufp = stpcpy(bufp, currency_symbol);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* See if we print sign now */
|
|
|
|
|
if (print_sign && (sign_posn == 4))
|
|
|
|
|
bufp = stpcpy(bufp, sign);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* See if we print sign now */
|
|
|
|
|
if (print_sign && (sign_posn == 2))
|
|
|
|
|
bufp = stpcpy(bufp, sign);
|
|
|
|
|
|
1999-01-30 21:22:13 +00:00
|
|
|
/* return length of printed string */
|
2000-01-17 21:39:42 +00:00
|
|
|
return (bufp - orig_bufp);
|
1999-01-30 21:22:13 +00:00
|
|
|
}
|
|
|
|
|
|
2000-02-27 21:33:56 +00:00
|
|
|
int
|
2000-05-16 23:48:19 +00:00
|
|
|
xaccSPrintAmount (char * bufp, double val, GNCPrintAmountFlags flags,
|
|
|
|
|
const char *curr_code)
|
2000-02-27 21:33:56 +00:00
|
|
|
{
|
2000-04-24 21:28:25 +00:00
|
|
|
struct lconv *lc;
|
2000-03-22 10:10:50 +00:00
|
|
|
int precision;
|
|
|
|
|
int min_trailing_zeros;
|
2000-05-03 09:56:04 +00:00
|
|
|
char curr_sym[5];
|
2000-02-27 21:33:56 +00:00
|
|
|
|
2000-04-24 21:28:25 +00:00
|
|
|
lc = gnc_localeconv();
|
|
|
|
|
|
|
|
|
|
if (curr_code && (strncmp(curr_code, lc->int_curr_symbol, 3) == 0))
|
|
|
|
|
curr_code = NULL;
|
2000-05-03 09:56:04 +00:00
|
|
|
else if (curr_code)
|
|
|
|
|
{
|
|
|
|
|
strncpy(curr_sym, curr_code, 3);
|
|
|
|
|
curr_sym[3] = '\0';
|
|
|
|
|
strcat(curr_sym, " ");
|
|
|
|
|
curr_code = curr_sym;
|
|
|
|
|
}
|
2000-04-24 21:28:25 +00:00
|
|
|
|
2000-05-03 09:56:04 +00:00
|
|
|
if (curr_code && (strncmp(curr_code, "EUR", 3) == 0))
|
2000-05-16 23:48:19 +00:00
|
|
|
flags |= PRTEUR;
|
2000-04-24 21:28:25 +00:00
|
|
|
|
2000-05-16 23:48:19 +00:00
|
|
|
if (flags & PRTCUR)
|
|
|
|
|
{
|
|
|
|
|
precision = 5;
|
|
|
|
|
min_trailing_zeros = 0;
|
|
|
|
|
}
|
|
|
|
|
else if (flags & PRTSHR)
|
2000-02-27 21:33:56 +00:00
|
|
|
{
|
|
|
|
|
precision = 4;
|
|
|
|
|
min_trailing_zeros = 0;
|
|
|
|
|
}
|
2000-05-16 23:48:19 +00:00
|
|
|
else if (flags & PRTEUR)
|
2000-04-24 21:28:25 +00:00
|
|
|
{
|
|
|
|
|
precision = 2;
|
|
|
|
|
min_trailing_zeros = 2;
|
|
|
|
|
}
|
2000-03-22 10:10:50 +00:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
precision = lc->frac_digits;
|
|
|
|
|
min_trailing_zeros = lc->frac_digits;
|
|
|
|
|
}
|
2000-02-27 21:33:56 +00:00
|
|
|
|
2000-05-16 23:48:19 +00:00
|
|
|
return xaccSPrintAmountGeneral(bufp, val, flags, precision,
|
2000-04-24 21:28:25 +00:00
|
|
|
min_trailing_zeros, curr_code);
|
2000-02-27 21:33:56 +00:00
|
|
|
}
|
|
|
|
|
|
2000-08-22 01:35:47 +00:00
|
|
|
const char *
|
2000-05-16 23:48:19 +00:00
|
|
|
xaccPrintAmount (double val, GNCPrintAmountFlags flags, const char *curr_code)
|
1999-01-30 21:22:13 +00:00
|
|
|
{
|
|
|
|
|
/* hack alert -- this is not thread safe ... */
|
|
|
|
|
static char buf[BUFSIZE];
|
|
|
|
|
|
2000-05-16 23:48:19 +00:00
|
|
|
xaccSPrintAmount (buf, val, flags, curr_code);
|
1999-01-30 21:22:13 +00:00
|
|
|
|
|
|
|
|
/* its OK to return buf, since we declared it static */
|
1997-12-06 02:59:44 +00:00
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
2000-08-22 01:35:47 +00:00
|
|
|
const char *
|
2000-08-21 10:26:57 +00:00
|
|
|
xaccPrintAmountArgs (double val, gboolean print_currency_symbol,
|
|
|
|
|
gboolean print_separators, gboolean is_shares_value,
|
2000-04-24 21:28:25 +00:00
|
|
|
const char *curr_code)
|
2000-03-09 08:04:03 +00:00
|
|
|
{
|
2000-05-16 23:48:19 +00:00
|
|
|
GNCPrintAmountFlags flags = 0;
|
2000-03-09 08:04:03 +00:00
|
|
|
|
2000-05-16 23:48:19 +00:00
|
|
|
if (print_currency_symbol) flags |= PRTSYM;
|
|
|
|
|
if (print_separators) flags |= PRTSEP;
|
|
|
|
|
if (is_shares_value) flags |= PRTSHR;
|
2000-03-09 08:04:03 +00:00
|
|
|
|
2000-05-16 23:48:19 +00:00
|
|
|
return xaccPrintAmount(val, flags, curr_code);
|
2000-03-09 08:04:03 +00:00
|
|
|
}
|
|
|
|
|
|
1998-02-02 23:25:44 +00:00
|
|
|
|
|
|
|
|
/********************************************************************\
|
2000-01-17 21:39:42 +00:00
|
|
|
* xaccParseAmount *
|
|
|
|
|
* parses amount strings using locale data *
|
|
|
|
|
* *
|
2000-08-31 07:16:55 +00:00
|
|
|
* Args: in_str -- pointer to string rep of num *
|
|
|
|
|
* monetary -- boolean indicating whether value is monetary *
|
|
|
|
|
* result -- pointer to result location, may be NULL *
|
|
|
|
|
* endstr -- used to store first digit not used in parsing *
|
|
|
|
|
* Return: gboolean -- TRUE if a number found and parsed *
|
|
|
|
|
* If FALSE, result is not changed *
|
2000-01-17 21:39:42 +00:00
|
|
|
\********************************************************************/
|
|
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* Parsing state machine states */
|
|
|
|
|
typedef enum
|
2000-01-17 21:39:42 +00:00
|
|
|
{
|
2000-08-31 07:16:55 +00:00
|
|
|
START_ST, /* Parsing initial whitespace */
|
|
|
|
|
NEG_ST, /* Parsed a negative sign */
|
|
|
|
|
PRE_GROUP_ST, /* Parsing digits before grouping and decimal characters */
|
|
|
|
|
START_GROUP_ST, /* Start of a digit group encountered (possibly) */
|
|
|
|
|
IN_GROUP_ST, /* Within a digit group */
|
|
|
|
|
FRAC_ST, /* Parsing the fractional portion of a number */
|
|
|
|
|
DONE_ST, /* Finished, number is correct module grouping constraints */
|
|
|
|
|
NO_NUM_ST /* Finished, number was malformed */
|
|
|
|
|
} ParseState;
|
|
|
|
|
|
|
|
|
|
#define done_state(state) (((state) == DONE_ST) || ((state) == NO_NUM_ST))
|
|
|
|
|
|
2000-09-13 22:33:15 +00:00
|
|
|
G_INLINE_FUNC double fractional_multiplier (int num_decimals);
|
|
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
G_INLINE_FUNC double
|
|
|
|
|
fractional_multiplier (int num_decimals)
|
|
|
|
|
{
|
|
|
|
|
switch (num_decimals)
|
|
|
|
|
{
|
|
|
|
|
case 8:
|
|
|
|
|
return 0.00000001;
|
|
|
|
|
case 7:
|
|
|
|
|
return 0.0000001;
|
|
|
|
|
case 6:
|
|
|
|
|
return 0.000001;
|
|
|
|
|
break;
|
|
|
|
|
case 5:
|
|
|
|
|
return 0.00001;
|
|
|
|
|
case 4:
|
|
|
|
|
return 0.0001;
|
|
|
|
|
case 3:
|
|
|
|
|
return 0.001;
|
|
|
|
|
case 2:
|
|
|
|
|
return 0.01;
|
|
|
|
|
case 1:
|
|
|
|
|
return 0.1;
|
|
|
|
|
default:
|
|
|
|
|
PERR("bad fraction length");
|
|
|
|
|
g_assert_not_reached();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2000-01-17 21:39:42 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
return 0.0;
|
|
|
|
|
}
|
2000-05-19 10:27:33 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
gboolean
|
|
|
|
|
xaccParseAmount (const char * in_str, gboolean monetary, double *result,
|
|
|
|
|
char **endstr)
|
|
|
|
|
{
|
|
|
|
|
struct lconv *lc = gnc_localeconv();
|
|
|
|
|
gboolean is_negative;
|
|
|
|
|
gboolean got_decimal;
|
|
|
|
|
GList *group_data;
|
|
|
|
|
int group_count;
|
|
|
|
|
double value;
|
|
|
|
|
|
|
|
|
|
ParseState state;
|
|
|
|
|
|
|
|
|
|
char negative_sign;
|
|
|
|
|
char decimal_point;
|
|
|
|
|
char group_separator;
|
|
|
|
|
const char *in;
|
|
|
|
|
char *out_str;
|
|
|
|
|
char *out;
|
|
|
|
|
|
|
|
|
|
/* Initialize *endstr to in_str */
|
|
|
|
|
if (endstr != NULL)
|
|
|
|
|
*endstr = (char *) in_str;
|
|
|
|
|
|
|
|
|
|
if (in_str == NULL)
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
negative_sign = lc->negative_sign[0];
|
|
|
|
|
if (monetary)
|
|
|
|
|
{
|
|
|
|
|
group_separator = lc->mon_thousands_sep[0];
|
|
|
|
|
decimal_point = lc->mon_decimal_point[0];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
group_separator = lc->thousands_sep[0];
|
|
|
|
|
decimal_point = lc->decimal_point[0];
|
|
|
|
|
}
|
2000-05-19 10:27:33 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* 'out_str' will be used to store digits for numeric conversion.
|
|
|
|
|
* 'out' will be used to traverse out_str. */
|
|
|
|
|
out = out_str = g_new(char, strlen(in_str) + 1);
|
2000-05-19 10:27:33 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* 'in' is used to traverse 'in_str'. */
|
|
|
|
|
in = in_str;
|
2000-05-19 10:27:33 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
is_negative = FALSE;
|
|
|
|
|
got_decimal = FALSE;
|
|
|
|
|
group_data = NULL;
|
|
|
|
|
group_count = 0;
|
|
|
|
|
value = 0.0;
|
2000-05-19 10:27:33 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* Initialize the state machine */
|
|
|
|
|
state = START_ST;
|
2000-01-17 21:39:42 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* This while loop implements a state machine for parsing numbers. */
|
|
|
|
|
while (TRUE)
|
|
|
|
|
{
|
|
|
|
|
ParseState next_state = state;
|
2000-05-19 10:27:33 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* Note we never need to check for then end of 'in_str' explicitly.
|
|
|
|
|
* The 'else' clauses on all the state transitions will handle that. */
|
|
|
|
|
switch (state)
|
|
|
|
|
{
|
|
|
|
|
/* START_ST means we have parsed 0 or more whitespace characters */
|
|
|
|
|
case START_ST:
|
|
|
|
|
if (isdigit(*in))
|
|
|
|
|
{
|
|
|
|
|
*out++ = *in; /* we record the digits themselves in out_str
|
|
|
|
|
* for later conversion by libc routines */
|
|
|
|
|
next_state = PRE_GROUP_ST;
|
|
|
|
|
}
|
|
|
|
|
else if (isspace(*in))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
else if (*in == negative_sign)
|
|
|
|
|
{
|
|
|
|
|
is_negative = TRUE;
|
|
|
|
|
next_state = NEG_ST;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
next_state = NO_NUM_ST;
|
|
|
|
|
}
|
2000-01-17 21:39:42 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
break;
|
2000-05-19 10:27:33 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* NEG_ST means we have just parsed a negative sign. For now,
|
|
|
|
|
* we only recognize formats where the negative sign comes first. */
|
|
|
|
|
case NEG_ST:
|
|
|
|
|
if (isdigit(*in))
|
|
|
|
|
{
|
|
|
|
|
*out++ = *in;
|
|
|
|
|
next_state = PRE_GROUP_ST;
|
|
|
|
|
}
|
|
|
|
|
else if (isspace(*in))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
next_state = NO_NUM_ST;
|
|
|
|
|
}
|
2000-01-17 21:39:42 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
break;
|
2000-01-17 21:39:42 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* PRE_GROUP_ST means we have started parsing the number, but
|
|
|
|
|
* have not encountered a decimal point or a grouping character. */
|
|
|
|
|
case PRE_GROUP_ST:
|
|
|
|
|
if (isdigit(*in))
|
|
|
|
|
{
|
|
|
|
|
*out++ = *in;
|
|
|
|
|
}
|
|
|
|
|
else if (*in == decimal_point)
|
|
|
|
|
{
|
|
|
|
|
next_state = FRAC_ST;
|
|
|
|
|
}
|
|
|
|
|
else if (*in == group_separator)
|
|
|
|
|
{
|
|
|
|
|
next_state = START_GROUP_ST;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
next_state = DONE_ST;
|
|
|
|
|
}
|
2000-01-17 21:39:42 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
break;
|
2000-06-21 09:48:39 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* START_GROUP_ST means we have just parsed a group character.
|
|
|
|
|
* Note that group characters might be whitespace!!! In general,
|
|
|
|
|
* if a decimal point or a group character is whitespace, we
|
|
|
|
|
* try to interpret it in the fashion that will allow parsing
|
|
|
|
|
* of the current number to continue. */
|
|
|
|
|
case START_GROUP_ST:
|
|
|
|
|
if (isdigit(*in))
|
|
|
|
|
{
|
|
|
|
|
*out++ = *in;
|
|
|
|
|
group_count++; /* We record the number of digits
|
|
|
|
|
* in the group for later checking. */
|
|
|
|
|
next_state = IN_GROUP_ST;
|
|
|
|
|
}
|
|
|
|
|
else if (*in == decimal_point)
|
|
|
|
|
{
|
|
|
|
|
/* If we now get a decimal point, and both the decimal
|
|
|
|
|
* and the group separator are also whitespace, assume
|
|
|
|
|
* the last group separator was actually whitespace and
|
|
|
|
|
* stop parsing. Otherwise, there's a problem. */
|
|
|
|
|
if (isspace(group_separator) && isspace(decimal_point))
|
|
|
|
|
next_state = DONE_ST;
|
|
|
|
|
else
|
|
|
|
|
next_state = NO_NUM_ST;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* If the last group separator is also whitespace,
|
|
|
|
|
* assume it was intended as such and stop parsing.
|
|
|
|
|
* Otherwise, there is a problem. */
|
|
|
|
|
if (isspace(group_separator))
|
|
|
|
|
next_state = DONE_ST;
|
|
|
|
|
else
|
|
|
|
|
next_state = NO_NUM_ST;
|
|
|
|
|
}
|
2000-06-21 09:48:39 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
break;
|
2000-06-21 09:48:39 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
/* IN_GROUP_ST means we are in the middle of parsing
|
|
|
|
|
* a group of digits. */
|
|
|
|
|
case IN_GROUP_ST:
|
|
|
|
|
if (isdigit(*in))
|
|
|
|
|
{
|
|
|
|
|
*out++ = *in;
|
|
|
|
|
group_count++; /* We record the number of digits
|
|
|
|
|
* in the group for later checking. */
|
|
|
|
|
}
|
|
|
|
|
else if (*in == decimal_point)
|
|
|
|
|
{
|
|
|
|
|
next_state = FRAC_ST;
|
|
|
|
|
}
|
|
|
|
|
else if (*in == group_separator)
|
|
|
|
|
{
|
|
|
|
|
next_state = START_GROUP_ST;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
next_state = DONE_ST;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/* FRAC_ST means we are now parsing fractional digits. */
|
|
|
|
|
case FRAC_ST:
|
|
|
|
|
if (isdigit(*in))
|
|
|
|
|
{
|
|
|
|
|
*out++ = *in;
|
|
|
|
|
}
|
|
|
|
|
else if (*in == decimal_point)
|
|
|
|
|
{
|
|
|
|
|
/* If a subsequent decimal point is also whitespace,
|
|
|
|
|
* assume it was intended as such and stop parsing.
|
|
|
|
|
* Otherwise, there is a problem. */
|
|
|
|
|
if (isspace(decimal_point))
|
|
|
|
|
next_state = DONE_ST;
|
|
|
|
|
else
|
|
|
|
|
next_state = NO_NUM_ST;
|
|
|
|
|
}
|
|
|
|
|
else if (*in == group_separator)
|
|
|
|
|
{
|
|
|
|
|
/* If a subsequent group separator is also whitespace,
|
|
|
|
|
* assume it was intended as such and stop parsing.
|
|
|
|
|
* Otherwise, there is a problem. */
|
|
|
|
|
if (isspace(group_separator))
|
|
|
|
|
next_state = DONE_ST;
|
|
|
|
|
else
|
|
|
|
|
next_state = NO_NUM_ST;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
next_state = DONE_ST;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
PERR("bad state");
|
|
|
|
|
g_assert_not_reached();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If we're moving out of the IN_GROUP_ST, record data for the group */
|
|
|
|
|
if ((state == IN_GROUP_ST) && (next_state != IN_GROUP_ST))
|
|
|
|
|
{
|
|
|
|
|
group_data = g_list_prepend(group_data, GINT_TO_POINTER(group_count));
|
|
|
|
|
group_count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If we're moving into the FRAC_ST or out of the machine
|
|
|
|
|
* without going through FRAC_ST, record the integral value. */
|
|
|
|
|
if (((next_state == FRAC_ST) && (state != FRAC_ST)) ||
|
|
|
|
|
((next_state == DONE_ST) && !got_decimal))
|
|
|
|
|
{
|
|
|
|
|
*out = '\0';
|
|
|
|
|
value = strtod(out_str, NULL);
|
|
|
|
|
|
|
|
|
|
if (value == HUGE_VAL)
|
|
|
|
|
{
|
|
|
|
|
next_state = NO_NUM_ST;
|
2000-01-17 21:39:42 +00:00
|
|
|
}
|
2000-08-31 07:16:55 +00:00
|
|
|
else if (next_state == FRAC_ST)
|
|
|
|
|
{
|
|
|
|
|
/* reset the out pointer to record the fraction */
|
|
|
|
|
out = out_str;
|
|
|
|
|
*out = '\0';
|
2000-01-17 21:39:42 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
got_decimal = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state = next_state;
|
|
|
|
|
if (done_state (state))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
in++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If there was an error, just quit */
|
|
|
|
|
if (state == NO_NUM_ST)
|
|
|
|
|
{
|
|
|
|
|
g_free(out_str);
|
|
|
|
|
g_list_free(group_data);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If there were groups, validate them */
|
|
|
|
|
if (group_data != NULL)
|
|
|
|
|
{
|
|
|
|
|
gboolean good_grouping = TRUE;
|
|
|
|
|
GList *node;
|
|
|
|
|
char *group;
|
|
|
|
|
|
|
|
|
|
group = monetary ? lc->mon_grouping : lc->grouping;
|
|
|
|
|
|
|
|
|
|
/* The groups were built in reverse order. This
|
|
|
|
|
* is the easiest order to verify them in. */
|
|
|
|
|
for (node = group_data; node; node = node->next)
|
|
|
|
|
{
|
|
|
|
|
/* Verify group size */
|
|
|
|
|
if (*group != GPOINTER_TO_INT(node->data))
|
|
|
|
|
{
|
|
|
|
|
good_grouping = FALSE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Peek ahead at the next group code */
|
|
|
|
|
switch (group[1])
|
|
|
|
|
{
|
|
|
|
|
/* A null char means repeat the last group indefinitely */
|
|
|
|
|
case '\0':
|
|
|
|
|
break;
|
|
|
|
|
/* CHAR_MAX means no more grouping allowed */
|
|
|
|
|
case CHAR_MAX:
|
|
|
|
|
if (node->next != NULL)
|
|
|
|
|
good_grouping = FALSE;
|
|
|
|
|
break;
|
|
|
|
|
/* Anything else means another group size */
|
|
|
|
|
default:
|
|
|
|
|
group++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!good_grouping)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_list_free(group_data);
|
|
|
|
|
|
|
|
|
|
if (!good_grouping)
|
|
|
|
|
{
|
|
|
|
|
g_free(out_str);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Cap the end of the fraction string, if any */
|
|
|
|
|
*out = '\0';
|
|
|
|
|
|
|
|
|
|
/* Add in fractional value */
|
|
|
|
|
if (got_decimal && (*out_str != '\0'))
|
|
|
|
|
{
|
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
|
|
len = strlen(out_str);
|
|
|
|
|
|
|
|
|
|
if (len > 8)
|
|
|
|
|
{
|
|
|
|
|
out_str[8] = '\0';
|
|
|
|
|
len = 8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
value += fractional_multiplier(len) * strtod(out_str, NULL);
|
|
|
|
|
|
|
|
|
|
if (value == HUGE_VAL)
|
|
|
|
|
{
|
|
|
|
|
g_free(out_str);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (auto_decimal_enabled && !got_decimal)
|
|
|
|
|
{
|
|
|
|
|
/* No decimal point and auto decimal point enabled, so assume
|
|
|
|
|
* that the value is an integer number of cents or a cent-type
|
|
|
|
|
* unit. For each auto decimal place requested, move the final
|
|
|
|
|
* decimal point one place to the left. */
|
|
|
|
|
if ((auto_decimal_places > 0) && (auto_decimal_places < 9))
|
|
|
|
|
value *= fractional_multiplier(auto_decimal_places);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_negative)
|
|
|
|
|
value = -value;
|
|
|
|
|
|
|
|
|
|
if (result != NULL)
|
|
|
|
|
*result = value;
|
|
|
|
|
|
|
|
|
|
if (endstr != NULL)
|
|
|
|
|
*endstr = (char *) in;
|
2000-01-17 21:39:42 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
g_free (out_str);
|
2000-01-17 21:39:42 +00:00
|
|
|
|
2000-08-31 07:16:55 +00:00
|
|
|
return TRUE;
|
2000-01-17 21:39:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
1997-11-22 10:42:02 +00:00
|
|
|
/************************* END OF FILE ******************************\
|
|
|
|
|
\********************************************************************/
|