Generalize xaccParseAmount.

Add a GnuCash-specific interface to the src/calculation
expression parser.


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@2736 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Dave Peticolas 2000-08-31 07:16:55 +00:00
parent 70361420c9
commit a0e4b34fbf
13 changed files with 886 additions and 186 deletions

View File

@ -8,7 +8,7 @@
# This script requires the programs 'makepatch', 'gzip',
# 'diff', and 'uuencode'.
#
# Author: Dave Peticolas <peticola@cs.ucdavis.edu>
# Author: Dave Peticolas <dave@krondo.com>
use strict;

View File

@ -39,7 +39,8 @@ gnucash_SOURCES = \
Destroy.c \
EuroUtils.c \
FileDialog.c \
Refresh.c
Refresh.c \
gnc-exp-parser.c
noinst_HEADERS = \
AccWindow.h \
@ -55,6 +56,7 @@ noinst_HEADERS = \
RegWindow.h \
SplitLedger.h \
file-history.h \
gnc-exp-parser.h \
gnc-ui-common.h \
messages.h \
messages_i18n.h \
@ -67,6 +69,7 @@ EXTRA_DIST = \
CFLAGS = @CFLAGS@ ${GLIB_CFLAGS} ${GNOME_CFLAGS} ${GUILE_COMPILE_ARGS}
INCLUDES = \
-I./calculation \
-I./engine \
-I./guile \
-I./register

View File

@ -129,10 +129,10 @@ bin_PROGRAMS = gnucash
LDADD = gnome/libgncgnome.a register/libgncregister.a register/gnome/libgncregistergnome.a guile/libgncguile.a gnome/libgncgnome.a calculation/libgnccalc.a engine/libgncengine.la @GNOME_LIBS@ @G_WRAP_LINK_ARGS@ @GUILE_LINK_ARGS@ @INTLLIBS@
gnucash_SOURCES = MultiLedger.c SplitLedger.c Destroy.c EuroUtils.c FileDialog.c Refresh.c
gnucash_SOURCES = MultiLedger.c SplitLedger.c Destroy.c EuroUtils.c FileDialog.c Refresh.c gnc-exp-parser.c
noinst_HEADERS = AccWindow.h AdjBWindow.h Destroy.h EuroUtils.h FileBox.h FileDialog.h MainWindow.h MultiLedger.h RecnWindow.h Refresh.h RegWindow.h SplitLedger.h file-history.h gnc-ui-common.h messages.h messages_i18n.h top-level.h ui-callbacks.h
noinst_HEADERS = AccWindow.h AdjBWindow.h Destroy.h EuroUtils.h FileBox.h FileDialog.h MainWindow.h MultiLedger.h RecnWindow.h Refresh.h RegWindow.h SplitLedger.h file-history.h gnc-exp-parser.h gnc-ui-common.h messages.h messages_i18n.h top-level.h ui-callbacks.h
EXTRA_DIST = .cvsignore
@ -140,7 +140,7 @@ EXTRA_DIST = .cvsignore
CFLAGS = @CFLAGS@ ${GLIB_CFLAGS} ${GNOME_CFLAGS} ${GUILE_COMPILE_ARGS}
INCLUDES = -I./engine -I./guile -I./register
INCLUDES = -I./calculation -I./engine -I./guile -I./register
mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
CONFIG_HEADER = ../config.h
@ -157,7 +157,7 @@ X_LIBS = @X_LIBS@
X_EXTRA_LIBS = @X_EXTRA_LIBS@
X_PRE_LIBS = @X_PRE_LIBS@
gnucash_OBJECTS = MultiLedger.o SplitLedger.o Destroy.o EuroUtils.o \
FileDialog.o Refresh.o
FileDialog.o Refresh.o gnc-exp-parser.o
gnucash_LDADD = $(LDADD)
gnucash_DEPENDENCIES = gnome/libgncgnome.a register/libgncregister.a \
register/gnome/libgncregistergnome.a guile/libgncguile.a \
@ -177,7 +177,8 @@ DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
TAR = gtar
GZIP_ENV = --best
DEP_FILES = .deps/Destroy.P .deps/EuroUtils.P .deps/FileDialog.P \
.deps/MultiLedger.P .deps/Refresh.P .deps/SplitLedger.P
.deps/MultiLedger.P .deps/Refresh.P .deps/SplitLedger.P \
.deps/gnc-exp-parser.P
SOURCES = $(gnucash_SOURCES)
OBJECTS = $(gnucash_OBJECTS)

