You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gnucash/lib/goffice/split/value.c

1830 lines
40 KiB

/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* value.c: Utilies for handling, creating, removing values.
*
* Authors:
* Miguel de Icaza (miguel@gnu.org).
* Michael Meeks (mmeeks@gnu.org)
* Jody Goldberg (jgolderg@home.com)
*/
#include <config.h>
#include <glib/gi18n.h>
#include "gnumeric.h"
#include "value.h"
//#include "parse-util.h"
#include "style.h"
#include "format.h"
#include "str.h"
#include "position.h"
#include "mathfunc.h"
#include "gutils.h"
//#include "workbook.h"
#include "split.h"
#include <stdlib.h>
#include <errno.h>
#include <math.h>
#include <string.h>
#include <ranges.h>
//#include <sheet.h>
//#include <cell.h>
#include <number-match.h>
#ifndef USE_VALUE_POOLS
#define USE_VALUE_POOLS 1
#endif
#if USE_VALUE_POOLS
#define CHUNK_ALLOC(T,p) ((T*)gnm_mem_chunk_alloc (p))
#define CHUNK_FREE(p,v) gnm_mem_chunk_free ((p), (v))
#else
#define CHUNK_ALLOC(T,c) g_new (T,1)
#define CHUNK_FREE(p,v) g_free ((v))
#endif
static struct {
char const *C_name;
char const *locale_name;
GnmString *locale_name_str;
} standard_errors[] = {
{ N_("#NULL!"), NULL, NULL },
{ N_("#DIV/0!"), NULL, NULL },
{ N_("#VALUE!"), NULL, NULL },
{ N_("#REF!"), NULL, NULL },
{ N_("#NAME?"), NULL, NULL },
{ N_("#NUM!"), NULL, NULL },
{ N_("#N/A"), NULL, NULL },
{ N_("#RECALC!"), NULL, NULL },
{ N_("#UNKNOWN!"), NULL, NULL }
};
GnmValue *
value_new_empty (void)
{
/* This is a constant. No need to allocate any memory. */
static GnmValueAny v = { VALUE_EMPTY, NULL };
return (GnmValue *)&v;
}
/* Memory pool for ints and bools. */
static GnmMemChunk *value_int_pool;
GnmValue *
value_new_bool (gboolean b)
{
GnmValueBool *v = CHUNK_ALLOC (GnmValueBool, value_int_pool);
*((GnmValueType *)&(v->type)) = VALUE_BOOLEAN;
v->fmt = NULL;
v->val = b;
return (GnmValue *)v;
}
GnmValue *
value_new_int (int i)
{
GnmValueInt *v = CHUNK_ALLOC (GnmValueInt, value_int_pool);
*((GnmValueType *)&(v->type)) = VALUE_INTEGER;
v->fmt = NULL;
v->val = i;
return (GnmValue *)v;
}
static GnmMemChunk *value_float_pool;
GnmValue *
value_new_float (gnm_float f)
{
if (finitegnum (f)) {
GnmValueFloat *v = CHUNK_ALLOC (GnmValueFloat, value_float_pool);
*((GnmValueType *)&(v->type)) = VALUE_FLOAT;
v->fmt = NULL;
v->val = f;
return (GnmValue *)v;
} else {
/* FIXME: bogus ep sent here. What to do? */
return value_new_error_NUM (NULL);
}
}
/* Memory pool for error values. */
static GnmMemChunk *value_error_pool;
GnmValue *
value_new_error (GnmEvalPos const *ep, char const *mesg)
{
GnmValueErr *v = CHUNK_ALLOC (GnmValueErr, value_error_pool);
*((GnmValueType *)&(v->type)) = VALUE_ERROR;
v->fmt = NULL;
v->mesg = gnm_string_get (mesg);
return (GnmValue *)v;
}
GnmValue *
value_new_error_str (GnmEvalPos const *ep, GnmString *mesg)
{
GnmValueErr *v = CHUNK_ALLOC (GnmValueErr, value_error_pool);
*((GnmValueType *)&(v->type)) = VALUE_ERROR;
v->fmt = NULL;
v->mesg = gnm_string_ref (mesg);
return (GnmValue *)v;
}
GnmValue *
value_new_error_std (GnmEvalPos const *pos, GnmStdError err)
{
size_t i = (size_t)err;
g_return_val_if_fail (i < G_N_ELEMENTS (standard_errors), NULL);
return value_new_error_str (pos, standard_errors[i].locale_name_str);
}
GnmValue *
value_new_error_NULL (GnmEvalPos const *pos)
{
return value_new_error_str (pos, standard_errors[GNM_ERROR_NULL].locale_name_str);
}
GnmValue *
value_new_error_DIV0 (GnmEvalPos const *pos)
{
return value_new_error_str (pos, standard_errors[GNM_ERROR_DIV0].locale_name_str);
}
GnmValue *
value_new_error_VALUE (GnmEvalPos const *pos)
{
return value_new_error_str (pos, standard_errors[GNM_ERROR_VALUE].locale_name_str);
}
GnmValue *
value_new_error_REF (GnmEvalPos const *pos)
{
return value_new_error_str (pos, standard_errors[GNM_ERROR_REF].locale_name_str);
}
GnmValue *
value_new_error_NAME (GnmEvalPos const *pos)
{
return value_new_error_str (pos, standard_errors[GNM_ERROR_NAME].locale_name_str);
}
GnmValue *
value_new_error_NUM (GnmEvalPos const *pos)
{
return value_new_error_str (pos, standard_errors[GNM_ERROR_NUM].locale_name_str);
}
GnmValue *
value_new_error_NA (GnmEvalPos const *pos)
{
return value_new_error_str (pos, standard_errors[GNM_ERROR_NA].locale_name_str);
}
GnmValue *
value_new_error_RECALC (GnmEvalPos const *pos)
{
return value_new_error_str (pos, standard_errors[GNM_ERROR_RECALC].locale_name_str);
}
char const *
value_error_name (GnmStdError err, gboolean translated)
{
size_t i = (size_t)err;
g_return_val_if_fail (i < G_N_ELEMENTS (standard_errors), NULL);
if (translated)
return standard_errors[i].locale_name;
else
return standard_errors[i].C_name;
}
/**
* value_error_set_pos :
* @err :
* @pos :
*
* Change the position of a ValueError.
*/
GnmValue *
value_error_set_pos (GnmValueErr *err, GnmEvalPos const *pos)
{
g_return_val_if_fail (err != NULL, NULL);
g_return_val_if_fail (err->type == VALUE_ERROR, NULL);
err->src = *pos;
return (GnmValue *)err;
}
GnmStdError
value_error_classify (GnmValue const *v)
{
size_t i;
g_return_val_if_fail (v != NULL, GNM_ERROR_UNKNOWN);
if (v->type != VALUE_ERROR)
return GNM_ERROR_UNKNOWN;
for (i = 0; i < G_N_ELEMENTS (standard_errors); i++)
if (standard_errors[i].locale_name_str == v->v_err.mesg)
return (GnmStdError)i;
return GNM_ERROR_UNKNOWN;
}
static GnmMemChunk *value_string_pool;
/* NOTE : absorbs the reference */
GnmValue *
value_new_string_str (GnmString *str)
{
GnmValueStr *v = CHUNK_ALLOC (GnmValueStr, value_string_pool);
*((GnmValueType *)&(v->type)) = VALUE_STRING;
v->fmt = NULL;
v->val = str;
return (GnmValue *)v;
}
GnmValue *
value_new_string (char const *str)
{
return value_new_string_str (gnm_string_get (str));
}
GnmValue *
value_new_string_nocopy (char *str)
{
return value_new_string_str (gnm_string_get_nocopy (str));
}
static GnmMemChunk *value_range_pool;
GnmValue *
value_new_cellrange_unsafe (GnmCellRef const *a, GnmCellRef const *b)
{
GnmValueRange *v = CHUNK_ALLOC (GnmValueRange, value_range_pool);
*((GnmValueType *)&(v->type)) = VALUE_CELLRANGE;
v->fmt = NULL;
v->cell.a = *a;
v->cell.b = *b;
return (GnmValue *)v;
}
/**
* value_new_cellrange : Create a new range reference.
*
* Attempt to do a sanity check for inverted ranges.
* NOTE : This is no longer necessary and will be removed.
* mixed mode references create the possibility of inversion.
* users of these values need to use the utility routines to
* evaluate the ranges in their context and normalize then.
*/
GnmValue *
value_new_cellrange (GnmCellRef const *a, GnmCellRef const *b,
int eval_col, int eval_row)
{
GnmValueRange *v = CHUNK_ALLOC (GnmValueRange, value_range_pool);
int tmp;
*((GnmValueType *)&(v->type)) = VALUE_CELLRANGE;
v->fmt = NULL;
v->cell.a = *a;
v->cell.b = *b;
/* Sanity checking to avoid inverted ranges */
tmp = a->col;
if (a->col_relative != b->col_relative) {
/* Make a tmp copy of a in the same mode as b */
if (a->col_relative)
tmp += eval_col;
else
tmp -= eval_col;
}
if (tmp > b->col) {
v->cell.a.col = b->col;
v->cell.a.col_relative = b->col_relative;
v->cell.b.col = a->col;
v->cell.b.col_relative = a->col_relative;
}
tmp = a->row;
if (a->row_relative != b->row_relative) {
/* Make a tmp copy of a in the same mode as b */
if (a->row_relative)
tmp += eval_row;
else
tmp -= eval_row;
}
if (tmp > b->row) {
v->cell.a.row = b->row;
v->cell.a.row_relative = b->row_relative;
v->cell.b.row = a->row;
v->cell.b.row_relative = a->row_relative;
}
return (GnmValue *)v;
}
GnmValue *
value_new_cellrange_r (Sheet *sheet, GnmRange const *r)
{
GnmValueRange *v = CHUNK_ALLOC (GnmValueRange, value_range_pool);
GnmCellRef *a, *b;
*((GnmValueType *)&(v->type)) = VALUE_CELLRANGE;
v->fmt = NULL;
a = &v->cell.a;
b = &v->cell.b;
a->sheet = sheet;
b->sheet = sheet;
a->col = r->start.col;
a->row = r->start.row;
b->col = r->end.col;
b->row = r->end.row;
a->col_relative = b->col_relative = FALSE;
a->row_relative = b->row_relative = FALSE;
return (GnmValue *)v;
}
static GnmMemChunk *value_array_pool;
GnmValue *
value_new_array_non_init (guint cols, guint rows)
{
GnmValueArray *v = CHUNK_ALLOC (GnmValueArray, value_array_pool);
*((GnmValueType *)&(v->type)) = VALUE_ARRAY;
v->fmt = NULL;
v->x = cols;
v->y = rows;
v->vals = g_new (GnmValue **, cols);
return (GnmValue *)v;
}
GnmValue *
value_new_array (guint cols, guint rows)
{
guint x, y;
GnmValueArray *v = (GnmValueArray *)value_new_array_non_init (cols, rows);
for (x = 0; x < cols; x++) {
v->vals[x] = g_new (GnmValue *, rows);
for (y = 0; y < rows; y++)
v->vals[x][y] = value_new_int (0);
}
return (GnmValue *)v;
}
GnmValue *
value_new_array_empty (guint cols, guint rows)
{
guint x, y;
GnmValueArray *v = (GnmValueArray *)value_new_array_non_init (cols, rows);
for (x = 0; x < cols; x++) {
v->vals[x] = g_new (GnmValue *, rows);
for (y = 0; y < rows; y++)
v->vals[x][y] = NULL;
}
return (GnmValue *)v;
}
GnmValue *
value_new_from_string (GnmValueType t, char const *str, GnmFormat *sf,
gboolean translated)
{
GnmValue *res = NULL;
switch (t) {
case VALUE_EMPTY:
res = value_new_empty ();
break;
case VALUE_BOOLEAN:
if (translated) {
/* FIXME: ascii??? */
if (0 == g_ascii_strcasecmp (str, format_boolean (TRUE)))
res = value_new_bool (TRUE);
else if (0 == g_ascii_strcasecmp (str, format_boolean (FALSE)))
res = value_new_bool (FALSE);
} else {
if (0 == g_ascii_strcasecmp (str, "TRUE"))
res = value_new_bool (TRUE);
else if (0 == g_ascii_strcasecmp (str, "FALSE"))
res = value_new_bool (FALSE);
}
break;
case VALUE_INTEGER: {
char *end;
long l;
errno = 0;
l = strtol (str, &end, 10);
if (str != end && *end == '\0' && errno != ERANGE)
res = value_new_int ((int)l);
break;
}
case VALUE_FLOAT: {
char *end;
gnm_float d;
errno = 0;
d = strtognum (str, &end);
if (str != end && *end == '\0' && errno != ERANGE)
res = value_new_float (d);
break;
}
case VALUE_ERROR:
/*
* Tricky. We are currently storing errors in translated
* format, so we might have to undo that.
*/
if (!translated) {
size_t i;
for (i = 0; i < G_N_ELEMENTS (standard_errors); i++)
if (strcmp (standard_errors[i].C_name, str) == 0) {
res = value_new_error_std (NULL, (GnmStdError)i);
break;
}
}
if (!res)
res = value_new_error (NULL, str);
break;
case VALUE_STRING:
res = value_new_string (str);
break;
/* Should not happen. */
case VALUE_ARRAY:
case VALUE_CELLRANGE:
default:
g_warning ("value_new_from_string problem.");
return NULL;
}
if (res)
value_set_fmt (res, sf);
return res;
}
void
value_release (GnmValue *value)
{
g_return_if_fail (value != NULL);
if (VALUE_FMT (value) != NULL)
style_format_unref (VALUE_FMT (value));
switch (value->type) {
case VALUE_EMPTY:
/* We did not allocate anything, there is nothing to free */
return;
case VALUE_BOOLEAN:
case VALUE_INTEGER:
CHUNK_FREE (value_int_pool, value);
return;
case VALUE_FLOAT:
CHUNK_FREE (value_float_pool, value);
return;
case VALUE_ERROR:
/* Do not release VALUE_TERMINATE, it is a magic number */
if (value == VALUE_TERMINATE) {
g_warning ("Someone freed VALUE_TERMINATE -- shame on them.");
return;
}
gnm_string_unref (value->v_err.mesg);
CHUNK_FREE (value_error_pool, value);
return;
case VALUE_STRING:
gnm_string_unref (value->v_str.val);
CHUNK_FREE (value_string_pool, value);
return;
case VALUE_ARRAY: {
GnmValueArray *v = (GnmValueArray *)value;
int x, y;
for (x = 0; x < v->x; x++) {
for (y = 0; y < v->y; y++) {
if (v->vals[x][y])
value_release (v->vals[x][y]);
}
g_free (v->vals[x]);
}
g_free (v->vals);
CHUNK_FREE (value_array_pool, value);
return;
}
case VALUE_CELLRANGE:
CHUNK_FREE (value_range_pool, value);
return;
default:
/*
* If we don't recognize the type this is probably garbage.
* Do not free it to avoid heap corruption
*/
g_warning ("value_release problem.");
return;
}
g_assert_not_reached ();
}
/**
* value_dup :
* @src : #GnmValue
*
* Returns a copy of @src. @src == NULL will return NULL
**/
GnmValue *
value_dup (GnmValue const *src)
{
GnmValue *res;
if (src == NULL)
return NULL;
switch (src->type){
case VALUE_EMPTY:
res = value_new_empty ();
break;
case VALUE_BOOLEAN:
res = value_new_bool (src->v_bool.val);
break;
case VALUE_INTEGER:
res = value_new_int (src->v_int.val);
break;
case VALUE_FLOAT:
res = value_new_float (src->v_float.val);
break;
case VALUE_ERROR:
res = value_new_error_str (&src->v_err.src,
src->v_err.mesg);
break;
case VALUE_STRING:
gnm_string_ref (src->v_str.val);
res = value_new_string_str (src->v_str.val);
break;
case VALUE_CELLRANGE:
res = value_new_cellrange_unsafe (&src->v_range.cell.a,
&src->v_range.cell.b);
break;
case VALUE_ARRAY: {
int x, y;
GnmValueArray *array = (GnmValueArray *)value_new_array_non_init (
src->v_array.x, src->v_array.y);
for (x = 0; x < array->x; x++) {
array->vals[x] = g_new (GnmValue *, array->y);
for (y = 0; y < array->y; y++)
array->vals[x][y] = value_dup (src->v_array.vals[x][y]);
}
res = (GnmValue *)array;
break;
}
default:
g_warning ("value_dup problem.");
res = value_new_empty ();
}
value_set_fmt (res, VALUE_FMT (src));
return res;
}
/**
* value_cmp :
* @ptr_a :
* @ptr_b :
*
* qsort style comparison function.
**/
int
value_cmp (void const *ptr_a, void const *ptr_b)
{
GnmValue const *a = *(GnmValue const **)ptr_a;
GnmValue const *b = *(GnmValue const **)ptr_b;
switch (value_compare (a, b, TRUE)) {
case IS_EQUAL : return 0;
case IS_LESS : return -1;
case IS_GREATER : return 1;
default :
break;
};
return a->type - b->type;
}
int
value_cmp_reverse (void const *ptr_a, void const *ptr_b)
{
GnmValue const *a = *(GnmValue const **)ptr_a;
GnmValue const *b = *(GnmValue const **)ptr_b;
switch (value_compare (a, b, TRUE)) {
case IS_EQUAL : return 0;
case IS_LESS : return 1;
case IS_GREATER : return -1;
default :
break;
};
return b->type - a->type;
}
gint
value_equal (GnmValue const *a, GnmValue const *b)
{
if (a->type != b->type)
return FALSE;
switch (a->type) {
case VALUE_BOOLEAN:
return a->v_bool.val == b->v_bool.val;
case VALUE_STRING:
return a->v_str.val == b->v_str.val;
case VALUE_ERROR:
return a->v_err.mesg == b->v_err.mesg;
case VALUE_INTEGER:
return a->v_int.val == b->v_int.val;
case VALUE_FLOAT:
return a->v_float.val == b->v_float.val;
case VALUE_EMPTY:
return TRUE;
/*
case VALUE_CELLRANGE:
return cellref_equal (&a->v_range.cell.a, &b->v_range.cell.a) &&
cellref_equal (&a->v_range.cell.b, &b->v_range.cell.b);
*/
case VALUE_ARRAY:
if (a->v_array.x == b->v_array.x && a->v_array.y == b->v_array.y) {
int x, y;
for (y = 0; y < a->v_array.y; y++)
for (x = 0; x < a->v_array.x; x++)
if (!value_equal (a->v_array.vals[x][y],
b->v_array.vals[x][y]))
return FALSE;
return TRUE;
} else
return FALSE;
#ifndef DEBUG_SWITCH_ENUM
default:
g_assert_not_reached ();
return FALSE;
#endif
}
}
guint
value_hash (GnmValue const *v)
{
switch (v->type) {
case VALUE_BOOLEAN:
return v->v_bool.val ? 0x555aaaa : 0xaaa5555;
case VALUE_STRING:
return g_str_hash (v->v_str.val->str);
case VALUE_ERROR:
return g_str_hash (v->v_err.mesg->str);
case VALUE_INTEGER:
return (guint)(v->v_int.val);
case VALUE_FLOAT: {
int expt;
gnm_float mant = frexpgnum (gnumabs (v->v_float.val), &expt);
guint h = ((guint)(0x80000000u * mant)) ^ expt;
if (v->v_float.val >= 0)
h ^= 0x55555555;
return h;
}
case VALUE_EMPTY:
return 42;
//case VALUE_CELLRANGE:
/* FIXME: take sheet into account? */
/*
return (cellref_hash (&v->v_range.cell.a) * 3) ^
cellref_hash (&v->v_range.cell.b);
*/
//return 42;
case VALUE_ARRAY: {
int i;
guint h = (v->v_array.x * 257) ^ (v->v_array.y + 42);
/* For speed, just walk the diagonal. */
for (i = 0; i < v->v_array.x && i < v->v_array.y; i++) {
h *= 5;
if (v->v_array.vals[i][i])
h ^= value_hash (v->v_array.vals[i][i]);
}
return h;
}
#ifndef DEBUG_SWITCH_ENUM
default:
g_assert_not_reached ();
return 0;
#endif
}
}
gboolean
value_get_as_bool (GnmValue const *v, gboolean *err)
{
*err = FALSE;
if (v == NULL)
return FALSE;
switch (v->type) {
case VALUE_EMPTY:
return FALSE;
case VALUE_BOOLEAN:
return v->v_bool.val;
case VALUE_STRING:
return v->v_str.val->str[0] != '\0';
case VALUE_INTEGER:
return v->v_int.val != 0;
case VALUE_FLOAT:
return v->v_float.val != 0.0;
default:
g_warning ("Unhandled value in value_get_boolean.");
case VALUE_CELLRANGE:
case VALUE_ARRAY:
case VALUE_ERROR:
*err = TRUE;
}
return FALSE;
}
/*
* use only if you are sure the value is ok
*/
gboolean
value_get_as_checked_bool (GnmValue const *v)
{
gboolean result, err;
result = value_get_as_bool (v, &err);
g_return_val_if_fail (!err, FALSE);
return result;
}
void
value_get_as_gstring (GnmValue const *v, GString *target,
GnmExprConventions const *conv)
{
if (v == NULL)
return;
switch (v->type){
case VALUE_EMPTY:
return;
case VALUE_ERROR: {
GnmStdError e = value_error_classify (v);
if (e == GNM_ERROR_UNKNOWN) {
g_string_append_c (target, '#');
gnm_strescape (target, v->v_err.mesg->str);
} else
g_string_append (target, value_error_name (e, conv->output_translated));
return;
}
case VALUE_BOOLEAN: {
gboolean b = v->v_bool.val;
g_string_append (target,
conv->output_translated
? format_boolean (b)
: (b ? "TRUE" : "FALSE"));
return;
}
case VALUE_STRING:
g_string_append (target, v->v_str.val->str);
return;
case VALUE_INTEGER:
g_string_append_printf (target, "%d", v->v_int.val);
return;
case VALUE_FLOAT:
g_string_append_printf (target, "%.*" GNUM_FORMAT_g, GNUM_DIG,
v->v_float.val);
return;
case VALUE_ARRAY: {
char const *row_sep, *col_sep;
char locale_arg_sep[2], locale_col_sep[2];
int x, y;
if (conv->output_argument_sep)
row_sep = conv->output_argument_sep;
else {
locale_arg_sep[0] = format_get_arg_sep ();
locale_arg_sep[1] = 0;
row_sep = locale_arg_sep;
}
if (conv->output_array_col_sep)
col_sep = conv->output_array_col_sep;
else {
locale_col_sep[0] = format_get_col_sep ();
locale_col_sep[1] = 0;
col_sep = locale_col_sep;
}
g_string_append_c (target, '{');
for (y = 0; y < v->v_array.y; y++){
if (y)
g_string_append (target, col_sep);
for (x = 0; x < v->v_array.x; x++){
GnmValue const *val = v->v_array.vals[x][y];
if (x)
g_string_append (target, row_sep);
/* quote strings */
if (val->type == VALUE_STRING)
gnm_strescape (target, val->v_str.val->str);
else
value_get_as_gstring (val, target, conv);
}
}
g_string_append_c (target, '}');
return;
}
#if 0
case VALUE_CELLRANGE: {
char *tmp;
/* Note: this makes only sense for absolute references or
* references relative to A1
*/
GnmRange range;
range_init_value (&range, v);
tmp = global_range_name (v->v_range.cell.a.sheet, &range);
g_string_append (target, tmp);
g_free (tmp);
return;
}
#endif // 0
default:
break;
}
g_assert_not_reached ();
}
/**
* value_get_as_string :
* @v :
*
* simplistic value rendering
*
* Returns a string that must be freed.
*/
char *
value_get_as_string (GnmValue const *v)
{
GString *res = g_string_sized_new (10);
value_get_as_gstring (v, res, gnm_expr_conventions_default);
return g_string_free (res, FALSE);
}
/*
* Result will stay valid until (a) the value is disposed of, or (b) two
* further calls to this function are made.
*/
char const *
value_peek_string (GnmValue const *v)
{
g_return_val_if_fail (v, "");
if (v->type == VALUE_STRING)
return v->v_str.val->str;
else if (v->type == VALUE_ERROR)
return v->v_err.mesg->str;
else {
static char *cache[2] = { NULL, NULL };
static int next = 0;
char const *s;
g_free (cache[next]);
s = cache[next] = value_get_as_string (v);
next = (next + 1) % G_N_ELEMENTS (cache);
return s;
}
}
/*
* FIXME FIXME FIXME : Support errors
*/
int
value_get_as_int (GnmValue const *v)
{
if (v == NULL)
return 0;
switch (v->type) {
case VALUE_EMPTY:
return 0;
case VALUE_STRING:
return atoi (v->v_str.val->str);
case VALUE_CELLRANGE:
g_warning ("Getting range as a int: what to do?");
return 0;
case VALUE_INTEGER:
return v->v_int.val;
case VALUE_ARRAY:
return 0;
case VALUE_FLOAT:
return (int) gnumeric_fake_trunc (v->v_float.val);
case VALUE_BOOLEAN:
return v->v_bool.val ? 1 : 0;
case VALUE_ERROR:
return 0;
default:
g_warning ("value_get_as_int unknown type.");
return 0;
}
return 0;
}
/*
* FIXME FIXME FIXME : Support errors
*/
gnm_float
value_get_as_float (GnmValue const *v)
{
if (v == NULL)
return 0.;
switch (v->type) {
case VALUE_EMPTY:
return 0.;
case VALUE_STRING:
return strtognum (v->v_str.val->str, NULL);
case VALUE_CELLRANGE:
g_warning ("Getting range as a double: what to do?");
return 0.0;
case VALUE_INTEGER:
return (gnm_float) v->v_int.val;
case VALUE_ARRAY:
return 0.0;
case VALUE_FLOAT:
return (gnm_float) v->v_float.val;
case VALUE_BOOLEAN:
return v->v_bool.val ? 1. : 0.;
case VALUE_ERROR:
return 0.;
default:
g_warning ("value_get_as_float type error.");
break;
}
return 0.0;
}
GnmRangeRef const *
value_get_rangeref (GnmValue const *v)
{
g_return_val_if_fail (v->type == VALUE_CELLRANGE, NULL);
return &v->v_range.cell;
}
#if 0
// -- jsled
/**
* value_coerce_to_number :
* @v :
* @valid :
*
* If the value can be used as a number return that number
* otherwise free it at return an appropriate error
**/
GnmValue *
value_coerce_to_number (GnmValue *v, gboolean *valid, GnmEvalPos const *ep)
{
g_return_val_if_fail (v != NULL, NULL);
*valid = FALSE;
if (v->type == VALUE_STRING) {
//GnmValue *tmp = format_match_number (value_peek_string (v), NULL,
//workbook_date_conv (ep->sheet->workbook));
GnmValue *tmp = format_match_number (value_peek_string (v), NULL,
workbook_date_conv (NULL));
value_release (v);
if (tmp == NULL)
return value_new_error_VALUE (ep);
v = tmp;
} else if (v->type == VALUE_ERROR)
return v;
if (!VALUE_IS_NUMBER (v)) {
value_release (v);
return value_new_error_VALUE (ep);
}
*valid = TRUE;
return v;
}
#endif // 0
void
value_array_set (GnmValue *array, int col, int row, GnmValue *v)
{
g_return_if_fail (v);
g_return_if_fail (array->type == VALUE_ARRAY);
g_return_if_fail (col>=0);
g_return_if_fail (row>=0);
g_return_if_fail (array->v_array.y > row);
g_return_if_fail (array->v_array.x > col);
if (array->v_array.vals[col][row] != NULL)
value_release (array->v_array.vals[col][row]);
array->v_array.vals[col][row] = v;
}
void
value_array_resize (GnmValue *v, int width, int height)
{
int x, y, xcpy, ycpy;
GnmValue *newval;
GnmValue ***tmp;
g_warning ("Totally untested");
g_return_if_fail (v);
g_return_if_fail (v->type == VALUE_ARRAY);
newval = value_new_array (width, height);
xcpy = MIN (width, v->v_array.x);
ycpy = MIN (height, v->v_array.y);
for (x = 0; x < xcpy; x++)
for (y = 0; y < ycpy; y++) {
value_array_set (newval, x, y, v->v_array.vals[x][y]);
v->v_array.vals[x][y] = NULL;
}
tmp = v->v_array.vals;
v->v_array.vals = newval->v_array.vals;
newval->v_array.vals = tmp;
newval->v_array.x = v->v_array.x;
newval->v_array.y = v->v_array.y;
v->v_array.x = width;
v->v_array.y = height;
value_release (newval);
}
static GnmValDiff
compare_bool_bool (GnmValue const *va, GnmValue const *vb)
{
gboolean err; /* Ignored */
gboolean const a = value_get_as_bool (va, &err);
gboolean const b = value_get_as_bool (vb, &err);
if (a)
return b ? IS_EQUAL : IS_GREATER;
return b ? IS_LESS : IS_EQUAL;
}
static GnmValDiff
compare_int_int (GnmValue const *va, GnmValue const *vb)
{
int const a = value_get_as_int (va);
int const b = value_get_as_int (vb);
if (a == b)
return IS_EQUAL;
else if (a < b)
return IS_LESS;
else
return IS_GREATER;
}
static GnmValDiff
compare_float_float (GnmValue const *va, GnmValue const *vb)
{
gnm_float const a = value_get_as_float (va);
gnm_float const b = value_get_as_float (vb);
if (a == b)
return IS_EQUAL;
else if (a < b)
return IS_LESS;
else
return IS_GREATER;
}
/**
* value_diff :
*
* @a : value a
* @b : value b
*
* IGNORES format.
*
* Returns a non-negative difference between 2 values
*/
gnm_float
value_diff (GnmValue const *a, GnmValue const *b)
{
GnmValueType ta, tb;
/* Handle trivial and double NULL case */
if (a == b)
return 0.;
ta = VALUE_IS_EMPTY (a) ? VALUE_EMPTY : a->type;
tb = VALUE_IS_EMPTY (b) ? VALUE_EMPTY : b->type;
/* string > empty */
if (ta == VALUE_STRING) {
switch (tb) {
/* Strings are > (empty, or number) */
case VALUE_EMPTY :
if (*a->v_str.val->str == '\0')
return 0.;
return DBL_MAX;
/* If both are strings compare as string */
case VALUE_STRING :
{
gint t = g_utf8_collate (a->v_str.val->str, b->v_str.val->str);
if (t == 0)
return 0.;
}
case VALUE_INTEGER : case VALUE_FLOAT : case VALUE_BOOLEAN :
default :
return DBL_MAX;
}
} else if (tb == VALUE_STRING) {
switch (ta) {
/* (empty, or number) < String */
case VALUE_EMPTY :
if (*b->v_str.val->str == '\0')
return 0.;
case VALUE_INTEGER : case VALUE_FLOAT : case VALUE_BOOLEAN :
default :
return DBL_MAX;
}
}
/* Booleans > all numbers (Why did excel do this ?? ) */
if (ta == VALUE_BOOLEAN && (tb == VALUE_INTEGER || tb == VALUE_FLOAT))
return DBL_MAX;
if (tb == VALUE_BOOLEAN && (ta == VALUE_INTEGER || ta == VALUE_FLOAT))
return DBL_MAX;
switch ((ta > tb) ? ta : tb) {
case VALUE_EMPTY: /* Empty Empty compare */
return 0.;
case VALUE_BOOLEAN:
return (compare_bool_bool (a, b) == IS_EQUAL) ? 0. : DBL_MAX;
case VALUE_INTEGER: {
int const ia = value_get_as_int (a);
int const ib = value_get_as_int (b);
return abs (ia - ib);
}
case VALUE_FLOAT: {
gnm_float const da = value_get_as_float (a);
gnm_float const db = value_get_as_float (b);
return gnumabs (da - db);
}
default:
return TYPE_MISMATCH;
}
}
/**
* value_compare :
*
* @a : value a
* @b : value b
* @case_sensitive : are string comparisons case sensitive.
*
* IGNORES format.
*/
GnmValDiff
value_compare (GnmValue const *a, GnmValue const *b, gboolean case_sensitive)
{
GnmValueType ta, tb;
/* Handle trivial and double NULL case */
if (a == b)
return IS_EQUAL;
ta = VALUE_IS_EMPTY (a) ? VALUE_EMPTY : a->type;
tb = VALUE_IS_EMPTY (b) ? VALUE_EMPTY : b->type;
/* string > empty */
if (ta == VALUE_STRING) {
switch (tb) {
/* Strings are > (empty, or number) */
case VALUE_EMPTY :
if (*a->v_str.val->str == '\0')
return IS_EQUAL;
case VALUE_INTEGER : case VALUE_FLOAT :
return IS_GREATER;
/* Strings are < FALSE ?? */
case VALUE_BOOLEAN :
return IS_LESS;
/* If both are strings compare as string */
case VALUE_STRING :
{
gint t;
if (case_sensitive) {
t = g_utf8_collate (a->v_str.val->str, b->v_str.val->str);
} else {
char *str_a = g_utf8_casefold (a->v_str.val->str, -1);
char *str_b = g_utf8_casefold (b->v_str.val->str, -1);
t = g_utf8_collate (str_a, str_b);
g_free (str_a);
g_free (str_b);
}
if (t == 0)
return IS_EQUAL;
else if (t > 0)
return IS_GREATER;
else
return IS_LESS;
}
default :
return TYPE_MISMATCH;
}
} else if (tb == VALUE_STRING) {
switch (ta) {
/* (empty, or number) < String */
case VALUE_EMPTY :
if (*b->v_str.val->str == '\0')
return IS_EQUAL;
case VALUE_INTEGER : case VALUE_FLOAT :
return IS_LESS;
/* Strings are < FALSE ?? */
case VALUE_BOOLEAN :
return IS_GREATER;
default :
return TYPE_MISMATCH;
}
}
/* Booleans > all numbers (Why did excel do this ?? ) */
if (ta == VALUE_BOOLEAN && (tb == VALUE_INTEGER || tb == VALUE_FLOAT))
return IS_GREATER;
if (tb == VALUE_BOOLEAN && (ta == VALUE_INTEGER || ta == VALUE_FLOAT))
return IS_LESS;
switch ((ta > tb) ? ta : tb) {
case VALUE_EMPTY: /* Empty Empty compare */
return IS_EQUAL;
case VALUE_BOOLEAN:
return compare_bool_bool (a, b);
case VALUE_INTEGER:
return compare_int_int (a, b);
case VALUE_FLOAT:
return compare_float_float (a, b);
default:
return TYPE_MISMATCH;
}
}
void
value_set_fmt (GnmValue *v, GnmFormat const *fmt)
{
if (fmt != NULL)
style_format_ref ((GnmFormat *)fmt);
if (VALUE_FMT (v) != NULL)
style_format_unref (VALUE_FMT (v));
VALUE_FMT (v) = (GnmFormat *)fmt;
}
/****************************************************************************/
#if 0
static gboolean
criteria_test_equal (GnmValue const *x, GnmValue const *y)
{
if (x == NULL || y == NULL)
return FALSE;
if (VALUE_IS_NUMBER (x) && VALUE_IS_NUMBER (y))
return (value_get_as_float (x) == value_get_as_float (y));
else
return (x->type == VALUE_STRING &&
y->type == VALUE_STRING &&
g_ascii_strcasecmp (x->v_str.val->str, y->v_str.val->str) == 0);
}
static gboolean
criteria_test_unequal (GnmValue const *x, GnmValue const *y)
{
if (x == NULL)
return y != NULL;
if (y == NULL)
return TRUE;
if (VALUE_IS_NUMBER (x) && VALUE_IS_NUMBER (y))
return (value_get_as_float (x) != value_get_as_float (y));
else
/* Hmm... Is this really right? number vs string, not unequal? */
return (x->type == VALUE_STRING &&
y->type == VALUE_STRING &&
g_ascii_strcasecmp (x->v_str.val->str, y->v_str.val->str) != 0);
}
static gboolean
criteria_test_less (GnmValue const *x, GnmValue const *y)
{
if (x == NULL || y == NULL)
return FALSE;
if (VALUE_IS_NUMBER (x) && VALUE_IS_NUMBER (y))
return (value_get_as_float (x) < value_get_as_float (y));
else
return FALSE;
}
static gboolean
criteria_test_greater (GnmValue const *x, GnmValue const *y)
{
if (x == NULL || y == NULL)
return FALSE;
if (VALUE_IS_NUMBER (x) && VALUE_IS_NUMBER (y))
return (value_get_as_float (x) > value_get_as_float (y));
else
return FALSE;
}
static gboolean
criteria_test_less_or_equal (GnmValue const *x, GnmValue const *y)
{
if (x == NULL || y == NULL)
return FALSE;
if (VALUE_IS_NUMBER (x) && VALUE_IS_NUMBER (y))
return (value_get_as_float (x) <= value_get_as_float (y));
else
return FALSE;
}
static gboolean
criteria_test_greater_or_equal (GnmValue const *x, GnmValue const *y)
{
if (x == NULL || y == NULL)
return FALSE;
if (VALUE_IS_NUMBER (x) && VALUE_IS_NUMBER (y))
return (value_get_as_float (x) >= value_get_as_float (y));
else
return FALSE;
}
#endif // 0 - unused, jsled
#if 0
--jsled
/*
* Finds a column index of a field.
*/
int
find_column_of_field (GnmEvalPos const *ep, GnmValue *database, GnmValue *field)
{
Sheet *sheet;
GnmCell *cell;
gchar *field_name;
int begin_col, end_col, row, n, column;
int offset;
offset = database->v_range.cell.a.col;
if (field->type == VALUE_INTEGER)
return value_get_as_int (field) + offset - 1;
if (field->type != VALUE_STRING)
return -1;
sheet = eval_sheet (database->v_range.cell.a.sheet, ep->sheet);
field_name = value_get_as_string (field);
column = -1;
/* find the column that is labeled after `field_name' */
begin_col = database->v_range.cell.a.col;
end_col = database->v_range.cell.b.col;
row = database->v_range.cell.a.row;
for (n = begin_col; n <= end_col; n++) {
char const *txt;
gboolean match;
cell = sheet_cell_get (sheet, n, row);
if (cell == NULL)
continue;
cell_eval (cell);
txt = cell->value
? value_peek_string (cell->value)
: "";
match = (g_ascii_strcasecmp (field_name, txt) == 0);
if (match) {
column = n;
break;
}
}
g_free (field_name);
return column;
}
/*
* Frees the allocated memory.
*/
void
free_criterias (GSList *criterias)
{
GSList *list = criterias;
while (criterias != NULL) {
GSList *l;
database_criteria_t *criteria = criterias->data;
for (l = criteria->conditions; l; l = l->next) {
func_criteria_t *cond = l->data;
value_release (cond->x);
g_free (cond);
}
g_slist_free (criteria->conditions);
g_free (criteria);
criterias = criterias->next;
}
g_slist_free (list);
}
/**
* parse_criteria :
* @crit_val : #GnmValue
* @fun : #criteria_test_fun_t result
* @test_value : #GnmValue the value to compare against.
* @iter_flags :
* @date_conv : #GnmDateConventions
*
* If @crit_val is a number set @text_val and @fun to test for equality, other
* wise parse it as a string and see if it matches one of the available
* operators. Caller is responsible for freeing @test_value.
**/
void
parse_criteria (GnmValue *crit_val, criteria_test_fun_t *fun, GnmValue **test_value,
CellIterFlags *iter_flags, GnmDateConventions const *date_conv)
{
int len;
char const *criteria;
if (iter_flags)
*iter_flags = CELL_ITER_IGNORE_BLANK;
if (VALUE_IS_NUMBER (crit_val)) {
*fun = criteria_test_equal;
*test_value = value_dup (crit_val);
return;
}
criteria = value_peek_string (crit_val);
if (strncmp (criteria, "<=", 2) == 0) {
*fun = criteria_test_less_or_equal;
len = 2;
} else if (strncmp (criteria, ">=", 2) == 0) {
*fun = criteria_test_greater_or_equal;
len = 2;
} else if (strncmp (criteria, "<>", 2) == 0) {
*fun = (criteria_test_fun_t) criteria_test_unequal;
len = 2;
if (iter_flags)
*iter_flags = CELL_ITER_ALL;
} else if (*criteria == '<') {
*fun = (criteria_test_fun_t) criteria_test_less;
len = 1;
} else if (*criteria == '=') {
*fun = (criteria_test_fun_t) criteria_test_equal;
len = 1;
} else if (*criteria == '>') {
*fun = (criteria_test_fun_t) criteria_test_greater;
len = 1;
} else {
*fun = (criteria_test_fun_t) criteria_test_equal;
len = 0;
}
*test_value = format_match (criteria + len, NULL, date_conv);
if (*test_value == NULL)
*test_value = value_new_string (criteria + len);
}
static GSList *
parse_criteria_range (Sheet *sheet, int b_col, int b_row, int e_col, int e_row,
int *field_ind)
{
database_criteria_t *new_criteria;
GSList *criterias = NULL;
GSList *conditions;
GnmCell *cell;
func_criteria_t *cond;
GnmDateConventions const *date_conv = workbook_date_conv (sheet->workbook);
int i, j;
for (i = b_row; i <= e_row; i++) {
new_criteria = g_new (database_criteria_t, 1);
conditions = NULL;
for (j = b_col; j <= e_col; j++) {
cell = sheet_cell_get (sheet, j, i);
if (cell != NULL)
cell_eval (cell);
if (cell_is_empty (cell))
continue;
cond = g_new (func_criteria_t, 1);
parse_criteria (cell->value, &cond->fun, &cond->x, NULL, date_conv);
cond->column = (field_ind != NULL) ? field_ind[j - b_col] : j - b_col;
conditions = g_slist_append (conditions, cond);
}
new_criteria->conditions = conditions;
criterias = g_slist_append (criterias, new_criteria);
}
return criterias;
}
/*
* Parses the criteria cell range.
*/
GSList *
parse_database_criteria (GnmEvalPos const *ep, GnmValue *database, GnmValue *criteria)
{
Sheet *sheet;
GnmCell *cell;
int i;
int b_col, b_row, e_col, e_row;
int *field_ind;
sheet = eval_sheet (criteria->v_range.cell.a.sheet, ep->sheet);
b_col = criteria->v_range.cell.a.col;
b_row = criteria->v_range.cell.a.row;
e_col = criteria->v_range.cell.b.col;
e_row = criteria->v_range.cell.b.row;
/* FIXME: are we sure that e_col>=b_col? */
/* Find the index numbers for the columns of criterias */
field_ind = g_alloca (sizeof (int) * (e_col - b_col + 1));
for (i = b_col; i <= e_col; i++) {
cell = sheet_cell_get (sheet, i, b_row);
if (cell == NULL)
continue;
cell_eval (cell);
if (cell_is_empty (cell))
continue;
field_ind[i - b_col] =
find_column_of_field (ep, database, cell->value);
if (field_ind[i - b_col] == -1)
return NULL;
}
return parse_criteria_range (sheet, b_col, b_row + 1,
e_col, e_row, field_ind);
}
/* Finds the rows from the given database that match the criteria.
*/
GSList *
find_rows_that_match (Sheet *sheet, int first_col, int first_row,
int last_col, int last_row,
GSList *criterias, gboolean unique_only)
{
GSList *current, *conditions, *rows;
GnmCell *test_cell;
int row, add_flag;
rows = NULL;
for (row = first_row; row <= last_row; row++) {
current = criterias;
add_flag = 1;
for (current = criterias; current != NULL;
current = current->next) {
database_criteria_t *current_criteria;
add_flag = 1;
current_criteria = current->data;
conditions = current_criteria->conditions;
while (conditions != NULL) {
func_criteria_t const *cond = conditions->data;
test_cell = sheet_cell_get (sheet,
first_col + cond->column, row);
if (test_cell != NULL)
cell_eval (test_cell);
if (cell_is_empty (test_cell))
continue;
if (!cond->fun (test_cell->value, cond->x)) {
add_flag = 0;
break;
}
conditions = conditions->next;
}
if (add_flag)
break;
}
if (add_flag) {
gint *p;
if (unique_only) {
GSList *c;
GnmCell *cell;
gint i, trow;
for (c = rows; c != NULL; c = c->next) {
trow = *((gint *) c->data);
for (i = first_col; i <= last_col; i++) {
char const *t1, *t2;
test_cell =
sheet_cell_get (sheet, i, trow);
cell =
sheet_cell_get (sheet, i, row);
t1 = cell->value
? value_peek_string (cell->value)
: "";
t2 = test_cell->value
? value_peek_string (test_cell->value)
: "";
if (strcmp (t1, t2) != 0)
goto row_ok;
}
goto filter_row;
row_ok:
;
}
}
p = g_new (gint, 1);
*p = row;
rows = g_slist_prepend (rows, (gpointer) p);
filter_row:
;
}
}
return g_slist_reverse (rows);
}
#endif // 0 --jsled
/****************************************************************************/
GnmValueErr const value_terminate_err = { VALUE_ERROR, NULL, NULL };
static GnmValueInt const the_value_zero = { VALUE_INTEGER, NULL, 0 };
GnmValue const *value_zero = (GnmValue const *)&the_value_zero;
void
value_init (void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS (standard_errors); i++) {
standard_errors[i].locale_name = _(standard_errors[i].C_name);
standard_errors[i].locale_name_str =
gnm_string_get (standard_errors[i].locale_name);
}
#if USE_VALUE_POOLS
/* GnmValueInt and GnmValueBool ought to have the same size. */
value_int_pool =
gnm_mem_chunk_new ("value int/bool pool",
MAX (sizeof (GnmValueInt), sizeof (GnmValueBool)),
16 * 1024 - 128);
value_float_pool =
gnm_mem_chunk_new ("value float pool",
sizeof (GnmValueFloat),
16 * 1024 - 128);
value_error_pool =
gnm_mem_chunk_new ("value error pool",
sizeof (GnmValueErr),
16 * 1024 - 128);
value_string_pool =
gnm_mem_chunk_new ("value string pool",
sizeof (GnmValueStr),
16 * 1024 - 128);
value_range_pool =
gnm_mem_chunk_new ("value range pool",
sizeof (GnmValueRange),
16 * 1024 - 128);
value_array_pool =
gnm_mem_chunk_new ("value array pool",
sizeof (GnmValueArray),
16 * 1024 - 128);
#endif
}
void
value_shutdown (void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS (standard_errors); i++) {
gnm_string_unref (standard_errors[i].locale_name_str);
standard_errors[i].locale_name_str = NULL;
}
#if USE_VALUE_POOLS
gnm_mem_chunk_destroy (value_int_pool, FALSE);
value_int_pool = NULL;
gnm_mem_chunk_destroy (value_float_pool, FALSE);
value_float_pool = NULL;
gnm_mem_chunk_destroy (value_error_pool, FALSE);
value_error_pool = NULL;
gnm_mem_chunk_destroy (value_string_pool, FALSE);
value_string_pool = NULL;
gnm_mem_chunk_destroy (value_range_pool, FALSE);
value_range_pool = NULL;
gnm_mem_chunk_destroy (value_array_pool, FALSE);
value_array_pool = NULL;
#endif
}
/****************************************************************************/