View File

@ -25,16 +25,17 @@
* Author: Linas Vepstas (linas@linas.org) *
\********************************************************************/
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <locale.h>
#include <limits.h>
#include <ctype.h>
/* #include <glib.h> */
#include "config.h"
#include <ctype.h>
#include <errno.h>
#include <glib.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "messages.h"
#include "gnc-common.h"
#include "util.h"
@ -59,6 +60,10 @@ gncLogLevel loglevel[MOD_LAST + 1] =
GNC_LOG_WARNING, /* QUERY */
};
/* This static indicates the debugging module that this .o belongs to. */
static short module = MOD_ENGINE;
/* Set the logging level of the given module. */
void
gnc_set_log_level(gncModuleType module, gncLogLevel level)
@ -434,10 +439,12 @@ gnc_localeconv()
gnc_lconv_set(&lc.decimal_point, ".");
gnc_lconv_set(&lc.thousands_sep, ",");
gnc_lconv_set(&lc.grouping, "\003");
gnc_lconv_set(&lc.int_curr_symbol, "USD ");
gnc_lconv_set(&lc.currency_symbol, CURRENCY_SYMBOL);
gnc_lconv_set(&lc.mon_decimal_point, ".");
gnc_lconv_set(&lc.mon_thousands_sep, ",");
gnc_lconv_set(&lc.mon_grouping, "\003");
gnc_lconv_set(&lc.negative_sign, "-");
gnc_lconv_set_char(&lc.frac_digits, 2);
@ -821,176 +828,427 @@ xaccPrintAmountArgs (double val, gboolean print_currency_symbol,
* xaccParseAmount *
* parses amount strings using locale data *
* *
* Args: str -- pointer to string rep of num *
monetary -- boolean indicating whether value is monetary *
* Return: double -- the parsed amount *
* 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 *
\********************************************************************/
double xaccParseAmount (const char * instr, gboolean monetary)
/* Parsing state machine states */
typedef enum
{
struct lconv *lc = gnc_localeconv();
gboolean isneg = FALSE;
char *mstr, *str, *tok;
double amount = 0.0;
char negative_sign;
char thousands_sep;
char decimal_point;
int len, i;
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;
if (!instr) return 0.0;
if (*instr == '\0') return 0.0;
#define done_state(state) (((state) == DONE_ST) || ((state) == NO_NUM_ST))
mstr = strdup (instr);
str = mstr;
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;
}
negative_sign = lc->negative_sign[0];
if (monetary)
{
thousands_sep = lc->mon_thousands_sep[0];
decimal_point = lc->mon_decimal_point[0];
}
else
{
thousands_sep = lc->thousands_sep[0];
decimal_point = lc->decimal_point[0];
}
return 0.0;
}
/* strip off garbage at the beginning of the line */
while (*str != '\0')
{
switch (*str)
{
case '\r':
case '\n':
case ' ':
case '\t':
str++;
continue;
break;
}
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;
break;
}
ParseState state;
/* look for a negative sign */
if (*str == negative_sign) {
isneg = TRUE;
str++;
}
char negative_sign;
char decimal_point;
char group_separator;
const char *in;
char *out_str;
char *out;
if (*str == '\0') return 0.0;
/* Initialize *endstr to in_str */
if (endstr != NULL)
*endstr = (char *) in_str;
/* go to end of string */
for (tok = str; *tok != '\0'; tok++)
;
if (in_str == NULL)
return FALSE;
/* strip off garbage at end of the line */
while (--tok != str)
{
switch (*tok)
{
case '\r':
case '\n':
case ' ':
case '\t':
continue;
break;
}
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];
}
break;
}
/* '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);
/* look for a negative sign at the end, some locales allow it,
* we'll just allow it everywhere. */
if (*tok == negative_sign) {
isneg = TRUE;
*tok = '\0';
}
/* 'in' is used to traverse 'in_str'. */
in = in_str;
if (*str == '\0') return 0.0;
is_negative = FALSE;
got_decimal = FALSE;
group_data = NULL;
group_count = 0;
value = 0.0;
/* remove thousands separator */
tok = strchr (str, thousands_sep);
while (tok) {
*tok = '\0';
amount *= 1000.0;
amount += ((double) (1000 * atoi (str)));
str = tok + sizeof(char);
tok = strchr (str, thousands_sep);
}
/* Initialize the state machine */
state = START_ST;
/* search for a decimal point */
tok = strchr (str, decimal_point);
if (tok) {
*tok = '\0';
amount += ((double) (atoi (str)));
str = tok + sizeof(char);
/* This while loop implements a state machine for parsing numbers. */
while (TRUE)
{
ParseState next_state = state;
/* if there is anything trailing the decimal
* point, convert it */
if (str[0]) {
/* 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;
}
/* strip off garbage at end of the line */
tok = strchr (str, ' ');
if (tok) *tok = '\0';
break;
/* adjust for number of decimal places */
len = strlen(str);
/* 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;
}
if (len > 8)
{
str[8] = '\0';
len = 8;
}
break;
if (8 == len) {
amount += 0.00000001 * ((double) atoi (str));
} else
if (7 == len) {
amount += 0.0000001 * ((double) atoi (str));
} else
if (6 == len) {
amount += 0.000001 * ((double) atoi (str));
} else
if (5 == len) {
amount += 0.00001 * ((double) atoi (str));
} else
if (4 == len) {
amount += 0.0001 * ((double) atoi (str));
} else
if (3 == len) {
amount += 0.001 * ((double) atoi (str));
} else
if (2 == len) {
amount += 0.01 * ((double) atoi (str));
} else
if (1 == len) {
amount += 0.1 * ((double) atoi (str));
}
/* 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;
}
break;
/* 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;
}
break;
/* 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;
}
else if (next_state == FRAC_ST)
{
/* reset the out pointer to record the fraction */
out = out_str;
*out = '\0';
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;
}
} else if( auto_decimal_enabled ) {
/* 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.
*/
amount += ((double) (atoi (str)));
for( i = 0; i < auto_decimal_places; i++ )
amount *= 0.1;
/* 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;
}
/* NOTE: further additions to amount after this point will
* generate incorrect results.
*/
if (!good_grouping)
break;
}
} else {
amount += ((double) (atoi (str)));
}
g_list_free(group_data);
if (isneg) amount = -amount;
if (!good_grouping)
{
g_free(out_str);
return FALSE;
}
}
free (mstr);
return amount;
/* 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;
g_free (out_str);
return TRUE;
}

View File

@ -180,7 +180,6 @@ extern char *strcasestr(const char *str1, const char *str2);
* An alernate (better??) implementation might be
* #define strpskip(s,r) (s+strspn(s,r))
*/
extern char * strpskip (const char * s, const char *reject);
/********************************************************/
@ -188,7 +187,6 @@ extern char * strpskip (const char * s, const char *reject);
* It accepts a number and prints it in the indicated base.
* The returned string should be freed when done.
*/
char * ultostr (unsigned long val, int base);
/* Returns true if string s is a number, possibly
@ -268,8 +266,21 @@ const char * xaccPrintAmountArgs (double val,
gboolean is_shares_value,
const char *curr_code);
/* Parse i18n amount strings */
double xaccParseAmount (const char * instr, gboolean monetary);
/* xaccParseAmount parses in_str to obtain a numeric result. The
* routine will parse as much of in_str as it can to obtain a single
* number. The number is parsed using the current locale information
* and the 'monetary' flag. The routine will return TRUE if it
* successfully parsed a number and FALSE otherwise. If TRUE is
* returned and result is non-NULL, the value of the parsed number
* is stored in *result. If FALSE is returned, *result is
* unchanged. If TRUE is returned and endstr is non-NULL, the
* location of the first character in in_str not used by the
* parser will be returned in *endstr. If FALSE is returned
* and endstr is non-NULL, *endstr will point to in_str.
*/
gboolean xaccParseAmount (const char * in_str, gboolean monetary,
double *result, char **endstr);
/** TEMPLATES ******************************************************/

336
src/gnc-exp-parser.c Normal file
View File

@ -0,0 +1,336 @@
/********************************************************************\
* gnc-exp-parser.c -- Implementation of expression parsing for *
* GnuCash using the routines in 'calculation'. *
* Copyright (C) 2000 Dave Peticolas <dave@krondo.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, write to the Free Software *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
\********************************************************************/
#include "config.h"
#include <ctype.h>
#include "finproto.h"
#include "fin_spl_protos.h"
#include "gnc-exp-parser.h"
#include "messages.h"
#include "util.h"
/** Data Types *****************************************************/
typedef struct ParserNum
{
double value;
} ParserNum;
/** Static Globals *************************************************/
static GHashTable *variable_bindings = NULL;
static ParseError last_error = PARSER_NO_ERROR;
static gboolean parser_inited = FALSE;
/** Implementations ************************************************/
void
gnc_exp_parser_init (void)
{
if (parser_inited)
gnc_exp_parser_shutdown ();
variable_bindings = g_hash_table_new (g_str_hash, g_str_equal);
parser_inited = TRUE;
}
static gboolean
remove_binding (gpointer key, gpointer value, gpointer not_used)
{
g_free(key);
g_free(value);
return TRUE;
}
void
gnc_exp_parser_shutdown (void)
{
if (!parser_inited)
return;
g_hash_table_foreach_remove (variable_bindings, remove_binding, NULL);
g_hash_table_destroy (variable_bindings);
variable_bindings = NULL;
last_error = PARSER_NO_ERROR;
parser_inited = FALSE;
}
static void
prepend_name (gpointer key, gpointer value, gpointer data)
{
GList **list = data;
*list = g_list_prepend (*list, key);
}
GList *
gnc_exp_parser_get_variable_names (void)
{
GList *names = NULL;
if (!parser_inited)
return NULL;
g_hash_table_foreach (variable_bindings, prepend_name, &names);
return names;
}
void
gnc_exp_parser_remove_variable (const char *variable_name)
{
gpointer key;
gpointer value;
if (!parser_inited)
return;
if (variable_name == NULL)
return;
if (g_hash_table_lookup_extended (variable_bindings, variable_name,
&key, &value))
{
g_hash_table_remove (variable_bindings, key);
g_free(key);
g_free(value);
}
}
void
gnc_exp_parser_remove_variable_names (GList * variable_names)
{
if (!parser_inited)
return;
while (variable_names != NULL)
{
gnc_exp_parser_remove_variable (variable_names->data);
variable_names = variable_names->next;
}
}
gboolean
gnc_exp_parser_get_value (const char * variable_name, double *value_p)
{
ParserNum *pnum;
if (!parser_inited)
return FALSE;
if (variable_name == NULL)
return FALSE;
pnum = g_hash_table_lookup (variable_bindings, variable_name);
if (pnum == NULL)
return FALSE;
if (value_p != NULL)
*value_p = pnum->value;
return TRUE;
}
void
gnc_exp_parser_set_value (const char * variable_name, double value)
{
char *key;
ParserNum *pnum;
if (variable_name == NULL)
return;
if (!parser_inited)
gnc_exp_parser_init ();
gnc_exp_parser_remove_variable (variable_name);
key = g_strdup (variable_name);
pnum = g_new(ParserNum, 1);
pnum->value = value;
g_hash_table_insert (variable_bindings, key, pnum);
}
static void
make_predefined_vars_helper (gpointer key, gpointer value, gpointer data)
{
var_store_ptr *vars_p = data;
var_store_ptr var;
var = g_new0 (var_store, 1);
var->variable_name = key;
var->value = value;
var->next_var = *vars_p;
*vars_p = var;
}
static var_store_ptr
make_predefined_variables (void)
{
var_store_ptr vars = NULL;
g_hash_table_foreach (variable_bindings, make_predefined_vars_helper, &vars);
return vars;
}
static void *
trans_numeric(const char *digit_str,
char radix_point,
char group_char,
char **rstr)
{
ParserNum *pnum;
double value;
if (digit_str == NULL)
return NULL;
if (!xaccParseAmount (digit_str, TRUE, &value, rstr))
return NULL;
pnum = g_new0(ParserNum, 1);
pnum->value = value;
return pnum;
}
static void *
numeric_ops(char op_sym,
void *left_value,
void *right_value)
{
ParserNum *left = left_value;
ParserNum *right = right_value;
ParserNum *result;
if ((left == NULL) || (right == NULL))
return NULL;
result = (op_sym == ASN_OP) ? left : g_new(ParserNum, 1);
switch (op_sym)
{
case ADD_OP:
result->value = left->value + right->value;
break;
case SUB_OP:
result->value = left->value - right->value;
break;
case DIV_OP:
result->value = left->value / right->value;
break;
case MUL_OP:
result->value = left->value * right->value;
break;
case ASN_OP:
result->value = right->value;
break;
}
return result;
}
static void *
negate_numeric(void *value)
{
ParserNum *result = value;
if (value == NULL)
return NULL;
result->value = -result->value;
return result;
}
gboolean
gnc_exp_parser_parse (const char * expression, double *value_p,
char **error_loc_p)
{
parser_env_ptr pe;
var_store_ptr vars;
struct lconv *lc;
var_store result;
char * error_loc;
ParserNum *pnum;
if (expression == NULL)
return FALSE;
if (!parser_inited)
gnc_exp_parser_init ();
result.variable_name = NULL;
result.value = NULL;
result.next_var = NULL;
vars = make_predefined_variables ();
lc = gnc_localeconv ();
pe = init_parser (vars, *lc->mon_decimal_point, *lc->mon_thousands_sep,
trans_numeric, numeric_ops, negate_numeric, g_free);
error_loc = parse_string (&result, expression, pe);
pnum = result.value;
if (error_loc == NULL)
{
if ((value_p != NULL) && (pnum != NULL))
*value_p = pnum->value;
if (error_loc_p != NULL)
*error_loc_p = NULL;
last_error = PARSER_NO_ERROR;
}
else
{
if (error_loc_p != NULL)
*error_loc_p = error_loc;
last_error = get_parse_error (pe);
}
/* fixme: update variables and free predefined variables */
exit_parser (pe);
return (error_loc == NULL);
}
const char *
gnc_exp_parser_error_string (void)
{
return NULL;
}

71
src/gnc-exp-parser.h Normal file
View File

@ -0,0 +1,71 @@
/********************************************************************\
* gnc-exp-parser.h -- Interface to expression parsing for GnuCash *
* Copyright (C) 2000 Dave Peticolas <dave@krondo.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, write to the Free Software *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
\********************************************************************/
#ifndef __GNC_EXP_PARSER_H__
#define __GNC_EXP_PARSER_H__
#include "config.h"
#include <glib.h>
/* Initialize the expression parser. If this function is not
* called before one of the other parsing routines (other than
* gnc_exp_parser_shutdown), it will be called if needed. */
void gnc_exp_parser_init (void);
/* Shutdown the expression parser and free any associated memory. */
void gnc_exp_parser_shutdown (void);
/* Return a list of variable names which are currently defined
* in the parser. The names should not be modified or freed. */
GList * gnc_exp_parser_get_variable_names (void);
/* Undefine the variable name if it is already defined. */
void gnc_exp_parser_remove_variable (const char *variable_name);
/* Undefine every variable name appearing in the list. Variables in
* the list which are not defined are ignored. */
void gnc_exp_parser_remove_variable_names (GList * variable_names);
/* Return TRUE if the variable is defined, FALSE otherwise. If defined
* and value_p != NULL, return the value in *value_p, otherwise, *value_p
* is unchanged. */
gboolean gnc_exp_parser_get_value (const char * variable_name,
double *value_p);
/* Set the value of the variable to the given value. If the variable is
* not already defined, it will be after the call. */
void gnc_exp_parser_set_value (const char * variable_name, double value);
/* Parse the given expression using the current variable definitions.
* If the parse was successful, return TRUE and, if value_p is
* non-NULL, return the value of the resulting expression in *value_p.
* Otherwise, return FALSE and *value_p is unchanged. If FALSE is
* returned and error_loc_p is non-NULL, *error_loc_p is set to the
* character in expression where parsing aborted. If TRUE is returned
* and error_loc_p is non-NULL, *error_loc_p is set to NULL. */
gboolean gnc_exp_parser_parse (const char * expression, double *value_p,
char **error_loc_p);
/* If the last parse returned FALSE, return an error string describing
* the problem. Otherwise, return NULL. */
const char * gnc_exp_parser_error_string (void);
#endif

View File

@ -864,7 +864,8 @@ subentry_amount_entry_focus_out(GtkWidget *widget, GdkEventFocus *event,
if ((string == NULL) || (*string == '\0'))
return FALSE;
value = xaccParseAmount(string, TRUE);
value = 0.0;
xaccParseAmount(string, TRUE, &value, NULL);
new_string = xaccPrintAmount(value, PRTSEP, NULL);

View File

@ -170,16 +170,17 @@ gui_to_fi(FinCalcDialog *fcd)
fcd->financial_info.npp = strtol(string, NULL, 10);
string = gtk_entry_get_text(GTK_ENTRY(fcd->entries[INTEREST_RATE]));
fcd->financial_info.ir = xaccParseAmount(string, FALSE);
xaccParseAmount(string, FALSE, &fcd->financial_info.ir, NULL);
string = gtk_entry_get_text(GTK_ENTRY(fcd->entries[PRESENT_VALUE]));
fcd->financial_info.pv = xaccParseAmount(string, TRUE);
xaccParseAmount(string, TRUE, &fcd->financial_info.pv, NULL);
string = gtk_entry_get_text(GTK_ENTRY(fcd->entries[PERIODIC_PAYMENT]));
fcd->financial_info.pmt = xaccParseAmount(string, TRUE);
xaccParseAmount(string, TRUE, &fcd->financial_info.pmt, NULL);
string = gtk_entry_get_text(GTK_ENTRY(fcd->entries[FUTURE_VALUE]));
fcd->financial_info.fv = -xaccParseAmount(string, TRUE);
if (xaccParseAmount(string, TRUE, &fcd->financial_info.fv, NULL))
fcd->financial_info.fv = -fcd->financial_info.fv;
i = gnc_option_menu_get_active(fcd->compounding_menu);
fcd->financial_info.CF = periods[i];
@ -327,7 +328,8 @@ can_calc_value(FinCalcDialog *fcd, FinCalcValue value)
case PERIODIC_PAYMENT:
case FUTURE_VALUE:
string = gtk_entry_get_text(GTK_ENTRY(fcd->entries[INTEREST_RATE]));
dvalue = xaccParseAmount(string, FALSE);
dvalue = 0.0;
xaccParseAmount(string, FALSE, &dvalue, NULL);
if (DEQ(dvalue, 0.0))
return CALC_INTEREST_MSG;
break;

View File

@ -158,7 +158,8 @@ gnc_xfer_update_cb(GtkWidget *widget, GdkEventFocus *event, gpointer data)
if ((string == NULL) || (*string == 0))
return FALSE;
value = xaccParseAmount(string, TRUE);
value = 0.0;
xaccParseAmount(string, TRUE, &value, NULL);
currency = xaccAccountGetCurrency(account);
@ -325,7 +326,8 @@ gnc_xfer_dialog_ok_cb(GtkWidget * widget, gpointer data)
}
string = gtk_entry_get_text(GTK_ENTRY(xferData->amount_entry));
amount = xaccParseAmount(string, TRUE);
amount = 0.0;
xaccParseAmount(string, TRUE, &amount, NULL);
time = gnc_date_edit_get_date(GNC_DATE_EDIT(xferData->date_entry));

View File

@ -110,15 +110,16 @@ gnc_ui_AdjBWindow_cancel_cb(GtkWidget * widget, gpointer data)
static void
gnc_ui_AdjBWindow_ok_cb(GtkWidget * widget, gpointer data)
{
AdjBWindow *adjBData = (AdjBWindow *) data;
AdjBWindow *adjBData = data;
Transaction *trans;
Split *source_split;
time_t time;
double new_balance, current_balance;
double new_balance = 0.0;
double current_balance;
gchar * string;
time_t time;
string = gtk_entry_get_text(GTK_ENTRY(adjBData->balance_entry));
new_balance = xaccParseAmount(string, TRUE);
xaccParseAmount(string, TRUE, &new_balance, NULL);
if (gnc_reverse_balance(adjBData->account))
new_balance = -new_balance;
@ -164,7 +165,8 @@ gnc_adjust_update_cb(GtkWidget *widget, GdkEventFocus *event, gpointer data)
string = gtk_entry_get_text(entry);
value = xaccParseAmount(string, TRUE);
value = 0.0;
xaccParseAmount(string, TRUE, &value, NULL);
currency = xaccAccountGetCurrency(account);

View File

@ -269,7 +269,8 @@ gnc_start_recn_update_cb(GtkWidget *widget, GdkEventFocus *event,
string = gtk_entry_get_text(entry);
value = xaccParseAmount(string, TRUE);
value = 0.0;
xaccParseAmount(string, TRUE, &value, NULL);
account_type = xaccAccountGetType(account);
if ((account_type == STOCK) || (account_type == MUTUAL) ||
@ -413,7 +414,8 @@ startRecnWindow(GtkWidget *parent, Account *account,
string = gtk_entry_get_text(GTK_ENTRY(end_value));
*new_ending = xaccParseAmount(string, TRUE);
*new_ending = 0.0;
xaccParseAmount(string, TRUE, new_ending, NULL);
*statement_date = gnc_date_edit_get_date(GNC_DATE_EDIT(date_value));
if (gnc_reverse_balance(account))

View File

@ -131,8 +131,8 @@ PriceMV (BasicCell *_cell,
if (1 < count) return NULL;
}
/* parse the float pt value and store it */
cell->amount = xaccParseAmount (newval, cell->monetary);
/* parse the value and store it */
xaccParseAmount (newval, cell->monetary, &cell->amount, NULL);
SET ((&(cell->cell)), newval);
return newval;
}
@ -144,7 +144,17 @@ PriceLeave (BasicCell *_cell, const char *val)
{
PriceCell *cell = (PriceCell *) _cell;
char *newval;
double amount;
if (val == NULL)
val = "";
if (*val == '\0')
amount = 0.0;
else if (!xaccParseAmount (val, cell->monetary, &amount, NULL))
amount = 0.0;
cell->amount = amount;
newval = xaccPriceCellPrintValue(cell);
/* If they are identical, return the original */
@ -168,7 +178,10 @@ PriceHelp (BasicCell *bcell)
{
char *help_str;
help_str = xaccPriceCellPrintValue(cell);
if (xaccParseAmount(bcell->value, cell->monetary, NULL, NULL))
help_str = xaccPriceCellPrintValue(cell);
else
help_str = bcell->value;
return g_strdup(help_str);
}
@ -371,12 +384,10 @@ PriceSetValue (BasicCell *_cell, const char *str)
if (str == NULL)
str = "";
if (!cell->blank_zero && (*str == '\0'))
xaccSetPriceCellBlank(cell);
else {
amount = xaccParseAmount (str, cell->monetary);
xaccSetPriceCellValue (cell, amount);
}
if (*str == '\0')
xaccSetPriceCellValue (cell, 0.0);
else if (xaccParseAmount (str, cell->monetary, &amount, NULL))
xaccSetPriceCellValue (cell, amount);
}
/* --------------- end of file ---------------------- */